Supporting the jsonp callback protocol with jQuery and Java

December 12, 2009

by Jason Buberel

14 comments

It’s time for a nerd break here on the Altos Research blog to help balance Scott’s bout of analytic analysis – so those of you who don’t write code for a living, it is probably best to avert your eyes.

While working on a new feature to make it easier for our users to send email copies of reports to their contacts, we ran into a few difficulties trying to debug errors in our email address search function. Our plan was to use the jQuery autocomplete plugin along with the standard jQuery JSON support to build this new feature.

But in our initial implementation of the code, we were seeing some rather cryptic error messages in both the Google Chrome JavaScript debugger (“Unexpected Token : “) as well as in the Firebug JavaScript console (“invalid label”). Read on for a detailed description of our solution, along with some sample code for others to use and improve.

The jQuery code snippet we’d borrowed from the example code section on the jQuery.getJSON() documentation page is fairly straightforward, and uses the ‘callback’ approach in order to allow the request originating from the browser to be sent directly to the server. In this example, we’re just logging the output to the Firebug Console:

$.getJSON('http://server.hostname.com/dostuff?format=json&jsoncallback=?',
function(data){
console.log(data);
});

On the server side, we are using the wonderful Gson library for converting Java objects into JSON-formatted output. A typical example, using a List of Strings, looks like this:

Gson g = new Gson();
List results = new ArrayList();
results.add("test@domain.com");
results.add("test2@domain.com");
results.add("test3@domain.com");
String jsonOutput = g.toJson(results);

Using this simple list of strings, the JSON output will look like this:

["test@domain.com","test2@domain.com","test3@domain.com"]

Unfortunately, if you feed that JSON output to the callback form of the jQuery.getJSON() function, it will cause the JavaScript interpreters on both Google Chrome and Firefox to throw an exception. In Google Chrome, the error message will be “Unexpected token : ” and in Firefox “invalid label”.

The cause of the problem is that the callback form of the jQuery.getJSON() function expects the return value from the server to be a JavaScript function, not simply a JSON-formatted string. That is why the last parameter in the URL used to invoke the jQuery.getJSON() method is that mysterious looking ‘jsoncallback=?’:

http://server.hostname.com/dostuff?format=json&jsoncallback=?

This causes jQuery to generate a random JavaScript function name, and pass it along to the server using the ‘jsoncallback’ request parameter. Armed with this information, the server-side solution is fairly simple. You need to check the incoming HTTP request object for a ‘jsoncallback’ URL parameter:

String jsonCallbackParam = httpRequest.getParameter("jsoncallback");

NOTE: In the interest of good secure coding practices, friend and crazy-smart engineer Nathan Schrenk has kindly pointed out to me that the blind acceptance and use of this parameter represents a very exploitable security hole. So instead of just getting/using the request parameter, you need to make absolutely sure that the value is not being used to inject evil JavaScript. Along with the Apache Commons Lang library for Java, I recommend something like the following for sanitizing the value. This seems to well for the random method names that are generated by the jQuery JSON library:

public static String sanitizeJsonpParam(String s) {
if ( StringUtils.isEmpty(s)) return null;
if ( !StringUtils.startsWithIgnoreCase(s,"jsonp")) return null;
if ( StringUtils.length(s) > 128 ) return null;
if ( !s.matches("^jsonp\\d+$")) return null;
return s;
}

If that parameter is not null or empty, you know that your service has been invoked by a client expecting JavaScript-callback-formatted output, and not a simple JSON-formatted reply. Assuming you’re JSON string has been generated (see above), this simple conditional statement will take care of properly formatting the output:

if ( jsonCallbackParam != null ) response = jsonCallbackParam + "(" + jsonString + ");";
else response = jsonString;
outputStream.write(response.getBytes());

With this in place, you now have a server-side Java web service that will automatically support both jsonp callback requests as well as plain JSON formatted output.

If I’ve made any syntax errors in the examples above, please comment below. Keep in mind that this is not meant to demonstrate a fully production-ready implementation, but a solution to a rather cryptic error message.

Reblog this post [with Zemanta]

Previous post:

Next post: