WebAssembly in Action

Author of the book "WebAssembly in Action"
Save 40% with the code: ggallantbl
The book's original source code can be downloaded from the Manning website and GitHub. The GitHub repository includes an updated-code branch that has been adjusted to work with the latest version of Emscripten (currently version 3.1.44).

Friday, October 8, 2010

JSONP Overview and JSONP in HTML 5 Web Workers


If you are new to JSON (JavaScript Object Notation), my previous post 'JSON Overview' might be something you would like to read since this topic deals with JSON.


Same Origin Policy

Usually, a browser will prevent a web page from requesting data from an origin other than the one that served the page with what is known as the 'same origin policy'.

For example, if the current web page was served from Site1.com and you try to do an XMLHttpRequest to Site2.com, the request will usually fail. I say 'usually' because in some browsers the settings can be adjusted to allow for cross-domain requests.

Depending on your settings in Internet Explorer, doing a cross domain request could result in an 'Access is denied' error.

Firefox returns the error 'Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIXMLHttpRequest.send]'

Chrome and Safari both return the error 'NETWORK_ERR: XMLHttpRequest Exception101'


Certain HTML tags within the browser do not enforce the same origin policy. Two of the tags are img and script.

There are advantages of having some HTML tags that can make cross-domain requests like the img tag being able to display pictures from other sites as well as the script tag allowing for the use of 3rd party files/libraries without having to host locally.


Security Concerns with 3rd Party Script Includes

Before I go any further, it's important to understand that there are some security concerns with importing JavaScript files from 3rd party sites.

When you include a script tag on an HTML page, the browser will download the file and then evaluate the code converting it from text into functions, variables, objects, etc.

The browser will also run any global code that is within the file as it's evaluated and this is where the security concern comes in.

If you are including code from a 3rd party site it could be running code to try and find sensitive information in cookies, web storage, etc and then pass that back to the 3rd party server. You might not even notice the information being leaked unless you were running HTTP traffic monitoring software.


JSONP

If you simply included a script tag that points to a 3rd party site and that site returns pure JSON (e.g. {"FirstName" : "Sam", "LastName" : "Smith"} ) the JavaScript will be evaluated but the JSON data will be unusable because even though the browser will automatically convert that JSON string into an object, the object is not assigned to a variable of any kind.

Where JSONP (JSON with padding) comes in is that it allows you to tell the server to wrap the JSON data in a function call to a function that you specify as part of the URI.

The following is an example of a JSONP call that indicates HandleRequest is the function to include with the response JSON:
<script type="text/javascript" src="http://SomeServer.com?jsonp=HandleRequest"></script>

The following is what the received JavaScript would look like:
HandleRequest({ "FirstName": "Sam", "LastName": "Smith" });



Script Injection

Now that we know what JSONP is and how it works, the issue that we now face is that we need to be able to make requests to the server and we likely don't know what those requests are when the page is first rendered.

One method is to change the src attribute of an existing script tag as in the following example:
Within your HTML page:

<script type="text/javascript" id="JSONPScriptTag" src=""></script>


Within your JavaScript:

// Grab a reference to the tag and then adjust the src property so that the
// proper data is returned
var ScriptTag = document.getElementById("JSONPScriptTag");
ScriptTag.src = "http://SomeServer.com?jsonp=HandleRequest";

// The callback function for the JSONP request results
function HandleRequest(objJSON) {
alert("Data returned from the server...FirstName: " + objJSON.FirstName + " LastName: " + objJSON.LastName);
}

If you have access to the html of the page and will only be making a JSONP request once in a while then this approach might be good for you.

If you do not have access to the html of the page or you need to make multiple JSONP requests, perhaps even at the same time, the other approach is to do what is known as 'Script Injection' where, through code you create a script tag and add it to the page.

The following is an example of script injection:
// Build up the Script tag dynamically
var ScriptTag = document.createElement("script");
ScriptTag.setAttribute("type", "text/javascript");
ScriptTag.setAttribute("src", "http://SomeServer.com?jsonp=HandleRequest");

// Grab the page's Head tag and add the script tag as a child
var hHead = document.getElementsByTagName("head")[0];
hHead.appendChild(ScriptTag);

// The callback function for the JSONP request results
function HandleRequest(objJSON) {
alert("Data returned from the server...FirstName: " + objJSON.FirstName + " LastName: " + objJSON.LastName);
}



HTML 5 Web Workers and JSONP

If you are unfamiliar with HTML 5 Web Workers, you can read up on them with my blog post 'An Introduction to HTML 5 Web Workers' as well as my post 'A Deeper Look at HTML 5 Web Workers'.

New: I have recently written an article for DZone.com which combines the Web Worker information presented in my blog posts and introduces some new information like transferable objects, inline workers, and includes a link to a sample project stored on github.com. The article can be found here: http://refcardz.dzone.com/refcardz/html5-web-workers


Web workers run in a separate thread from the UI thread of the browser itself. As a result, the web worker threads do not have access to the DOM or any other UI elements.

Traditionally with JSONP you use Script Injection by modifying the DOM. In the case of web workers that is not an option. So how do we accomplish JSONP from a Web Worker?

The answer was surprisingly simple.

In a web worker, you have access to an importScripts function that lets you pull in JavaScript files/libraries to aid in processing.

As it turns out, you can use the importScripts function to make JSONP requests as in the following example:
// Helper function to make the server requests
function MakeServerRequest()
{
importScripts("http://SomeServer.com?jsonp=HandleRequest");
}

// Callback function for the JSONP result
function HandleRequest(objJSON)
{
// Up to you what you do with the data received. In this case I pass
// it back to the UI layer so that an alert can be displayed to prove
// to me that the JSONP request worked.
postMessage("Data returned from the server...FirstName: " + objJSON.FirstName + " LastName: " + objJSON.LastName);
}

// Trigger the server request for the JSONP data
MakeServerRequest();



In Closing

JSONP is a fairly easy way to obtain JSON data from an origin other than the origin that served the page currently being viewed. JSONP is even available from within HTML 5 Web Workers.

The main thing to remember with JSONP is that there is a security concern since the browser automatically loads and evaluates the code. The global code could be executed before your data is received and you may not be aware of it without tools that monitor the HTTP traffic.

2 comments:

  1. This is genius - I've been experimenting with JSONP solutions for Hive/PollenJs and this is brilliant. will credit.

    http://github.com/rwldrn/jquery-hive

    ReplyDelete
  2. I had no idea about jQuery.Hive. I just wrote a powerful SharedWorkers lib called "worker.io", which uses 2 APIs: Apis (latin for Bee), and Scriptorium (latin for Hive).

    https://github.com/cScarlson/worker.io

    It uses 'pseudo-native'-events for all custom-event emission/reception, not promises - so its as reliable as onmessage/postMessage:

    $bee() for client,
    $hive() for worker,
    $bee.on('broadcast', fn); //special event
    $hive.emit('broadcast', {}); //special emission

    Check it out!

    ReplyDelete