一、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()函數返回的對象中,在所有瀏覽器中都能正常使用。我們常用到的就是onload
和onerror
分別代表當請求成功時觸發和當請求失敗時觸發。其基本用法如下:
request.onload = function(){
console.log(request.responseText);
//對 request.responseText 進行處理
};
request.onerror = function(){
alert("There is an error!");
};