CORS Data Fetching
This article was written by Todd Anglin, originally published in the Kendo UI Blogs on October 3, 2011 and consequently updated in July 2012.
The original spelling, grammar, and structure of the item is preserved here for consistency.
Using CORS with All (Modern) Browsers
Cross-Origin Resource Sharing (CORS) is a (slowly) emerging technology for the web that finally gives async web operations a way to directly grab resources from different domains. In fact, we've talked about CORS a couple of times on the Kendo UI blogs here and here.
By default, the "same origin" security sandbox built-in to all browsers does not allow XHR (Ajax) calls across different domains. You can try, but you'll get an error that looks something like this:
XMLHttpRequest cannot load [URL]. Origin [YOUR WEBSITE] is not allowed by Access-Control-Allow-Origin.
This message basically says that you can't use Ajax to load resources from a different domain. But what's that last part of the error?
Access-Control-Allow-Origin
CORS works by adding a special header to responses from a server to the client. If a response contains the Access-Control-Allow-Origin
header, and if the browser supports CORS, then there is a chance you can load the resource directly with Ajax&dmash;no need for a proxy or JSONP hacks.
Why just a chance?
The header is capable of specifying which remote sites are allowed to load the cross-origin resources. For example, consider the following CORS header in a hypothetical response: Access-Control-Allow-Origin [http://htmlui.com](http://htmlui.com/)
.
With this configuration, only scripts that originate from http://htmlui.com are allowed to load resources from telerik.com
. Any other domain trying to use Ajax to load resources from telerik.com
will be given the standard security error message. In this way, site owners can limit which domains are allowed to load their resources with CORS.
Alternatively, site owners can grant wide-open access with the always ready to party asterisk: __Access-Control-Allow-Origin: *__
.
Now, any site that wants to load a resource by directly using Ajax can do so without getting the browser security error. The technique is helpful for modern applications which often load data by using JavaScript and a greater number of modern web APIs are considering support for CORS. To see CORS in action, see the GeoNames.org, Last.fm, and Google APIs.
Server-Side Setting
Clearly, CORS is powerful. It opens up the tightly controlled browser security sandbox that is essential to the trusted fabric of the web. As you might expect, then, it's a decision that must be made by site owners (you can't use CORS with sites that don't allow it), and it's controlled by web server configuration.
Anyone can CORS-enable their site by simply having the web server add the necessary Access-Control-Allow-Origin
header. In fact, there is an entire website dedicated to showing you how to add this header for a host of different web servers. In Apache, it's as simple as adding this line to the .htaccess file
: Header set Access-Control-Allow-Origin *
Dealing with Browsers
It's never simple with browsers, especially when you want to ensure broad compatibility.
As confirmed by the useful CanIUse.com
, support for CORS is a bit of a mixed-bag. CORS is 100% ready to roll in:
- WebKit browsers (Chrome, Safari, iOS, Android)
- Gecko browsers (Firefox)
- Trident browsers (Internet Explorer 8+)
- Presto browsers (specifically, Opera 12+)
That's not bad. Until recently Opera was missing from that list, but with the release of Opera 12 in mid 2012, Opera also now supports CORS.
We'll talk about handling old Opera versions more in a minute, but let's first address IE.
Internet Explorer and XDomainRequest
Internet Explorer, pre IE10, approaches cross-origin resource sharing a bit differently. Rather than go the route of WebKit and Gecko, IE 8 and 9 do not reuse the standard Ajax XMLHttpRequest
object for CORS requests. Instead, they introduce a brand new object for cross-origin resource sharing called XDomainRequest
.
This means your Ajax code for cross-domain calls looks 100% identical to same-domain
calls in Chrome and Firefox, but it will have to fork in Internet Explorer to use the new XDR object with CORS requests. A pain, but a solvable problem. There are some other limits with XDR, but we'll leave that to you to research.
With this small snippet, we can write code that works with CORS XHR, XDR, and when that doesn't work, some kind of fallback approach:
//Detect browser support for CORS
if ('withCredentials' in new XMLHttpRequest()) {
/* supports cross-domain requests */
document.write("CORS supported (XHR)");
}
else{
if(typeof XDomainRequest !== "undefined"){
//Use IE-specific "CORS" code with XDR
document.write("CORS supported (XDR)");
}else{
//Time to retreat with a fallback or polyfill
document.write("No CORS Support!");
}
}
You can try this code snippet live in different browsers to confirm it works.
The XMLHttpRequest2
object, which includes required support for CORS, can be used to feature-detect browser support for CORS. If the browser's XHR
object has the XHR2 "withCredentials"
property, you're in business. If not, step 2.
In step 2, we take one more attempt to use CORS by looking for IE's proprietary XDomainRequest
object (which works with the same Access-Control-Allow-Origin
headers, by the way). If the XDR object exists, we're in IE and we can write the necessary code to do XDR CORS.
Finally, if all else fails (such as in older versions of Opera or IE6/7), we have to either provide an alternate experience or find a hack for CORS.
Handling Opera (or Non-CORS Browsers)
Assuming we reach the point where XHR2/CORS
and XDR
are not available, what's the next step? Abandon CORS? As it turns out, you have a few choices. You can:
Use a CORS polyfill There are a couple of rather "hacky" polyfills for CORS that rely on either A) Flash under the covers, or B) some voodoo HTML5. Either way, they work around the browser limits and try to give you some semblance of CORS support.
Use JSONP Of course, if you can use JSONP, the argument can be made that you should just use JSONP instead of CORS for all browsers since it is still more universally supported. But let's say you're trying to "evolve" past JSONP except when you absolutely need it. In that case, you can fallback to XHR and JSONP callbacks with Opera and older browsers.
Display an error message The most draconian of your choices, but given most browsers do support CORS, you could simply elect to tell the small audience of users to update their browser. Just depends how important Opera and IE 6/7 traffic is to your site.
The choice is yours, but clearly, you have some choice that should still make CORS appealing.
Putting It All Together
In the Kendo UI Feed Reader demo, we use YQL to feed RSS XML directly to the browser. YQL supports CORS, so we elected to send XML to the browser instead of JSONP to highlight Kendo UI's data source support for XML.
Version 1 of this demo did not support non-CORS browsers. To add support for these browsers, we modified the code to use XDR with IE and YQL JSONP with Opera and all non-CORS browsers.
Fixing IE with jQuery $.ajax transports
Making CORS code work with IE 8/9 is actually very easy thanks to some code written by Derek Kastner. With jQuery 1.5+, you can create custom transport implementations to control the inner-workings of jQuery $.ajax. Derek has done just that with XDR.
His iecors
project simply creates a jQuery transport that uses XDR in jQuery $.ajax requests when it's needed. All you have to do is add the small JS file to your page and everything starts working (no changes to your code). The Kendo UI Data Source, which uses jQuery $.ajax under the covers, will now use XDR in IE.
So, to make the Feed Reader demo CORS work in IE 8/9, a small snippet is added to the bottom of the page, after jQuery but before any Kendo script references:
<!--[if lt IE 10]>
<!--iecors provides a jQuery ajax custom transport for IE8/9 XDR-->
<script src="scripts/jquery.iecors.js"></script>
<![Kendo UI for jQuery endif]-->
Fixing Opera with More Code and JSONP
Unfortunately, fixing older versions of Opera is not as easy. And, ultimately, there is no clean way to do CORS in Opera. Your choices are to either display a "browser not supported" message for Opera (pre v12) users OR bite the bullet and "fallback" to YQL JSONP when CORS is just not going to work.
We elected to use the later approach in the Feed Reader demo since it also helps extend support to other older browsers.
//**HACK for OPERA (and non-XHR2/XDR browsers)
//For lack of a reasonable Opera workaround to support CORS, fallback to use
//YQL support for JSONP when dealing with a browser than doesn't support
//CORS XHR or XDR
if (!('withCredentials' in new XMLHttpRequest()) && !(typeof XDomainRequest !== "undefined")){
_feedItemDS = new kendo.data.DataSource({
transport:{
read:{
url: "#",
dataType: "jsonp"
},
dialect: function (options) {
var result = ["callback=?","format=json"],
data = options || {};
return result.join("&");
}
},
schema:{
type:"json",
data:"query.results.rss.channel.item",
}
});
//**END OPERA/Non-CORS HACK
}
Now, Opera (any other non-CORS browser) will use an alternate configuration of the Kendo UI data source pointed at a JSONP endpoint and expecting a JSON response. Not pretty code, but it's functional. Sometimes, that's what it takes to build software that runs in every major browser and platform.
Bottom Line on CORS
Hopefully this post helps highlight the value of CORS and how it can be used with most modern browsers. As more app code moves to the client, the need for CORS will only grow. Start playing with it today and help push web standards to the next level.