JSONP-DOM, JSONP without callback querystring.

While working on centrally hosted Backbase widgets we needed a JSONP solution that is cachable by proxies (Squid, akamai) as well for browsers. Where regular JSONP requires the response to wrap the response using a callback.

As we have full control on the response, the solution we came up with (and dubbed “JSONP-DOM”) is to use attributes on the script tag that includes the file:

<script
  type="text/javascript"
  src="myService?response=application/json"
  callback="MyLoader.requests[{someId}].success"
  callbackError="MyLoader.requests[{someId}].error"
></script>

In contrast to regular JSONP responders which wrap the response in the callback function, the JSONP DOM response needs to be  an interactive script, that has to lookup the script tag to find the callbacks it needs to call. The major benefit however is that it can be cached really aggressively.

Because this method allows you to define any number of callbacks, it also allows proxy script or response filters to trigger the callbackError as well for example to convert HTTP 500 from underlaying services in HTTP 200 responses, and loop them through to the callbackError trigger, thus allowing for proper error handling, instead of having to rely solely on client side timeouts to guess an error has occurred.

From a serverside perspective it requires you to do some additional work. A basic callback needs up to 30 lines of additional coding making it a heavy additional payload in case of small responses.

(function temp(doc) {
var respond = function() {
// Retrieve all document script tags
var scriptTags = doc.getElementsByTagName("script"), baseURI="", callBackName="";
 
// Reverse loop through script tags, assuming the
// script tag is added low in the document.
for(var i = scriptTags.length; i &gt; 0; i--) {
var s = scriptTags[i-1];
// Skip inline scripts
if(!s.src) {
continue;
}

// Hardcoded match on src attribute
if(s.src.indexOf("/myService") &gt; -1) {
if(s.callback &amp;&amp; s.callback.length &gt; 0) {
// Internet Explorer family testing
callbackName = s.callback;
baseURI = s.src;
// All other browser testing
} else if(s.hasAttribute &amp;&amp; s.hasAttribute("callback")) {
callbackName = s.getAttribute("callback");
baseURI = s.src;
} else {
alert('Unable to determine the callback to call!');
}
s.parentNode.removeChild(s);
break;
}
}
 if(callbackName) {
// Response arguments
var myData = {newMessage: false, "messages" : []};
eval(callbackName + "(myData)");
}
}
respond();
})(document);

Currently only a Backbase implementation exists; If time allows lets convert it into a jQuery plugin.

Be Sociable, Share!

Tags: , , , ,

Leave a Comment

This blog is kept spam free by WP-SpamFree.