jQuery and JSONP

June 28th, 2011

For the sake of application decoupling on one of the Merb web apps we’re working on, we identified the separation of blocks that shouldn’t depend on each other, with the advantage of having fewer dependencies, and lower the risk of malfunction in one part of a system when the other part is changed.

So a Sinatra web app was built on another server/port, with a simple yet effective control panel for internal management and an API to generate JSON responses.

Some requests to that Sinatra app API are being made via jQuery originating on the Merb application. This represents a problem. Even if in the same server, but with a different port. The browser security model tells us that XMLHttpRequest, frames, etc. must have the same domain in order to communicate. Makes sense for security reasons, but at the same time distributed services feel the pain.

For production we use a server side proxy so that the request comes from the client to our (origin) domain and is dispatched to the Sinatra app on a port or server/port combination. But in development we use another technique to get around the same origin browser policy.

That’s where JSONP (JSON with Padding) makes its debut. jQuery > 1.5 brought some new features like the ability to control the callback configuration. If we specify an ajax call/request with dataType: "jsonp", jQuery adds a parameter to our request, with the standard name “callback”. That parameter name will be used as a function name to process data.

Callback function name can be overridden using the jsonp parameter, something like jsonp: 'onJSONLoad'. That will result in the callback parameter name change. So, we’d get http://new.server:port/service/parameter?onJSONLoad= followed by an unique stamp that jQuery creates for the request.

One of the advantages of controlling the callback parameter is related to better browser caching of GET requests, since it is set to false as default for ‘jsonp’ dataType ajax requests.

All the above in the client. What about the server ? Mind that the request is getting a new parameter. For the Sinatra app, we had to change the final response. So, based on the fact of the appearance of a callback parameter:

callback = params.delete('callback')
and in the end of the action:
final_response(callback, response)
with response being a well formed JSON object.

For the final_response method, we have:

def final_response( callback, res)
  if callback
    content_type :js
    response = "#{callback}(#{res})"
  else
    content_type :json
    response = res
  end
  response
end

This will make the necessary adjustment to the response for the case of cross domain requests or same (origin) domain requests.

So a simple jQuery function would be like:


$("#ep_search_medics").click(function() {
  $.ajax({
    type: 'get',
    dataType: 'jsonp',
    url: 'http://localhost:4567/dcipt/rosuvast',
    success: function(data) { $("#ep_search_results_nr").html(data.count); },
    error: function() { console.log("Error"); }
  });
  return false;
});

Final Notes:
- one can read more at the excellent jQuery page for ajax requests;
- be very confident on your JSON response. One can test for JSON validation in JSONLint;
- Google Chrome is an amazing browser to develop on, using the Developer Tools.