Friday, July 16, 2010

jQuery, JSONP, and the Same-Origin Policy

First of all, my apologies for the rather massive delay on my JavaScript template series.  The combination of a new job and a family doesn’t allow much time for blogging.  But the topic of client-side template engines is still a hot one, so I plan to resume the series in the near future.

I just finished reading the post on DotNetCurry about using jQuery to query Twitter’s public API.  It is a great post, but I thought that some developers might want some additional details on how exactly the code in the article is able to get around browsers’ same-origin policy.  At first glance, there appears to be some magic involved.

As I’m fond of saying, there is no magic in software development, so let’s see what is really going on:

Browsers prevent JavaScript code from accessing resources from a different domain than the one that delivered the page in the first place.  This is a security feature designed to prevent the browser from unwittingly passing (possibly sensitive) data from one site (your bank’s online account page, for example) to a malicious third party.  Unfortunately, this same policy means that well-meaning scripts are unable to access potentially useful external resources (a third-party web service like Twitter’s, for example).

Is there a loophole?  It turns out that the <script> tag is exempt from the same-origin policy.  It really must be, if you think about it: otherwise, a page would only be able to reference JavaScript files served from the same domain as the page.  Content delivery networks, for example, would be impossible as a result.

JSONP to the rescue.  JSONP stands for “JSON with Padding”.  Actually, I’ve always thought that this was a misleading name.  It is padded, in a sense, but I think a better name would have been “JSONC” for “JSON with Callback”.  Instead of using vanilla Ajax to call a remote web service that then returns JSON to our page, we put the URL to the remote web service in the src=”” attribute of a <script> tag and (here is the key part) include the name of a callback function in the URL.  The typical way this is done is to append callback=<functionname> to the end of the URL:

<script type="text/javascript" src="http://api.twitter.com/1/statuses/user_timeline/mattspeterson.json?callback=DisplayResults"></script>

We are responsible for writing the DisplayResults() function to handle the JSON data returned by the web service call and passed as a parameter to this function.  The remote web service is responsible for noticing that we have appended the name of a callback function and wrapping the results appropriately when they are sent to us.

But the example in the blog post referenced above didn’t use this technique.  So what gives?

It turns out that jQuery’s $ajax.getJSON() method is doing some extra leg-work for us.  Let’s have a look at a similar example:

<script type="text/javascript">

    $(document).ready(function () {
        $.getJSON("http://api.twitter.com/1/statuses/user_timeline/mattspeterson.json?callback=?", DisplayTweets);
    });

    function DisplayTweets(tweets)
    {
        // Bind the tweets returned to the DOM using your JavaScript template engine of choice...
    }

</script>

The getJSON() method looks for callback=? in the first parameter (the URL) and if it is present, dynamically creates a “throwaway” callback function and puts its name in the ? placeholder when it makes the Ajax call to the URL.

Using your web developer tool of choice (I’m using Google Chrome’s tools), you can verify that this is what is happening:

image

Let’s have a look at the response we get back from this call to the Twitter API:

image

The response isn’t JSON-encoded data, it is a call to the ad-hoc callback function that jQuery created for us on the fly with the data passed as a parameter.  So where is this callback function?  jQuery has dynamically added it to the DOM:

image

image

$ajax.getJSON() does one more bit of work for you: it calls $.parseJSON() to convert the JSON-encoded string returned from the Twitter web service to a JavaScript object before passing it to your named callback function:

image

Hope this helps shine a light on the inner workings of JSONP.  The lesson here is that jQuery really does a lot of the messy heavy lifting for you so that your web application can get around a well-intentioned security policy imposed by your browser.

1 comment:

Frex65 said...

Hi, great post - thank you. I've read scores of these, but yours was very easy to understand and drove the concept home at last.

Is there any way to use POST rather than GET in this scenario?