The web has changed and with it the way we develop websites. Today, the web is becoming a place where we develop web apps, rather than websites. We use third party API's to create our next mashups. So knowing how to make a cross-site AJAX request or requests that do not comply with the same origin policy is a must. In this article, you will learn 4 cross-site AJAX request methods (plus 4 bonus legacy methods and links to jQuery plugins).
This methods will be handy to overcome Same origin policy as well. Browsers will throw an error if you are making AJAX request to the same domain but with different protocol (to https from http), use different port (http://same-domain.com:81) or subdomain.
This article reviews the following 4 methods and discusses their advantages & disadvantages. Also, summarise cases when they are better used.
Here is the list of methods:
- CORS (Cross-Origin Resource Sharing)
- JSONP
- window.postMessage
- Setting up a local proxy
- + 4 bonus legacy methods (document.domain, window.name, iframe, flash)
- + list of JavaScript libraries and jQuery plugins for making XSS requests.
Before we dive into the method details, let's cover most common cases:
- Firstly, if you are trying to read data that is available as RSS feed, you are better off with universal RSS to JSON converter powered by Google.
- Secondly, if you are accessing data from some popular website API, it's more likely they support JSONP as well. See their documentation.
JSONP is a cross browser method that does not rely on any browser hacks. It is supported by all browsers and many javascript libraries provide methods that make JSONP request seamless.
1. CORS (Cross-Origin Resource Sharing)
CORS is a W3C recommendation and supported by all major browsers. It makes use of HTTP headers to help browser decide if a cross-domain AJAX request is secure. Basically, when you make a CORS request, browser adds Origin
header with the current domain value. For example:
Origin: http://jquery-howto.blogspot.com
The server, where the script makes its' CORS request, checks if this domain is allowed and sends response with Access-Control-Allow-Origin
response header. Upon receiving, browser checks if the header is present and has the current domain value. If domains match, browser carries on with AJAX request, if not throws an error.
Access-Control-Allow-Origin: http://jquery-howto.blogspot.com
To make a CORS request you simply use XMLHttpRequest
in Firefox 3.5+, Safari 4+ & Chrome and XDomainRequest
object in IE8+. When using XMLHttpRequest
object, if the browser sees that you are trying to make a cross-domain request it will seamlessly trigger CORS behaviour.
Here is a javascript function that helps you create a cross browser CORS object.
function createCORSRequest(method, url){
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr){
// XHR has 'withCredentials' property only if it supports CORS
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined"){ // if IE use XDR
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
}
Function takes 2 arguments: method
- request method ("GET", "POST", etc.) and url
- URL where to send the request. Here is how to make a "GET" request to Google.
var request = createCORSRequest( "get", "http://www.google.com" );
if ( request ){
// Define a callback function
request.onload = function(){};
// Send request
request.send();
}
Because CORS specification relies on HTTP headers and all the heavy lifting is done by browser and server, our code does not need to change. In other words, you can make cross-domain AJAX requests like any other in jQuery.
$.get('http://cors-support.com/api', function( data ) {
alert( 'Successful cross-domain AJAX request.' );
});
Requirements & Notes
In order to be able to make a CORS request, you need CORS supporting browser and a server. Check if your browser and server support it.
Also note, that if AJAX request adds any custom HTTP headers or use any method other than GET, POST or HEAD as a request type; browser will make a "preflight" request to check if the server responds with correct headers before sending the actual request. This adds an overhead to your AJAX requests.
Advantages & Disadvantages
CORS is a W3C specification and supported by all major browsers. It is how the cross domain AJAX querying will work in the future. So using CORS would be a future safe bet.
However, it requires CORS supporting server and browser. If you have administrative privileges at the server you can add CORS support as explained here. If you do not have any control over the server, then you are out of luck. You will need to choose some other method.
2. JSONP (JSON Padding)
Because of the same origin policy, we can not make cross domain AJAX requests, but we can have <script>
tags that load javascript files from other domains. JSONP uses this exception in order to make cross domain requests by dynamically creating a <script>
tag with necessary URL.
Here is how it works. Server wraps data, usually in JSON format, in a function call. Upon loading the script, browser calls that function and passes loaded data. This implies the third party server knows the local javascript function name, but for obvious reasons that is not practical. The workaround is to pass the function name as a parameter to the request URL.
Let's seen an example. Facebook's Open Graph supports JSONP calls. Here is a normal JSON response:
// https://graph.facebook.com/10150232496792613
{
"id": "10150232496792613",
"url": "http://jquery-howto.blogspot.com/",
"type": "website",
"title": "jQuery Howto",
...
}
Open Graph documentation says that it takes callback
parameter to turn JSON into JSONP response. So let's add callback parameter and turn it into JSONP request.
// https://graph.facebook.com/10150232496792613?callback=myFunc
/**/ myFunc({
"id": "10150232496792613",
"url": "http://jquery-howto.blogspot.com/",
"type": "website",
"title": "jQuery Howto",
...
});
Noticed how the previous JSON data is now wrapped into myFunc();
? So if we had defined myFunc()
function previously, it would have been called with the Open Graph data.
function myFunc( data ){
console.log( data.title ); // Logs "jQuery Howto"
}
jQuery has built in support for JSONP requests in it's AJAX methods. To trigger a JSONP request you need to add callback_name=?
string at the end of the URL. Here is a previous example using jQuery.
$.getJSON( "https://graph.facebook.com/10150232496792613?callback=?", function( data ){
console.log( data.title ); // Logs "jQuery Howto"
});
// OR using $.ajax()
$.ajax({
type: "GET",
url: "https://graph.facebook.com/10150232496792613",
dataType: "jsonp",
success: function(data){
console.log(data);
}
});
Requirements & Notes
The JSONP has become de facto method to overcome same origin policy restrictions and it is supported by major data providers (Facebook, Twitter, Google, Yahoo, etc.). In order to be able to use JSONP, the third party server must support it. In other words wrapping JSON data into a function call.
Please remember, that the returned data is plain javascript file. This means that you are running arbitrary javascript code within the scope of your domain with access to all of the user cookies and data in the browser. This introduces a huge security concern. That is why you absolutely must trust the server you are fetching data using JSONP method.
Advantages & Disadvantages
Advantages:
- Supported by almost all browsers.
- Supported by major data providers and easy to implement on your own server.
- Well supported by javascript libraries, including jQuery (see examples above).
- No request overhead.
Disadvantages:
- Run as arbitrary javascript code. Using JSONP implies that you absolutely trust data provider.
- Requires server support (even though easy to implement).
3. window.postMessage
window.postMessage method is part of HTML5 introductions. It allows communication between window frames without being subject to same origin policy. Using postMessage()
one can trigger a message
event with attached data on another window, even if the window has different domain, port or a protocol. The frame where the event is triggered must add an event listener in order to be able to respond.
Let's see an example. Assume, we are on http://example.com (1) website and would like to make a request to http://example2.net (2) domain. We first must obtain a reference to (2) window. This can be either iframe.contentWindow
, window.open
, or window.frames[]
. For our case it's best to create a hidden iframe element and send messages to it. Here is how it looks.
// Create an iframe element
$('<iframe />', { id: 'myFrame', src: 'http://example2.net' }).appendTo('body');
// Get reference to the iframe element
var iframe = $('#myFrame').get(0);
// Send message with {some: "data"} data
iframe.postMessage( {some: 'data'}, 'http://example2.net');
The first argument is the data to be sent, the second is the URL of the current document. If this value is different from document.domain
at the time when message is sent, browser will do nothing and silently ignore it. This is done for security reasons, since the frame's URL may change.
The page on server (2) must have an html content with a "message" event listener function. Let's use jQuery to do just that:
<html>
<body>
<script>
$(window).on("message", function( event ){
// We must check event.origin, because anyone can
// trigger event. Unless, you are public data provider.
if (event.origin !== "http://example.com") return;
// Now let's send the (1) window data
event.source.postMessage({name: "Someone", avatar: "url.jpg"}, event.origin);
});
</script>
</body>
</html>
In order to receive the data sent from server (2), we must add another event listener on page (1). Let's update our previous code.
var iframe = $('#myFrame').get(0);
iframe.postMessage( {some: 'data'}, 'http://example.com');
$(window).on("message", function( event ){
if (event.origin !== "http://example2.net") return;
console.log( event.data ); // Logs {name: "Someone", avatar: "url.jpg"}
});
Requirements & Notes
This method is relatively new and it is not used by that many services yet. All latest major browsers support it. However, IE8 & IE9 support only messaging between <frame>
and <iframe>
's. IE10 supports messaging between windows, but only through MessageChannel
's.
This method is great for intranet projects where you control the environment (know exactly installed browsers, etc.). Also, it has high performance compared to any other method of communication.
Advantages & Disadvantages
Advantages:
- No need to install or update on the server.
- Recommended way of communication between the browser windows.
- Secure (when used correctly).
Disadvantages:
- Not supported by all major browsers (mainly IE problems).
4. Setup local proxy
This method overcomes same origin policy by proxying content on another domain through itself. Thus making cross-domain issue irrelevant. To use this method you will either a) setup your server as a reverse proxy to fetch content from another server or b) write a script that would do that.
This cross domain querying solution works because you actually loading content from your own domain. You request the URL and the proxy script on your server loads the content and passes it over to you.
Here is a sample PHP proxy to get RSS feed from FeedBurner.
<?php
// Set your return content type
header('Content-type: application/xml');
// Website url to open
$url = 'http://feeds.feedburner.com/jQueryHowto';
// Get that website's content
$handle = fopen($url, "r");
// If there is something, read and return
if ($handle) {
while (!feof($handle)) {
$buffer = fgets($handle, 4096);
echo $buffer;
}
fclose($handle);
}
?>
Named the file proxy.php and make AJAX request to this URL. Here is a jQuery code example:
$("#rssFeeds").load("path/to/proxy.php", function(){
// Some callback functions
});
And this is how you can overcame the jQuery cross site scripting problem.
Requirements & Notes
While setting up a proxy script, do not forget to cache the fetched data. This will reduce the loading times and save some processing on your hosting server.
This method must be your last resort, when previous 3 methods do not meet your requirements.
Advantages & Disadvantages
Advantages:
- Does not rely on browser support.
- Does not rely on data provider's support.
- Can be used to solve any cross-domain request problem.
Disadvantages:
- Requires setting up a proxy server. The bad news is that not all web hosting companies allow
fopen()
to other domains, but enable it on request. My web server was very strict on security but the script above worked well on it.
5. Legacy methods
Before new methods of cross domain request and messaging were introduced, developers relied on hacks and workarounds. Most popular ones were:
- iframe - Include a hidden
iframe
and change it's URL fragment to exchange data. Latest browsers have added security restrictions that throw an error for accessing iframe location properties from different domains. - window.name - Changing
window.name
property for exchanging data. - flash - Flash can communicate with javascript and has different security rules. So developers included flash object on their pages to make cross-domain requests.
- document.domain - This method is used for communication between two subdomains on the same domain by changing the
document.domain
property to the root domain value.
6. Links & Resources for making cross-domain requests
There are many libraries built around cross-domain AJAX problem. Here is a list of notable libraries and plugins.
- easyXDM - Makes use of all possible cross-domain AJAX request methods and workarounds. If a browser does not support postMessage, CORS, etc. it will fall back to hacks (flash, etc.).
- jQuery postMessage plugin - a wrapper around postMessage.
- jQuery.ajax() - read jQuery's AJAX documentation to learn more about it's settings that help to configure remote requests.
In this post I tried to collect all the information available on cross-domain AJAX requests. If I missed anything, please let me know in the comments. Like/share for future reference.