We were recently asked to develop some javascript widgets that would read from a central REST based API. And of course, these widgets needed to be able to be put on any website. That’s all well and good until Cross Site Scripting rears it’s ugly head. As of right now, modern browser don’t allow AJAX calls to be made from one domain to another. There are a lot of potential security problems with cross domain scripting, though they’re not really my concern. For the purposes of this article, I’m only concerned with how we can work around these browser restrictions.
Luckily there are a few options. The first of which is to use JSONP. If you’re not familiar with JSONP, you can get caught up here. The problem with JSONP is that it only works for GET requests. In simple situations, that will probably suffice. But in more complex situations, where you might need to use POST, PUT, or DELETE, another solution is going to be needed. But more about that later… Back to JSONP. jQuery has a pretty simple JSONP implementation. Let’s take a look at a regular jQuery JSON GET request.
$.getJSON("test.js", function(json) {
alert("JSON Data: " + json);
});
Nothing too out of the ordinary there. It makes a request to test.js and the callback function receives the results of the request as a json object. Suppose that test.js is now a different domain than the script that is calling it. This is when you need to use JSONP. With jQuery, making a JSONP request is quite easy. All you need to do is append a query string argument called jsoncallback, that is the name of the function that’s used as your JSONP wrapper. Let’s look at an example:
$.getJSON("http://someotherdomain.ca/test.js?jsoncallback=myFunction", function(json) {
alert("JSON Data: " + json);
});
and where the GET request to http://someotherdomain.ca/test.js returns something like this:
myFunction({data: 'value'});
The trick with JSONP is that the server that you’re making the GET request to, has to be returning the JSON in a wrapper function and you need to know the name of that function. Alternatively, jQuery allows you to not specify the name of the callback function and use a ? as a placeholder instead. jQuery will then generate a unique callback function name and pass that as a parameter in the GET request.
$.getJSON("http://someotherdomain.ca/test.js?jsoncallback=?", function(json) {
alert("JSON Data: " + json);
});
What gets sent to the server looks something like this: http://someotherdomain.ca/test.js?jsoncallback=12049583_dss. The code running on the someotherdomain.ca server will need to check that the jsoncallback query parameter exists and then return a response like:
12049583_dss({data: 'value'});
This can get tricky if you’re not in control of the code that you’re making requests to. JSONP isn’t a solution for all situations; it really only covers a subset of use cases for cross domain scripting. If you find that you’re unable to utilize JSONP, you may need to move to a more robust solution.
Enter flash proxies. Flash can make cross domain requests. All that’s required is that the server that you’re calling as a file in it’s root directory called crossdomain.xml. It will look something like this:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<!-- Policy file for xmlsocket://socks.mysite.com -->
<cross-domain-policy>
<site-control permitted-cross-domain-policies="master-only" />
<allow-access-from domain="*" />
<allow-http-request-headers-from domain="*" headers="*" />
</cross-domain-policy>
The crossdomain.xml is a pretty powerful little file and care needs to be taken when using it. Now that the server can accept cross domain requests, we need to find the right flash proxy to make those requests. The best one that I could find is called flXHR. flXHR only requires the inclusion of a javascript file, then uses javascript to write a hidden swf to the dom, and then uses that swf to make the cross domain requests. It has an API identical to the XmlHttpRequest object, so if you’re comfortable working with the XmlHttpRequest object, you’ll already be comfortable working with flXHR. Plus, flXHR offers plugins for most of the major javascript frameworks. Since we were already using jQuery, let’s take a look at an example of using flXHR with jQuery.
First we include all the files we need:
<!-- include the flXHR file -->
<script type="text/javascript" src="flXHR.js"></script>
<!-- include the most recent version of jQuery -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<!-- include an xhr jquery plugin, used from the flXHR jquery plugin -->
<script type="text/javascript" src="jquery.xhr.js"></script>
<!-- include the flXHR jquery plugin -->
<script type="text/javascript" src="jquery.flXHRproxy.js"></script>
Then let’s write the javascript to make the requests:
$(document).ready(function() {
// set up the domain that we're going to call and tell flXHR not to try to parse the response as XML..
$.flXHRproxy.registerOptions("http://someotherdomain.ca/", {xmlResponseText:false});
// set flXHR as the default XHR object used in jQuery AJAX requests
$.ajaxSetup({transport:'flXHRproxy'});
// make a post request
$.post(
"http://someotherdomain.ca/some-process.php",
{param1: 'abc', param2: 'xyz'},
function(json) {
alert("JSON Data: " + json);
},
'json'
);
});
And that’s about it! Now you’ll be able to make cross domain POST requests.