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.