AJAX 跨源 HTTP 請求

oschina發表於2014-04-30

 背景

  跨源HTTP請求(也稱跨域AJAX請求)是大多數Web開發人員可能遇到的一個問題,根據同源策略,瀏覽器將限制客戶端的JavaScript在一個安全沙箱內,通常JS不能直接同一臺不同的域的遠端伺服器通訊。在過去,開發者們創造了許多解決方法以實現跨域資源請求,常用的方法如下:

  1. 使用Flash/Silverlight或伺服器端“代理”來與遠端通訊 

  2. 帶填充JSON (JSONP).

  3. 在iframe中嵌入遠端伺服器並通過fragment或window.name通訊,參考這裡。

  如此等等..

  這些解決方法或多或少都有問題,比如使用JSONP時若只是簡單的“eval”將導致安全漏洞,#3雖然能用,但兩個域間必須依據嚴格的協議,恕我直言它既不靈活也不優雅

  W3C已經引入了跨域資源共享 (CORS)作為能夠解決該問題並提供安全、靈活以及推薦標準的解決方案。

 機制

  從較高的層次來看我們可以簡單認為CORS 是介於 域A客戶端 的AJAX呼叫 和一個託管在域B的頁面 之間的契約, 一個典型的跨源 請求或者響應將會是這樣:

  域 A 的 AJAX 請求頭

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

  域 B 的 響應頭

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

  我上面標記的藍色部分是關鍵實現, "Origin" 請求頭表示  跨源請求 或者 預檢請求 源於哪裡,  "Access-Control-Allow-Origin" 請求頭 表示這個頁面允許來自域A 的請求(其值為 * 表示允許任何域的遠端請求)。

  像我上面提到的,W3 建議瀏覽器在提交實際跨源HTTP 請求前,實現“預檢請求”, 簡而言之,就是一個HTTP OPTIONS 請求:

OPTIONS DomainB.com/foo.aspx HTTP/1.1

  如果 foo.aspx 支援 OPTIONS HTTP 指令, 它可能會像下面這樣返回響應:

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

  只有滿足在響應中包含 "Access-Control-Allow-Origin" , 並且其值為 "*" 或者包含提交CORS請求的域,這些強制條件的瀏覽器才能提交正式的跨域請求, 並在 預檢結果快取” 中快取請求結果 。

 實現

  讓我們看一下伺服器端程式碼,例子如下(ASP.NET和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

 

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; 
 }

  客戶端AJAXY發起請求程式碼:

 

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);
 }

  JS程式碼適用於所有主流瀏覽器(IE8+, FF 3.6+, Chrome 8+),我沒有用IE8所採用的XDomainObject,因為 IE8+, FF and Chrome, Safari等瀏覽器支援XMLHTTP請求。而且XDomainObject(XDR)似乎有很多限制(參考: http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx

 結論

  跨源資源共享為網站開發人員實現跨源通訊提供了一個安全,靈活,標準的方案。也許是時候擯棄像JSONP,Flash,Silverlight,server bridge以及window.name等等並不是很實用的方法。

 參考資料

  原文地址:http://www.codeproject.com/Articles/185506/AJAX-Cross-Origin-HTTP-request

相關文章