The JSONP Hack

| Comments

We skipped over the JSONP hack in the last post (My First API: Fetching and Displaying Tweets). This post takes a look at what happens in the background when we use JSONP.

We previously used jQuery’s getJSON method to request data from twitter’s server - just give getJSON a URL and it requests data for you. Our endpoint URL contained two parameters:

  1. The search query, q
  2. The callback, callback

Here’s the same endpoint again:

http://search.twitter.com/search.json?q=cats&callback=?

We included a callback parameter to make use of the JSONP hack. Otherwise, getJSON requests the data we’re after using XMLHttpRequest - a very sophisticated JavaScript object that’s more powerful but also more law abiding than JSONP.

XMLHttpRequest is subject to the same-origin policy. For security reasons, browsers block data requests made to third-party domains (i.e., domains other than the domain hosting the web page). So, if a page originates from Server X it shouldn’t make a request to Server Y. As Cross Origin Resource Sharing (CORS) limits the security issues that were once present, XMLHttpRequest may soon be the default for cross-domain API requests, but for now we still need JSONP to get over this security hurdle.

The JSONP hack uses the script tag’s security pass. Surprisingly the script tag is not restricted by the same-origin policy, so it can request and execute JavaScript from any server. This is a great benefit in our era of information sharing across the internet.

If we were to write our own version of getJSON, then it might go something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  var myGetJSON = function(endpoint, callback) {

    // Create a script tag.
    var script = document.createElement("script");

    // Create a unique random string of characters to be used as a
    // function name. We don't want to clash with any existing
    // function names.
    var functionName = Math.random().toString(36).substring(7);

    // Set the value of the endpoint's callback parameter to functionName:
    // "http://search.twitter.com/search.json?q=cats&callback=?"
    // becomes
    // "http://search.twitter.com/search.json?q=cats&callback=randomString"
    endpoint = endpoint.replace("callback=?", "callback=" + functionName);

    // Set the script tag's src attribute to the endpoint.  
    script.src = endpoint;

    // Dynamically create and attach to the window object
    // a new method with the same name as the callback parameter.
    window[functionName] = function(data) {
      // NB DATA PASSED TO THE myGetJSON CALLBACK
      callback(data);
    };

    // Append script tag to webpage body
    document.getElementsByTagName("head")[0].appendChild(script);

  };

  // TO TEST:
  myGetJSON("http://search.twitter.com/search.json?q=cats&callback=?", function(data) {
    console.log(data);
  });

As soon as the script tag is appended to the body, the browser calls the endpoint. A JSONP savvy API will look for the callback parameter in the endpoint. That is the indicator the client is expecting a JSONP response. The server responds with an invocation of the dynamically created method (named the value of the callback parameter), passing the JSON object:

1
randomString({load of tweets});

Comments