Skip to content

Improved Testing JSONP Without a Server

May 6, 2013

The previous post presents a quick and dirty way to create a server for JSONP testing. While it works for the example provided, the assumption that the callback name is fixed creates shortcomings when used with many Javascript libraries.

When using some libraries, an ajax handler will proxy your callback through its own dynamically generated callback, so you cannot know a priori what function name to return in the JSONP response. For example, when using the YUI JSONP library, the callback requested in the URL query arguments function looks like:
/?callback=YUI.Env.JSONP.yui_3_9_1_1_1367896794667_292.

The jQuery example from the last post suggested using:

function myCallback(data) {
    // Here I can handle the response
}
 
$.ajax({type: 'GET', url: 'http://localhost:50000', dataType: 'jsonp'});

Because the test JSONP server was equipped to only respond with a fixed callback name (here “myCallback”), it works by bypassing the jQuery response handler (and possibly missing any cleanup code that the ajax function needs to do). Usually a JSONP query is sent like so:

$.ajax({
    type: 'GET',
    url: 'http://localhost:50000',
    dataType: 'jsonp',
    success: function(data) {
        // Here I can handle the response
    }
});

In this case, the anonymous function that handles a successful response will be invoked by jQuery, and my server receives the request:
/?callback=jQuery18204170536657329649_1367898203473&_=1367898203496.

So instead of using netcat, an approach that works more universally is to make an HTTP server. The server may examine the query to determine the requested callback function, for example using a regex. With a scripting language such as Python, this is not difficult to do:

#!/usr/bin/python
 
import optparse
import re
import socket
import sys
 
def main():
    """Basic JSONP tester that serves static json response."""
    parser = optparse.OptionParser("usage: %prog [options] file_to_serve")
    parser.add_option("-p", "--port", default=50000, type=int,
            help="Server port")
    parser.add_option("-d", "--debug", default=False, action="store_true",
            help="Show incoming headers")
    (options, args) = parser.parse_args()
    if len(args) != 1:
        parser.error("incorrect number of arguments")
 
    contents = ""
    with open(args[0], 'r') as json_file:
        contents = json_file.read().strip()
 
    response_template = \
            "HTTP/1.1 200\nContent-Type:application/javascript\n\n{0}({1});"
 
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(("", options.port))
    sock.listen(1)
    while True:
        (client, _) = sock.accept()
        data = client.recv(1024)
        if options.debug:
            print data
        match = re.search(r"callback=(\S+?)[\s&]", data)
        callback = match.group(1) if match else "callback"
        response = response_template.format(callback, contents)
        client.send(response)
        client.close()
    sock.close()
    return 0
 
if __name__ == "__main__":
    sys.exit(main())

To launch the server, just invoke it providing the name a file that contains the static JSON response to return (unlike the example in the last article, it should consist of only the valid JSON response, and not contain any HTTP header or function name).

Leave a Reply

Your email address will not be published. Required fields are marked *