Login Register

Cross Domain XMLHttpRequest using an IFrame Proxy

Note: The code for this feature is available in Dojo 0.4 and later. IE 7 Support in Dojo 0.4.1 and later.

Background

The browser security model does not allow using XMLHttpRequest (XHR) from one web page domain to contact an URL on another domain. However, there are cases when it would be nice to do cross domain XHR requests. There is a proposal in the W3C's Web API group to address this need (see this Mozilla tracking bug, and the bug comments for a link to the proposal).



As with most standards, it will take a while for this proper solution to saturate the marketplace. In the meantime, to get something like cross domain XHR requests today, there are the following options:

  • Set up a proxy server on the web page domain and have it forward the requests to the real XHR endpoint (requires server infrastructure).
  • Use Flash (user has to have Flash installed).
  • Use script tags (can do cross domain requests but return type must be JavaScript/JSON, and a callback mechanism needs to be established).
Another way to allow cross domain requests is to use the technique that is now available via dojo.io.XhrIframeProxy: use iframes that communicate with each other by changing URL fragment identifiers. This has the benefit of being just plain HTML and JavaScript (no additional server infrastructure or Flash), and it should be able to accommodate any asynchronous XHR request. It has been tested and works in IE 6.0, Firefox 1.5, Safari 2.0.3, and Opera 9.



It also contains a security mechanism that API providers can use to restrict the allowed cross-domain requests.

IFrames, Fragment Identifiers and XHR Proxying

Fragment Identifiers are the part of an URL that comes after the # sign:



http://www.a.com/path/to/file.html#fragmentIdentifier



A document in an IFrame can change the fragment identifier on its parent document (the document containing the IFrame). Changing the fragment identifier does not cause the page to reload. Similarly, the parent document can change an IFrame's fragment identifier without causing page reloads. Since the pages don't reload, state can be maintained inside the page.



To communicate between two cross domain documents :

  • A document (the Client document) defines an IFrame that loads the other document (the Server document).
  • Define a protocol to pass information through fragment identifiers.
  • Tell each document about the URL for the other document (so they can set the fragment identifiers correctly -- the browser needs a complete URL when setting a cross domain location).
  • Use a JavaScript timer to check for changes in the fragment identifiers.
To send an XHR request to another domain:

  • Define a JavaScript object that implements the XHR interface (a Facade).
  • Use that object instead of an actual XHR object.
  • For the Facade's send() method, serialize the request headers, method, URL and data.
  • The browser places a limit on the size of a document's URL, so the Client document breaks this serialized data into a set of fragment identifiers that will fit under the URL limit.
  • The Client document sends each fragment identifier to the Server document. The Server document sends an acknowledgement back to the Client, and the Client sends the next fragment identifier, until all are sent.
  • The Server document assembles the fragment identifier parts into the original serialized data, unpacks it into an object, then uses a real XHR object (now on the Server's domain) to do the final API service call.
  • The Server document then serializes the XHR response, and sends it back to the Client using fragment identifier segments.
  • The Client unpacks the serialized response, and sets the appropriate values on the XHR Facade.

Trade-Offs

Pros

  • 100% pure browser. No Flash or additional server infrastructure.
  • It can be dropped in fairly transparently to code that is already using XHR.
Cons
  • The technique uses IFrames and loads documents into the IFrames, so it takes more browser memory than native XHR. It would be interesting to compare the resource requirements with the amount needed to run Flash.
  • More network traffic to download xip_client.html and xip_server.html (the contents of the IFrames). However, you can configure your web server to tell the browser to cache these files for a very long time.
  • Timers are involved, with message serialization and deserialization.
  • Setting all of those URLs in the IFrames causes MSIE to make lots of those "clicking" sounds (the sound normally to indicate to the user they clicked on a link).

Security Considerations

This approach does not allow cross domain access to any XHR-enabled API service. For it to work, the API service must place the Server document (web page) on its server. That web page is given the Client URL and the XHR request in serialized form, so it can restrict who can contact the service and what types of requests are allowed. Note that all request validation happens inside the Server document's JavaScript.



You should not experiment with this technique unless you are very restrictive on the clients and API URLs that are allowed. Placing the Server document on your web server means opening up the allowed URLs to the world.

Dojo Implementation/Examples

As of 7/31/2006, the Dojo tree has support for XHR IFrame Proxying. The relevant files are:

  • src/io/XhrIframeProxy.js: the Dojo package, dojo.io.XhrIframeProxy, that provides the XHR Facade and manages the use of xip_client.html.
  • src/io/xip_client.html: the Client document. Used internally by dojo.io.XhrIframeProxy.
  • src/io/xip_server.html: the Server document. Used by API service providers to enable cross domain XHR requests.
  • tests/io/iframeproxy: test files.
The test files are running here if you want to try it out (note that the API server for these tests is not a powerful box, so it may seem slower than usual to get the responses).

For web page developers

In addition to doing the normal things for dojo.io.bind(), do the following:

  • To enable src/io/xip_client.html, find the commented out script tag under the <!-- Security protection: uncomment the script tag to enable. --> comment and remove the comments from that opening script tag.
  • dojo.require("dojo.io.XhrIframeProxy");
  • Define an iframeProxyUrl parameter to dojo.io.bind(). This will be an URL to the xip_server.html file on the API service server.
  • Only asynchronous XHR requests are supported.
Example code snippet:



dojo.require("dojo.io.*");

dojo.require("dojo.io.XhrIframeProxy");



dojo.io.bind({

iframeProxyUrl: "http://some.domain.com/path/to/xip_server.html",

url: "http:/some.domain.com/path/to/api",

load: function(type, data, evt, kwArgs){

/* do stuff with the result here */

}

});


For API service providers

API service providers will not care about src/io/XhrIframeProxy.js or xip_client.hml. They will be most interested in xip_server.html. For security reasons, xip_server.html will not run "out of the box". The following function needs to be defined:



function isAllowedRequest(request){

/* Decide if you want to allow the request. Return true or false */

}



By default, it is expecting this to be declared in an isAllowed.js file in the same directory as xip_server.html. See the comments in xip_server.html for more information.

In addition to defining the isAllowedRequest() function, the script in xip_server.html needs to be enabled. To enable xip_server.html, find the commented out script tag under the <!-- Security protection: uncomment the script tag to enable. --> comment and remove the comments from that opening script tag.

Reusable Parts for Non-Dojo Implementations

  • src/io/XhrIframeProxy.js: Provides the XHR Facade and manages the use of xip_client.html. It does not have all XHR methods defined, only the ones needed by Dojo's usage of XHR. You can look at the package code to see how it manages the Facade objects and the interaction with xip_client.html.
  • src/io/xip_client.html: Does not depend on any Dojo files, but it makes a call to a Dojo function when it receives a response from the Server document. Just replace the function call to your own function. Used internally by XhrIframeProxy.js.
  • src/io/xip_server.html: Does not depend on any Dojo files. Used for the final XHR request to the API service.