Tags: , , | Posted by Kevin Babcock on 2/26/2010 12:55 AM | Comments (0)

Most web developers are keenly aware of the browser’s same-origin policy which prevents document elements (including scripts) from accessing elements of a document from a different origin. A document is considered to be of the same origin if it shares the same protocol, port, and host. For example, http://www.myviewstate.net and http://www.myviewstate.net/blog have the same origin; on the other hand, they do not share the same origin with URLs such as https://www.myviewstate.net (different protocol) or http://myviewstate.net (different host). This is generally a good security measure as it helps prevent access to the document by sites that you did not visit. Granted, there are still security vulnerabilities that can be used to work around this policy, but that’s a topic for another day.

One drawback to this security feature is that is requires web developers to go through an intermediary server on the same domain to load content from other sites into a local page. For example, a page wanting to load data from Twitter’s search API might create a server-side proxy to negotiate communication between the client and the remote server. This is a good practice for most sites. However, it is still possible to make such a request directly from client script using the JSON with padding (JSONP) pattern. With JSONP a script tag pointing to the remote server is injected into the page, forcing the browser to download the remote content since script tags are not subject to the cross-domain policy restriction. The server on the other end “pads” the response with a callback function that is injected into the page and executed. In the following example the browser provides the name of the callback by way of a query string parameter in the URL.

<script type='text/javascript' 
        src='http://search.twitter.com/search.json?callback=parseData&q=jquery'>
</script>

If the service on the other end supports JSONP it will pad the returned JSON data with the provided function name.

parseData({"results":[{"profile_image_url":"…”}…]})

When the script is injected into the page, parseData is executed and you immediately have access to the JSON data returned from the remote server.

JSONP & jQuery

jQuery provides built-in support for this functionality through use of the jQuery.ajax member. I’ve discussed some of its features in previous posts, but in summary this function can be used to make just about every kind of Ajax request. To make a JSONP call and process the response you must provide a minimum of the following settings to the function:

$.ajax({
    url: 'http://search.twitter.com/search.json?q=jquery',
    dataType: 'jsonp',
    success: function(data) {
        // process result data
    }
});

jQuery automatically generates a callback function, which it attaches to the window property of the DOM, and adds a unique callback query string parameter to the request. The requested URL looks something like this: http://search.twitter.com/search.json?q=jquery&callback=jsonp1267158972337. By letting jQuery auto generate the callback function, you can access the results via the function assigned to the success setting that you pass into jQuery.ajax.

If you want to define your own callback handler, you can specify it using the jsonpCallback setting passed to jQuery.ajax.

$.ajax({
    url: 'http://search.twitter.com/search.json?q=jquery',
    dataType: 'jsonp',
    jsonpCallback: 'myCallback'
});

function myCallback(data) {
    // process result data
}

The requested URL for this request would look like this: http://search.twitter.com/search.json?q=jquery&callback=myCallback. When you provide your own callback handler the handler generated by jQuery is not called, so providing a function for the success setting is pointless in this case.

If you need to provide the name of the query string parameter uses to define the callback function, you can do so with the jsonp setting. This is useful for Ajax calls to a server that expects a different parameter than the default (callback). A good example of this is the Bing API, which expects a query string parameter called JsonCallback to contain the name of the callback function.

$.ajax({
    url: 'http://api.search.live.net/json.aspx?Appid=<id>&JsonType=callback&sources=web&query=jQuery',
    dataType: 'jsonp',
    jsonp: 'JsonCallback',
    jsonpCallback: 'searchResults'
});

function searchResults(data) {
    // process search results
}

The requested URL for this request would look like this: http://api.search.live.net/json.aspx?Appid=<id>&JsonType=callback&sources=web&query=jQuery&JsonCallback=searchResults.

One final option for making a cross-domain request using the jQuery library is to use the higher-level function jQuery.getJSON. This function merely abstracts the settings typically passed to jQuery.ajax, replacing them with default values. This means you get a function that is easier to use, but less flexible.

$.getJSON('http://search.twitter.com/search.json?q=jquery&callback=?', 
    function(data) {
        // process result data
    }
);

Final Thoughts

Having the ability to make cross-domain requests can be quite useful, but only when used wisely. Remember that with this method you are injecting a remote script into your page; it is best to use this functionality only when you absolutely trust that the content returned from the remote server is not malicious.

Thoughts?