A lot of people (including me a few weeks ago) still think that the same-origin policy of the XMLHttpRequest object makes it impossible to send an AJAX request to a foreign domain, but luckily this isn’t true anymore. Ways like JSONP, Flash bridges or some weird iframe calls (it’s fun – spent a whole night on that aproach ;P) have been found to tackle this issue somehow, but there’s a nice draft from the W3C called Cross-Origin Resource Sharing or short CORS, which solves this problem in a very elegant way and is supported by all major browsers.
The good thing is you don’t need to make any changes on the client side, everything will work like you’re expecting it from a usual XMLHttpRequest, all you’ve got do is to make the server send some “magic” HTTP headers and cross domain requests won’t be a problem no more. And that’s the bad thing as well: Sending the headers shouldn’t be a problem, but it implies that you have server-side access. However, let’s just assume you have access to the server and take a look on a simple example including some PHP and Node.js code snippets.
A simple request
Before we start digging deeper I want to show you the most simple cross domain request (XDR) you could do. As I’ve said nothing specific has to be done on the client side, so it’s just the usual JavaScript code you need for an AJAX request. Since we need different hosts for a cross domain request we assume the following code is part of the page http://www.example.com/dir/page.html
:
var request = new XMLHttpRequest(); request.open('GET', 'http://xdr.example.net/', true); request.send();
Normally the browser won’t allow such a request, since we try to communicate with another host, but with the right header this is no problem anymore. Here is a simple example for a possible HTTP response with CORS:
HTTP/1.1 200 OK Access-Control-Allow-Origin: * Content-Length: 6 Content-Type: text/plain Connection: keep-alive foobar
The interesting part is the Access-Control-Allow-Origin
header, which defines what origins are allowed to send requests. A star (*
) means requests from any origin are allowed, any other value has to be identical to the request origin, otherwise the response will be discarded by the client (aka browser).
But what exactly is the origin of a request? That’s the scheme://host:port
part of the URL the request came from (the :port
part is optional). In our example the request came from http://www.example.com/dir/page.html
, therefore the Access-Control-Allow-Origin
value has to be either *
or http://www.example.com
to be accepted.
Coding the server response
Here are some examples how you could generate the response on the server side. Since this can become as complex as you like I’ll just show some short PHP and Node.js snippets here, but I hope some other guys will give you hints in the comments about how to send the HTTP response in other languages.
PHP: The HTTP response of the example above could be generated by an index.php
with the following code:
<?php header('Access-Control-Allow-Origin: *'); echo 'foobar'; ?>
Node.js: A simple HTTP server which always sends a response similar to the one in the example could look like this:
function pickup(request, response) { response.setHeader('Access-Control-Allow-Origin', '*'); response.end('foobar'); } require('http').createServer(pickup).listen(80);
Preflighted requests
The example above will work for GET
, HEAD
and POST
requests without any user-defined headers, but as soon as you have set some additional headers via request.setRequestHeader()
or want to use another HTTP method, request.send()
will actually result in two requests: a preflight and the real one. The browser will handle the whole preflight stuff for you, so you only have add some extra code on the server side.
A preflight uses two special headers, Access-Control-Request-Method
and Access-Control-Request-Headers
, to tell the server, what is going to be sent in the actual request. The response contains two similar headers, Access-Control-Allow-Methods
and Access-Control-Allow-Headers
, to tell the client what methods and headers are allowed for the intended request.
If the preflight fails due to an error (HTTP status 404, 500 or something like that) or non-matching Access-Control-Request
and Access-Control-Allow
headers, the whole request will fail. However, we’re optimistic, so here is an example of a successful XMLHttpRequest with preflight:
var request = new XMLHttpRequest(); request.open('GET', 'http://xdr.example.net/', true); request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); request.send();
Once again the usual AJAX JavaScript, but this time we’ve send a non-standard header along with our request. Therefore the client will automatically send a preflight before the request and the server has to send an approbate response:
HTTP/1.1 200 OK Date: Wed, 21 Dec 2011 14:40:58 GMT Access-Control-Allow-Origin: * Access-Control-Allow-Methods: POST, GET, PUT, DELETE Access-Control-Allow-Headers: X-Requested-With, Content-Type Content-Length: 0 Content-Type: text/plain
As you can see it is possible to define a list of allowed methods and headers by separating each with a comma. After the preflight has been completed the client will send the actual request and the server has to respond the same way as in the simple request example:
HTTP/1.1 200 OK Access-Control-Allow-Origin: * Content-Length: 6 Content-Type: text/plain Connection: keep-alive foobar
As you can see Access-Control-Allow-Origin
is the only header that has to be included in every response, but it’s no problem to include the other headers as well.
The server code to handle preflights
We only have to add some lines to recognize and respond to preflight requests to the code we already know. Fortunately this is quite simple, since a preflight always uses the HTTP method OPTIONS
:
PHP: The method used by the HTTP request is stored in the global $_SERVER['REQUEST_METHOD']
variable:
<?php if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST, GET, PUT, DELETE'); header('Access-Control-Allow-Headers: X-Requested-With, Content-Type'); } else { header('Access-Control-Allow-Origin: *'); echo 'foobar'; } ?>
Node.js: The method
property of the request
object contains the name of the used HTTP method:
function pickup(request, response) { if (request.method == 'OPTIONS') { response.setHeader('Access-Control-Allow-Origin', '*'); response.setHeader('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE'); response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type'); response.end(); } else { response.setHeader('Access-Control-Allow-Origin', '*'); response.end('foobar'); } } require('http').createServer(pickup).listen(80);
Preflight caching
In order to reduce the network traffic the client won’t send another preflight request as long as it holds a valid preflight-response in its cache. However, this could make debugging somehow difficult, so I recommend to disable caching during development.
This can be achived by sending an Access-Control-Max-Age
header along with the preflight-response, which defines how many seconds the response is allowed to stay in the preflight-result-cache of the client. Setting the header value to zero will disable caching.
Some words on the Internet Explorer
Once again our special blue-e’d friend needs some extra care. The Internet Explorer supports CORS since version 8, but alas someone decided not to implement it the standard way, so we can’t use the XMLHttpRequest object, but have to create a XDomainRequest (MSDN page) object instead. The properties and methods of XDomainRequest are very similar to the normal XMLHttpRequest object – except for two things: It won’t allow us to use other methods like GET or POST or to set any user defined headers, so it’s rather useless when you want to communicate with a RESTful API. However, here’s some JavaScript code that should send a cross domain request in any browser that supports CORS:
var request = XDomainRequest ? new XDomainRequest() : new XMLHttpRequest(); request.open('GET', 'http://xdr.example.net/', true); request.send();
Finally IE10 supports all CORS features within the normal XMLHttpRequest object, but I think we still have to wait a while until version 10 has become the normal use-case…
Further reading
That’s pretty much everything you need to know for a start. You might also want to check out the W3C Cross-Origin Resource Sharing draft for all details and the “CORS enabled” page in the W3C Wiki to see who’s already in the boat. The MDN page on HTTP access control is another good explanation of CORS and my Node.js CORS example server might be of some use, too.
Cross domain is entirely a different subject. But cross sub-domain is relatively easy. All you need to do is to set the document.domain to be same in both the parent page and the iframe page.
Nice idea! Never thought of this – thanks for sharing :)
Great Article! Well explained too!
Thanks for info… Got a clear idea.