As it is widely known, AJAX Requests are only possible if port, protocol and domain of sender and receiver are equal. This means, that the following requests generally won’t work:
- Requesting https://foo.bar/target.php from http://foo.bar/source.php
- Requesting http://sub.foo.bar from http://foo.bar
- Requesting http://foo.bar:5000 from http://foo.bar
Having this cleared out, we will cover ways around this restriction.
CORS
CORS stands for Cross-origin resource sharing and has to be supported on the server side. If we take jQuery, the requesting side will look like this:
$.ajax({ type: 'POST', url: 'http://d1303.de/remote/cors.php', crossDomain: true, data: "my_request_is=foo", dataType: 'json', success: function(responseData, textStatus, jqXHR) { console.log(responseData); }, error: function (responseData, textStatus, errorThrown) { console.warn(responseData, textStatus, errorThrown); alert('CORS failed - ' + textStatus); } });
Be aware of the crossDomain: true. But take care! This will only work as expected, if the server side sends the appropriate response headers for CORS.
header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST, GET, OPTIONS'); header('Access-Control-Max-Age: 1000'); header('Access-Control-Allow-Headers: Content-Type'); echo json_encode(array("your_request_was" => $_POST['my_request_is']));
Here, we accept requests from each and every source for the request methods POST, GET and OPTIONS. For more details regarding the various parameters, see this W3C document. For example, you can accept requesting domains like so:
switch ($_SERVER['HTTP_ORIGIN']) { case 'http://from.com': case 'https://from.com': header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']); header('Access-Control-Allow-Methods: POST, GET, OPTIONS'); header('Access-Control-Max-Age: 1000'); header('Access-Control-Allow-Headers: Content-Type'); echo json_encode(array("your_request_was" => $_POST['my_request_is'])); break; }
For more ways to implement CORS on the server side, see enable-cors.org.
Browser support is excellent (IE >= 8, Firefox >= 3.5, Chrome >= 3).
JSONP
Let’s move on to the next way of making Cross Domain AJAX possible: JSONP. Like CORS, the server has to support JSONP. Basically, the client tells the server the callback function for the response. The server then wraps the response in this callback function. Example? Example!
$.ajax({ type: 'GET', url: 'http://d1303.de/remote/jsonp.php', data: "my_request_is=foo", dataType: 'jsonp', success: function(responseData, textStatus, jqXHR) { console.log("the response is", responseData); }, error: function (responseData, textStatus, errorThrown) { console.warn(responseData, textStatus, errorThrown); alert('JSONP failed - ' + textStatus); } });
Let’s look at the request:
jQuery automatically appends a no cache-parameter with the timestamp and – more interesting – the callback. On the server side, we can now do the following:
$callback = $_GET['callback']; $response = json_encode(array("your_request_was" => $_GET['my_request_is'])); echo $callback . "(" . $response . ")";
This way, the servers response looks like this:
jQuery is now able to invoke the success callback with this information. It is also possible, to specify your own callback with a more readable name, see the $.ajax docs.
As far as I know, there are no browser compatibility issues at all.
iframe
This is more a hack than a „clean“ solution. The theory behind this approach is to place a hidden iframe to your requesting page via javascript and then construct a hidden form, that is posting to the iframe. This way, it’s possible to get around the cross domain issue.
The function:
function postIframe(target_url, method, params) { //Add iframe var iframe = document.createElement("iframe"); document.body.appendChild(iframe); iframe.style.display = "none"; //Give the frame a name var frame_name = "frame_name" + (new Date).getTime(); iframe.contentWindow.name = frame_name; //build the form var form = document.createElement("form"); form.target = frame_name; form.action = target_url; form.method = method; //loop through all parameters for (var key in params) { if (params.hasOwnProperty(key)) { var input = document.createElement("input"); input.type = "hidden"; input.name = key; input.value = params[key]; form.appendChild(input); } } document.body.appendChild(form); form.submit(); }
As we see, a hidden iframe is put on the page. After that, we create a form containing all request parameters as a hidden form field. Finally, the form is programatically sent. We can now trigger the request like this:
var obj = { my_request_is: "foo", bar: "baz" }; postIframe("http://d1303.de/remote/iframe.php", "POST", obj);
However, there is one big downside: There is no easy way to get the server response from our request, this is more like a „fire and forget“ one way-request. If you really want to stick to this approach, here is more info on that.
Other approaches
Even though CORS and JSONP are the most popular methods of doing cross domain AJAX, there are other ways.
- Take a look at the relatively new window.postMessage (part of the HTML5 feature set) – examples here and here.
- Another classic approach that is typically taken for this kind of problem is to place a server side script in the language of your choice (e.g. PHP) on your server and request this script via AJAX – which is not a problem, because requesting side and responding side are on the same domain. Your server side script then forwards the request to the remote location and responds back to your script. See the excellent article Building a simple API proxy server with PHP for more details.
Hi David,
Very interesting article, thanks for sharing.
I think there might be a mistake though or I didn’t understood correctly: on CORS you said „For example, you can restrict the requesting domains like so:“ and I think it should be „For example, you can ACCEPT the requesting domains like so:“.
Best wishes,
Alex
Hey Alex, yep – you are right. Changed it.
Hi,
another way to circumvent the Same-Origin-Policy is using the script-tag, for which (to my knowledge) no restrictions apply whatsoever. One can combine that nicely with JSONP, for instance.
This way, you won’t need to use AJAX, hence you won’t run into any trouble when testing locally without a webserver. Instead of firing an AJAX call, just create and inject a script-element in the page. If you attach event handlers for the onload and onerror-events then you gain the same magnitude of control as if using AJAX.
I use it frequently, and when you remove the script-element after completion you operate in complete-stealh-mode ;)
Thanks for your comment, I think thats a good substitution for the iframe method. However, you only get the onload and onerror event but other than that no server reponse – like the iframe method.
Hi david ,
very interesting article you wrote . keep it up
Thanks. Have any idea without iframe?
Hi David,
Thanks a Lot.. This article helped me a lot to fix an issue in my clients website.. Keep posting.
@Arun G!
Your article was quite helpful!
Your explanations are good and the examples are complete :D
I had some hours dealing with this, thanks a lot!
Rodrigo
Great article David, thanks!
By default, cross-origin requests do not provide credentials (for example cookies). So if you need to send them alongside the request you should provide the withCredentials property. See how: http://zinoui.com/blog/cross-domain-ajax-request
by js load:
// dynamic js
function jsDynaSource(pS){
var mStarget = document.createElement(„script“);
mStarget.type = „text/javascript“;
mStarget.src = pS;
document.getElementsByTagName(„head“)[0].appendChild(mStarget);
}
just make sure my client is under connection with policy,
var mSfoo=’n‘;
jsDynaSource(‚http://BLAHBLAH/foo.js?ut=’+ Math.round( (new Date()).getTime()/1000) );
from ‚http://BLAHBLAH/foo.js‘
var mSfoo=’y‘;
I use the following script for header authentication but its not working with IE and Safari.
What is the best way to do cross origin request with header authentication ?
var url = ‚https://username:password@domain.com‘;
$.ajax({
url: url,
dataType: ‚jsonp‘,
jsonpCallback: „callback“,
success: function(json) {
alert(json);
}
});