跨源資源共享(CORS)

一、CORS簡介

在前一篇博客中,我介紹了利用JSONP實現跨域請求,但是在上篇文章中也指出了用JSONP實現跨域存在的一些缺點, 因此W3C 提出了另外一個跨域的方法:CORS,全稱是”跨域資源共享”(Cross-origin resource sharing)。與JSONP相比,CORS更爲爲先進、方便和可靠:

  • JSONP只能實現GET請求,而CORS支持所有類型的HTTP請求。
  • 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得數據,比起JSONP有更好的錯誤處理。
  • JSONP主要被老的瀏覽器支持,它們往往不支持CORS,而絕大多數現代瀏覽器都已經支持了CORS。

    關於瀏覽器對CORS的支持情況,讀者可以參照網址http://caniuse.com/#feat=cors,該網站中對CORS的支持情況進行了總結(綠色代表支持):

    這裏寫圖片描述

CORS 背後的基本思想,就是使用自定義的 HTTP 頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功,還是應該失敗。比如一個簡單的使用 GET 或 POST 發送的請求,它沒有自定義的頭部,而主體內容是 text/plain。在發送該請求時,需要給它附加一個額外的 Origin 頭部,其中包含請求頁面的源信息(協議、域名和端口),以便服務器根據這個頭部信息來決定是否給予響應。例如我們的

 Origin:http://localhost

在服務器端如果認爲這個請求可以接受,就在 Access-Control-Allow-Origin 頭部中回發相同的源信息(如果是公共資源,可以回發”*”)。例如:

Access-Control-Allow-Origin: http://localhost

二、IE對CORS的實現

微軟在 IE8 中引入了 XDR(XDomainRequest)類型。這個對象與 XHR 類似,但能實現安全可靠的跨域通信。 XDR 對象的安全機制部分實現了 W3C 的 CORS 規範。

XDR 對象的使用方法與 XHR 對象非常相似。也是創建一個 XDomainRequest 的實例,調用 open()方法,再調用 send()方法。但與 XHR 對象的 open()方法不同, XDR 對象的 open()方法只接收兩個參數:請求的類型和 URL。

所有 XDR 請求都是異步執行的,不能用它來創建同步請求。請求返回之後,會觸發 load 事件,響應的數據也會保存在 responseText 屬性中,如下是一個簡單的示例:

var xdr = new XDomainRequest();

xdr.onload = function(){
    console.log(xdr.responseText);
    };

xdr.open("get", "http://182.254.146.112/demo.php");
xdr.send(null);

demo.php文件是我放在遠程服務器上的文件,文件內容如下:

<?php

header("Access-Control-Allow-Origin: http://localhost");  

$info=array("name"=>"sean","age"=>"25","city"=>"Nanjing");

echo json_encode($info);
?>

注意:特別需要注意的是,服務端的文件一定要在header中設置Access-Control-Allow-Origin

在IE<11版本瀏覽器中測試,會得到demo.php中的返回值,如下:

{"name":"sean","age":"25","city":"Nanjing"}

這裏需要注意的是:在IE11中測試時會報錯“XDomainRequest 未定義”的錯誤,因此IE11已經不採用該方法了,而是採用下面的標準方法。

三、其他瀏覽器對CORS的實現

Firefox 3.5+、 Safari 4+、 Chrome、 iOS 版 Safari 和 Android 平臺中的 WebKit 都通過 XMLHttpRequest對象實現了對 CORS 的原生支持。在嘗試打開不同來源的資源時,無需額外編寫代碼就可以觸發這個行爲。 要請求位於另一個域中的資源,使用標準的 XHR 對象並在 open()方法中傳入絕對 URL 即可:

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                    console.log(xhr.responseText);
            } else {
                    console.log("Request was unsuccessful: " + xhr.status);
                }
            }
        };

xhr.open("get", "http://182.254.146.112/demo.php", true);
xhr.send(null);

在chrome和Firefox瀏覽器中測試都能得到結果:

{"name":"sean","age":"25","city":"Nanjing"}

IE11也選擇了XMLHttpRequest對象實現 CORS ,而拋棄了XDomainRequest,在IE11瀏覽器中測試,也能得到上述結果。

四、跨瀏覽器的CORS

即使瀏覽器對 CORS 的支持程度並不都一樣,但所有瀏覽器都支持簡單的(非 Preflight 和不帶憑據的)請求,因此有必要實現一個跨瀏覽器的方案。檢測 XHR 是否支持 CORS 的最簡單方式,就是檢查是否存在 withCredentials 屬性。再結合檢測 XDomainRequest 對象是否存在,就可以兼顧所有瀏覽器了。

withCredentials 屬性可以指定某個請求是否應該發送憑據,如果withCredentials 屬性設置爲 true,即請求應該發送憑據。如果服務器接受帶憑據的請求,會用下面的 HTTP 頭部來響應。

Access-Control-Allow-Credentials: true;

跨瀏覽器的CORS簡單代碼如下:

function createCORSRequest(method, url){

    var xhr = new XMLHttpRequest();

    if ("withCredentials" in xhr){
            xhr.open(method, url, true);
        } else if (typeof XDomainRequest != "undefined"){
            vxhr = new XDomainRequest();
            xhr.open(method, url);
                } else {
                        xhr = null;
                }
    return xhr;
}

var request = createCORSRequest("get", "http://182.254.146.112/demo.php");
if (request){
        request.onload = function(){
        console.log(request.responseText);
        //對 request.responseText 進行處理
        };
        request.send();
    }

上述代碼在幾大主流瀏覽器中都能得到很好的支持。另外,Firefox、 Safari 、 Chrome 和 IE11 中的 XMLHttpRequest 對象與 IE<11 中的 XDomainRequest 對象都提供了一些接口,這兩個對象共同的屬性/方法如下:

方法/屬性 說明
abort() 用於停止正在進行的請求
onerror 用於替代 onreadystatechange 檢測錯誤
onload 用於替代 onreadystatechange 檢測成功
responseText 用於取得響應內容
send() 用於發送請求

以上成員都包含在 createCORSRequest()函數返回的對象中,在所有瀏覽器中都能正常使用。我們常用到的就是onloadonerror分別代表當請求成功時觸發和當請求失敗時觸發。其基本用法如下:

request.onload = function(){
                console.log(request.responseText);
                //對 request.responseText 進行處理
                };

request.onerror = function(){
                alert("There is an error!");
                };
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章