Have you ever found yourself needing to query data from another domain in Javascript? Because data in Javascript is usually fetched via AJAX, you will find yourself inside the Javascript sandbox of the Same Origin Policy (SOP). This leads you to immediately finding a way to circumvent this security via something like a proxy server. Fortunately for us, it is well known that the script tag’s “src” attribute is not limited to this restriction. Using this behavior, we can easily query data from across domains without the use of a proxy. The concept that I will be showing below is JSONP proposed by Bob Ippolito
To start with, lets take a look at how JSONP works. As a great example, go to this URL and look at the very beginning of the JSON response. It leads right into the JSON. Now, lets go to the same URL, with a callback variable in the query string. Try this link. Now you can see that the JSON result is wrapped inside of “test(…)”. This is padding the JSON result, hence the name. Because this is wrapped inside a function call, when the data is returned to our requesting resource, it will be passed to a local function with the same name – in this case “test”. So, we can write something like this:
function test(data) { console.log(data); }
This would echo the result of the request out to our Javascript console.
To see a fully working example, lets include our Javascript library on the requesting end. If you are using JQuery, this functionality is built in, using the “callback=?” functionality, and an inline method. Congratulations – you are one of the cool people and you are done for a second. If you are using Prototype.js, you will need to extend its functionality with a community contributed patch, such as this one. Just include this in a separate file, or merge and minify, etc. Whatever makes you happy.
Lets talk about the server for a minute. If you are running Rails, lets setup a sample controller method that we can access. For example, I have the following:
# app/controllers/users_controller.rb def show respond_to do |format| format.json do render :inline => "#{params[:callback]('testing JSONP')" end end end
You can see that this action, show will respond to a Javascript request, and pad the result with the name of our callback variable. More on this in a second.
Now, lets fire up the server, and test out or new code. On the requesting end, do the following:
getJSON('http://path/to/server/users/show?callback=?', function(data) { console.log(data); } );
We have taken the Javascript function from earlier, and made it inline, with our getJSON call. Change your URL path to reflect your Rails server. You should get a response like below if you go to the Rails server directly in your browser:
container('testing JSONP')
Now, within your requesting page, when you reload you should see the response ‘testing JSONP’ in your console log. Congratulations, you have just served JSON cross-domain without going to jail for breaking the SOP law.
It turns out that Rails handles this beautifully. Per the documentation for render:
Sometimes the result isn’t handled directly by a script (such as when the request comes from a SCRIPT tag), so the :callback option is provided for these cases.
# Renders ‘show({“name”: “David”})’
render :json => {:name => “David”}.to_json, :callback => ‘show’
We can refactor our controller to look like this instead:
# app/controllers/users_controller.rb def show respond_to do |format| format.json do render :inline => 'testing JSONP', :callback => params[:callback] end end end
Rail’s render will pad the JSON response if a callback variable is present, or just serve the JSON response otherwise. Happy cross-site scripting!
I’m not sure if you’re aware of the changes with Rails 2.3+ and it being moved to a Rack-based implementation, and that this means you can easily insert middleware in the stack.
Regardless, here’s a middleware for handling JSONP without mucking up your code!
http://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/jsonp.rb
Which reminds me, we need to grab beers soon… been way too long.
LikeLike