The Problem
Many times I’ve been frustrated with the same-origin policy for AJAX requests. I have legitimate content on another domain that I would like to retrieve and display on another page using AJAX.
As an example, department.unl.edu would like to display a list of upcoming events from events.unl.edu/department/.
Historically you would have to serve the content out as JSON, or use a proxy to convert the content to json, and reference the resource using a script tag which gets around the same-origin policy – and for years, this has worked well.
Unfortunately this requires you to either send content out in multiple formats (html + json for these requests), or use html and send all requests for cross-site resources through a proxy script, which adds a translation delay.
Serving the content out in two formats requires more work to maintain, and if the content output is cached, requires more resources to generate. If you use a proxy to translate the content, you have an open proxy unless you lock down what resources the proxy script can retrieve and translate.
CORS is the Answer
The answer is in the Cross-Origin Resource Sharing specification, which is supported in Firefox 3.5+, Safari 4+ and albeit using agent specific methods, supported in Internet Explorer 8+.
The specification allows resources on other domains to be retrieved through AJAX methods, if the resource announces support as a Cross-Origin Resource. This is done through special headers which identify to the user-agent which alternate origin domains can request the resource and what methods are supported.
Demo
For the demo, the html and js is served off of this domain, and the CORS resource is located on ucommbieber.unl.edu.
This page demonstrates both GET and POST methods, with custom callbacks, using an api just like the jQuery get and post methods.
The HTML:
<html>
<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="cors.js"></script>
<script type="text/javascript">
function testGet()
{
getCORS('http://ucommbieber.unl.edu/CORS/cors.php', null, function(data){alert(data);});
}
function testPost(form)
{
postCORS('http://ucommbieber.unl.edu/CORS/cors.php', {name: form.name.value}, function(data){alert(data);});
}
</script>
<body>
<h1>CORS Examples</h1>
<p>Test GET
This page retrieves content from another server, using CORS<br />
<a href="#" onclick="testGet(); return false;">Get content from another server</a>
</p>
<p>Test POST:
<form action="#" onsubmit="testPost(this); return false">
Name: <input type="text" name="name" />
<input type="submit" />
</form>
</p>
<a href="http://saltybeagle.com/2009/09/cross-origin-resource-sharing-demo/">More info</a>
</body>
</html>
The javascript:
/**
* This is for Cross-site Origin Resource Sharing (CORS) requests.
*
* Additionally the script will fail-over to a proxy if you have one set up.
*
* @param string url the url to retrieve
* @param mixed data data to send along with the get request [optional]
* @param function callback function to call on successful result [optional]
* @param string type the type of data to be returned [optional]
*/
function getCORS(url, data, callback, type) {
try {
// Try using jQuery to get data
jQuery.get(url, data, callback, type);
} catch(e) {
// jQuery get() failed, try IE8 CORS, or use the proxy
if (jQuery.browser.msie && window.XDomainRequest) {
// Use Microsoft XDR
var xdr = new XDomainRequest();
xdr.open("get", url);
xdr.onload = function() {
callback(this.responseText, 'success');
};
xdr.send();
} else {
try {
// Ancient browser, use our proxy
var mycallback = function() {
var textstatus = 'error';
var data = 'error';
if ((this.readyState == 4)
&& (this.status == '200')) {
textstatus = 'success';
data = this.responseText;
}
callback(data, textstatus);
};
// proxy_xmlhttp is a separate script you'll have to set up
request = new proxy_xmlhttp();
request.open('GET', url, true);
request.onreadystatechange = mycallback;
request.send();
} catch(e) {
// Could not fetch using the proxy
}
}
}
}
/**
* This method is for Cross-site Origin Resource Sharing (CORS) POSTs
*
* @param string url the url to post to
* @param mixed data additional data to send [optional]
* @param function callback a function to call on success [optional]
* @param string type the type of data to be returned [optional]
*/
function postCORS(url, data, callback, type)
{
try {
// Try using jQuery to POST
jQuery.post(url, data, callback, type);
} catch(e) {
// jQuery POST failed
var params = '';
for (key in data) {
params = params+'&'+key+'='+data[key];
}
// Try XDR, or use the proxy
if (jQuery.browser.msie && window.XDomainRequest) {
// Use XDR
var xdr = new XDomainRequest();
xdr.open("post", url);
xdr.send(params);
xdr.onload = function() {
callback(xdr.responseText, 'success');
};
} else {
try {
// Use the proxy to post the data.
request = new proxy_xmlhttp();
request.open('POST', url, true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
request.send(params);
} catch(e) {
// could not post using the proxy
}
}
}
}
The PHP code responding from the server looks like this:
<?php
// Specify domains from which requests are allowed
header('Access-Control-Allow-Origin: *');
// Specify which request methods are allowed
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
// Additional headers which may be sent along with the CORS request
// The X-Requested-With header allows jQuery requests to go through
header('Access-Control-Allow-Headers: X-Requested-With');
// Set the age to 1 day to improve speed/caching.
header('Access-Control-Max-Age: 86400');
// Exit early so the page isn't fully loaded for options requests
if (strtolower($_SERVER['REQUEST_METHOD']) == 'options') {
exit();
}
// If raw post data, this could be from IE8 XDomainRequest
// Only use this if you want to populate $_POST in all instances
if (isset($HTTP_RAW_POST_DATA)) {
$data = explode('&', $HTTP_RAW_POST_DATA);
foreach ($data as $val) {
if (!empty($val)) {
list($key, $value) = explode('=', $val);
$_POST[$key] = urldecode($value);
}
}
}
echo 'Hello CORS, this is '
. $_SERVER['SERVER_NAME'] . PHP_EOL
.'You sent a '.$_SERVER['REQUEST_METHOD'] . ' request.' . PHP_EOL;
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
echo 'Your name is ' . htmlentities($_POST['name']);
}
Note the special headers that have to be returned to allow the request to complete:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: X-Requested-With
The X-Requested-With allows us to use jQuery’s internal GET method which adds in the additional header.
Support for proxy fallback for older browsers is left up to you to code. The proxy requires you to set up a script which translates a page into JSON and handles the response. There are many examples of how to do this.
I added this to plugins.jquery.com
Excellent article, helped me a lot, Thanks.
Awesome. Thank you for posting this. I wanted to detect a browser’s cross-origin capabilities, and your demo has everything I needed to know.
Great article, I found it useful when implementing the client side XDR stuff.
I know your demo is PHP, but I’d like to share some experience with implementing the server side stuff in ASP.NET. I have put together a little article about problems you may come across when supporting XDR with ASP.NET at the page below;
http://www.actionmonitor.co.uk/NewsItem.aspx?id=5
Can I allow my page to access a service I do not control?
For instance, I want my page to access api.flickr.com? Even though I have no control on api.flickr.com.
No, youll have to act as a proxy if you want to do that.
Hi, this article is great and is hard to get cross-domain ajax plugins without flash or iframes (or back-end proxies), so the XDomainRequest is the convenient solution for IE8+.
One thing I want to add: we are using jQuery so you could use the params = $.param( data ) instead the for in, because jQuery 1.4+ has full object support in this method, and complete objects will be sent to instead an [object Object] string
Other thing I saw is that IE8 & 9 actually resolves the XHR cross domain but with a ugly confirm, so I moved the – if ( $.browser.msie && window.XDomainRequest ) – up, so first will try xdomain and if doesn’t support will fall in jQuery or proxy, this might could be resolved too with an if in “withCredentials” but I don’t tried that yet.
I hope this helps, thanks again for the tutorial!
I know this is a bit of an older topic, but to get it to work in IE9 you need to have:
xdr.onprogress = function(){};
I know it seems stupid to have an empty function, but there is something strange with IE9.
http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/30ef3add-767c-4436-b8a9-f1ca19b4812e/
Hi! Thank you for theat script! Its awesome! But i have a problem using it :/
On my local server I load a Flickr stream and use drawImage() to apply that img on canvas. Now I want to get the pixel data with getImageData(), which throws an Security Error. So I have to use CORS.
getCORS(img.src, null, function() {var image_data = ctx.getImageData(0, 0, 100, 100);});
But when I do so it says:
XMLHttpRequest cannot load.
Origin http://colourcity.site is not allowed by Access-Control-Allow-Origin.
Can anyone help me out?