Loading blog entries.. loading

AJAX Cross-Origin HTTP request Friday, April 1, 2011 3:58 PM
Written by Wayne Ye
Font Size: S  M  L 

Background

Cross-Origin Request Sharing - CORS (A.K.A. Cross-Domain AJAX request) is an issue that most web developers might encounter, according to Same-Origin-Policy, browsers restrict client JavaScript in a security sandbox, usually JS cannot directly communicate with a remote server from a different domain. In the past developers created many tricky ways to achieve Cross-Domain resource request, most commonly using ways are:

  1. Use Flash/Silverlight or server side as a "proxy" to communicate with remote.
  2. JSON With Padding (JSONP).
  3. Embeds remote server in an iframe and communicate through fragment or window.name, refer here.

And so on..

Those tricky ways have more or less some issues, for example JSONP might result in security hole if developers simply "eval" it, and #3 above, although it works, both domains should build strict contract between each other, it neither flexible nor elegant IMHO:)

W3C had introduced Cross-Origin Resource Sharing (CORS) as a standard solution to provide a safe, flexible and a recommended standard way to solve this issue. 

Mechanism

From a high level we can simply deem CORS is a contract between client AJAX call from domain A and a page hosted on domain B, a tipical Cross-Origin request/response would be:

DomainA AJAX request headers

Host DomainB.com
User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0) Gecko/20100101 Firefox/4.0
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/json
Accept-Language en-us;
Accept-Encoding gzip, deflate
Keep-Alive 115
Origin http://DomainA.com 

DomainB response headers

Cache-Control private
Content-Type application/json; charset=utf-8
Access-Control-Allow-Origin DomainA.com
Content-Length 87
Proxy-Connection Keep-Alive
Connection Keep-Alive

The blue parts I marked above were the kernal facts, "Origin" request header "indicates where the cross-origin request or preflight request originates from", the "Access-Control-Allow-Origin" response header indicates this page allows remote request from DomainA (if the value is * indicate allows remote requests from any domain).

As I mentioned above, W3 recommended browser to implement a "preflight request" before submiting the actually Cross-Origin HTTP request, in a nutshell it is an HTTP "OPTIONS" request:

OPTIONS DomainB.com/foo.aspx HTTP/1.1

If foo.aspx supports OPTIONS HTTP verb, it might return response like below:

HTTP/1.1 200 OK
Date: Wed, 01 Mar 2011 15:38:19 GMT
Access-Control-Allow-Origin: http://DomainB.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD
Access-Control-Allow-Headers: X-Requested-With
Access-Control-Max-Age: 1728000
Connection: Keep-Alive
Content-Type: application/json

Only if the response contains "Access-Control-Allow-Origin" AND its value is "*" or contain the domain who submitted the CORS request, by satisfying this mandtory condition browser will submit the actual Cross-Domain request, and cache the result in "Preflight-Result-Cache".

Implementation

Let's take a look at server side code samples below (ASP.NET and PHP):

ASP.NET (C#)

protected void Page_Load(object sender, EventArgs e)
{
    String data = String.Empty;
    String returnJSONStr = String.Empty;

    switch (Request.HttpMethod)
    {
        case "GET":
            data = Request.QueryString["Data"];
            returnJSONStr = "{\"Data\":\"Hi remote friend, you tried to passed me data: *" + data + "* through HTTP GET.\"}";
            break;
        case "POST":
            data = Request.Form["Data"];
            returnJSONStr = "{\"Data\":\"Hi remote friend, you tried to POST some mock data: *" + data + "* to me.\"}";
            break;
        case "OPTIONS":
            break;
        default:
            returnBadRequestResponse();
            break;
    }

    if (String.IsNullOrEmpty(data))
        returnBadRequestResponse();
    else
    {
        Response.AddHeader("Access-Control-Allow-Origin", "*");
        Response.ContentType = "application/json";
        Response.Write(returnJSONStr);
    }
}

private void returnBadRequestResponse()
{
    Response.StatusCode = 400;
    Response.ContentType = "application/json";
    Response.Write("{\"Error\":\"Bad HTTP request type!\"}");
}

PHP:

<?php
if(isset($["Data"]))
{
$method=$_SERVER['REQUEST_METHOD'];
$data="";
 if($method=="POST")
{
$data=$_POST["Data"];

$fakeData=new FakeData();
$fakeData->Data="Hi remote friend, you tried to POST some mock data: *"+data+"* to me.";
$fakeData->Time=new DateTime("now");
}
 elseif($method=="GET")
{
$fakeData=new FakeData();
$fakeData->Data="Hi remote friend, you tried to passed me data: *"+data+"* through HTTP GET.";
$fakeData->Time=new DateTime("now");
}
 else
{
RaiseError();
}

header('Content-type: application/json');
$jsonStr= json_encode($fakeData);
 echo($jsonStr);
}
else
{
RaiseError();
}

function RaiseError()
{
http_send_status(405);
header("Status: 405 Method Not Allowed");
}

/*Classes definition*/
class FakeData
{
public $Data;
public $Time;
}
?>

Then client AJax call code:

var cor = null; // cor stands for Cross-Origin request

if (window.XMLHttpRequest) {
    cor = new XMLHttpRequest();
}
//else if (window.XDomainRequest) {
    //cor = new XDomainRequest();
//}
else {
    alert("Your browser does not support Cross-Origin request!");
    return;
}

cor.onreadystatechange = function () {
    if (cor.readyState == 4) {
        document.getElementById('lbl').innerHTML = cor.responseText;
    }
};

var data = 'Some fake data';
if (method == 'POST') {
    cor.open('POST', 'http://WayneYe.com/Demo/CORSDemo/CORSDemoServer.aspx', true);
    cor.withCredential = "true";
    cor.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    cor.send('Data=' + data);
}
else if (method == 'GET') {
    cor.open('GET', 'http://WayneYe.com/Demo/CORSDemo/CORSDemoServer.aspx?Data=' + data, true);
    cor.withCredential = "true";
    cor.send(null);
}

 The JS code works for all mainstream browsers (IE8+, FF 3.6+, Chrome 8+), I didn’t use XDomainObject which introduced in IE8 because XMLHttpRequest is already supported by IE8+, FF and Chrome, Safari, in additional XDR seems have a lot of restrictions (refer: http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx).

Conclusion

Cross-Origin Resource Sharing provides a safe, flexible and a standard way for web developers to achieve Cross-Origin communication, maybe it is time to forget those tricky or inelegant ways like JSONP, Flash/Silverlight/server bridge or even window.name and so on:)

References

 

Permalink: http://wayneye.com/Blog/Ajax-Cross-Origin-HTTP-request 4748 Views  3 Comments
Tag: Category:Programming»Web Development»HTTP/REST

 

 


↓Comments↓

  SteveTuesday, October 16, 2012 1:19 PM
I implemented this asp.net sample and it works when I run it on the same server. However, when the client is on another server, I get the "Access is denied" error on: cor.open('POST', '... I didn't see an impletation here of the pre-flight request, correct? Is there anything else I need to do to in order for it to work cross domain?
 
  WayneTuesday, October 16, 2012 9:38 PM
@Steve Thank you for leaving a comment:) First of all, a "pr-flight" request is nothing but a normal HTTP request with OPTIONS verb, this can be easily done by: xhr.method = "OPTION"; or jQuery: $.ajax("http://foo.bar", { type: "OPTION" }); The server which supports cross-domain request is recommended to respond the OPTIONS request with "Access-Control-Allow-Origin" information; by knowing the support of the server, we can then fire a cross-domain request.
 
  SteveWednesday, October 17, 2012 7:15 AM
I didn't know if it was the preflight that was causing this example not to work on my server. My basic problem is that I'm getting the "Access Denied" error when running this code. Do you know what I need to do to make it work cross domain?
 

Your view point or opinion?
Nickname *
 
Gravatar *
Required (not shown), used only for displaying Gravatar and receiving future notification when new comment(s) posted on this blog.
 
Website/Blog
 
Content *
Current length:     Maximum allowed: charactors