[轉]利用CORS實現跨域請求

from:http://newhtml.net/using-cors/html

跨域請求一直是網頁編程中的一個難題,在過去,絕大多數人都傾向於使用JSONP來解決這一問題。不過如今,咱們能夠考慮一下W3C中一項新的特性——CORS(Cross-Origin Resource Sharing)了。html5

 

本文的全部代碼均來自http://www.html5rocks.com/en/tutorials/cors/,若是您對其中的任何技術細節存在疑問,請以原文爲準。web

客戶端

建立XmlHttpRequest對象

對於CORS,Chrome、FireFox以及Safari,須要使用XmlHttpRequest2對象;而對於IE,則須要使用XDomainRequest;Opera目前還不支持這一特性,但很快就會支持。編程

所以,在對象的建立上,咱們不得不首先針對不一樣的瀏覽器而進行一下預處理:json

 

 

事件處理

原先的XmlHttpRequest對象僅僅只有一個事件——onreadystatechange,用以通知全部的事件,而如今,咱們除了這個事件以外又多了不少新的。api

事件 說明
onloadstart* 當請求發生時觸發
onprogress 讀取及發送數據時觸發
onabort* 當請求被停止時觸發,如使用abort()方法
onerror 當請求失敗時觸發
onload 當請求成功時觸發
ontimeout 當調用者設定的超時時間已過而仍未成功時觸發
onloadend* 請求結束時觸發(不管成功與否)

注:帶星號的表示IE的XDomainRequest仍不支持。
數據來自http://www.w3.org/TR/XMLHttpRequest2/#events
跨域

絕大多數狀況下,咱們只須要和onloadonerror打交道,就像下面這樣:瀏覽器

 

 

這兒有一點小異樣。儘管咱們能夠經過onerror得知請求發生了錯誤,但在事件處理時,咱們沒法從代碼上獲知失敗的任何緣由。好比,FireFox在失敗時將responseText置空並返回一個0值做爲狀態,這當中並不包含任何錯誤的具體狀況。緩存

withCredentials

標準的CORS請求不對cookies作任何事情,既不發送也不改變。若是但願改變這一狀況,就須要將withCredentials設置爲true安全

 

 

另外,服務端在處理這一請求時,也須要將Access-Control-Allow-Credentials設置爲true。這一點咱們稍後來講。

withCredentials屬性使得請求包含了遠程域的全部cookies,但值得注意的是,這些cookies仍舊遵照「同域」的準則,所以從代碼上你並不能從document.cookies或者回應HTTP頭當中進行讀取。

發送請求

請求經過一個簡單的send()方法進行發送,若是請求當中須要包含任何內容,也只須要將其做爲一個參數傳遞給send()便可。一旦服務端配置OK,那麼你將只須要處理後續的onload事件,這正像咱們平時所熟悉的XHR同樣。

來看一段完整的小代碼:

 

 

服務端

一個CORS請求可能包含多個HTTP頭,甚至有多個請求實際發送,這對於客戶端的開發者來講一般是透明的。由於瀏覽器已經負責實現了CORS最關鍵的部分;可是服務端的後臺腳本則須要咱們本身進行處理,所以咱們還須要瞭解到服務端到底從瀏覽器那裏收到了怎樣的內容。

先來看看流程圖吧。

CORS流程圖

CORS分類

CORS能夠分紅兩種:

  • 簡單請求
  • 複雜請求

一個簡單的請求大體以下:

  • HTTP方法是下列之一
    • HEAD
    • GET
    • POST
  • HTTP頭包含
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type,但僅能是下列之一
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain

任何一個不知足上述要求的請求,即被認爲是複雜請求。一個複雜請求不只有包含通訊內容的請求,同時也包含預請求(preflight request)。

簡單請求

爲了搞清楚複雜請求與簡單請求有何區別,咱們首先來看看簡單請求是怎樣處理的。

JavaScript:

 

 

HTTP請求:

 

 

簡單請求的發送從代碼上來看和普通的XHR沒太大區別,可是HTTP頭當中要求老是包含一個域(Origin)的信息。該域包含協議名、地址以及一個可選的端口。不過這一項實際上由瀏覽器代爲發送,並非開發者代碼能夠觸及到的。

HTTP迴應:

 

 

在迴應中,COR相關的項目全都是以「Access-Control-」做爲前綴的,其意義分列以下:

  • Access-Control-Allow-Origin(必含)- 不可省略,不然請求按失敗處理。該項控制數據的可見範圍,若是但願數據對任何人均可見,能夠填寫「*」。
  • Access-Control-Allow-Credentials(可選) – 該項標誌着請求當中是否包含cookies信息,只有一個可選值:true(必爲小寫)。若是不包含cookies,請略去該項,而不是填寫false。這一項與XmlHttpRequest2對象當中的withCredentials屬性應保持一致,即withCredentialstrue時該項也爲truewithCredentialsfalse時,省略該項不寫。反之則致使請求失敗。
  • Access-Control-Expose-Headers(可選) – 該項肯定XmlHttpRequest2對象當中getResponseHeader()方法所能得到的額外信息。一般狀況下,getResponseHeader()方法只能得到以下的信息:
    • Cache-Control
    • Content-Language
    • Content-Type
    • Expires
    • Last-Modified
    • Pragma

    當你須要訪問額外的信息時,就須要在這一項當中填寫並以逗號進行分隔。不過目前瀏覽器對這一項的實現仍然有一些問題,具體請見文尾的BUG一節。

複雜請求

若是僅僅是簡單請求,那麼即使不用CORS也沒有什麼大不了,但CORS的複雜請求就令CORS顯得更加有用了。簡單來講,任何不知足上述簡單請求要求的請求,都屬於複雜請求。好比說你須要發送PUTDELETE等HTTP動做,或者發送Content-Type: application/json的內容。

複雜請求表面上看起來和簡單請求使用上差很少,但實際上瀏覽器發送了不止一個請求。其中最早發送的是一種「預請求」,此時做爲服務端,也須要返回「預迴應」做爲響應。預請求其實是對服務端的一種權限請求,只有當預請求成功返回,實際請求才開始執行。

JavaScript:

 

 

預請求:

 

 

預請求以OPTIONS形式發送,當中一樣包含域,而且還包含了兩項CORS特有的內容:

  • Access-Control-Request-Method – 該項內容是實際請求的種類,能夠是GET、POST之類的簡單請求,也能夠是PUTDELETE等等。
  • Access-Control-Request-Headers – 該項是一個以逗號分隔的列表,當中是複雜請求所使用的頭部。

顯而易見,這個預請求實際上就是在爲以後的實際請求發送一個權限請求,在預迴應返回的內容當中,服務端應當對這兩項進行回覆,以讓瀏覽器肯定請求是否可以成功完成。例如,剛纔的預請求可能得到服務端以下的迴應:

 

 

來看看預迴應當中可能的項目:

  • Access-Control-Allow-Origin(必含) – 和簡單請求同樣的,必須包含一個域。
  • Access-Control-Allow-Methods(必含) – 這是對預請求當中Access-Control-Request-Method的回覆,這一回復將是一個以逗號分隔的列表。儘管客戶端或許只請求某一方法,但服務端仍然能夠返回全部容許的方法,以便客戶端將其緩存。
  • Access-Control-Allow-Headers(當預請求中包含Access-Control-Request-Headers時必須包含) – 這是對預請求當中Access-Control-Request-Headers的回覆,和上面同樣是以逗號分隔的列表,能夠返回全部支持的頭部。
  • Access-Control-Allow-Credentials(可選) – 和簡單請求當中做用相同。
  • Access-Control-Max-Age(可選) – 以秒爲單位的緩存時間。預請求的的發送並不是免費午飯,容許時應當儘量緩存。

一旦預迴應如期而至,所請求的權限也都已知足,則實際請求開始發送。

實際請求:

 

 

實際迴應:

 

 

若是預請求所要求的權限服務端不容許,那麼服務端能夠直接返回一個普通的HTTP迴應,好比:

 

 

這樣的返回由於不符合客戶端的需求,於是客戶端會直接將請求以失敗計,雖然不是很美氣,不過正符合咱們的實際。此時若是客戶端的onerror事件有監聽函數,那麼將會觸發,而瀏覽器的console窗口也會輸出:

 

 

不過很惋惜,瀏覽器並不會給出詳細的錯誤狀況,僅僅是告知咱們出錯而已。

安全問題

跨域請求始終是網頁安全中一個比較頭疼的問題,CORS提供了一種跨域請求方案,但沒有爲安全訪問提供足夠的保障機制,若是你須要信息的絕對安全,不要依賴CORS當中的權限制度,應當使用更多其它的措施來保障,好比OAuth2。

已知問題

CORS是W3C中一項較「新」的方案,以致於各大網頁解析引擎尚未對其進行完美的實現。下面是截至2011年11月13日時的已知問題:

    • getAllResponseHeaders()方法沒法獲取Access-Control-Expose-Headers當中要求的信息。在Chrome/Safari當中,僅僅只有簡單的頭部可以讀取,其餘沒法獲取;在FireFox當中,沒法得到任何信息。(FireFox Bugzilla/Webkit Bugzilla
    • 在Safari當中,使用GETPOST方法的複雜請求發送時沒有發送預請求的環節。
    • onerror觸發時statusText獲取不到任何內容。
    • Opera截至11.60仍舊不支持CORS,但在12當中會支持(Opera Core Concerns – CORS goes mainline)。
相關文章
相關標籤/搜索