利用CORS实现跨域请求--转
原文地址:http://newhtml.net/using-cors/
跨域請求一直是網頁編程中的一個難題,在過去,絕大多數人都傾向于使用JSONP來解決這一問題。不過現在,我們可以考慮一下W3C中一項新的特性——CORS(Cross-Origin Resource Sharing)了。
?
本文的所有代碼均來自http://www.html5rocks.com/en/tutorials/cors/,如果您對其中的任何技術細節存在疑問,請以原文為準。
客戶端
創建XmlHttpRequest對象
對于CORS,Chrome、FireFox以及Safari,需要使用XmlHttpRequest2對象;而對于IE,則需要使用XDomainRequest;Opera目前還不支持這一特性,但很快就會支持。
因此,在對象的創建上,我們不得不首先針對不同的瀏覽器而進行一下預處理:
?
function createCORSRequest(method, url) {var xhr = new XMLHttpRequest();if ("withCredentials" in xhr) {// "withCredentials"屬性是XMLHTTPRequest2中獨有的xhr.open(method, url, true);} else if (typeof XDomainRequest != "undefined") {// 檢測是否XDomainRequest可用xhr = new XDomainRequest();xhr.open(method, url);} else {// 看起來CORS根本不被支持xhr = null;}return xhr; }var xhr = createCORSRequest('GET', url); if (!xhr) {throw new Error('CORS not supported'); }?
事件處理
原先的XmlHttpRequest對象僅僅只有一個事件——onreadystatechange,用以通知所有的事件,而現在,我們除了這個事件之外又多了很多新的。
| onloadstart* | 當請求發生時觸發 |
| onprogress | 讀取及發送數據時觸發 |
| onabort* | 當請求被中止時觸發,如使用abort()方法 |
| onerror | 當請求失敗時觸發 |
| onload | 當請求成功時觸發 |
| ontimeout | 當調用者設定的超時時間已過而仍未成功時觸發 |
| onloadend* | 請求結束時觸發(無論成功與否) |
注:帶星號的表示IE的XDomainRequest仍不支持。
數據來自http://www.w3.org/TR/XMLHttpRequest2/#events。
絕大多數情況下,我們只需要和onload及onerror打交道,就像下面這樣:
?
xhr.onload = function() {var responseText = xhr.responseText;console.log(responseText);// 繼續其它代碼 };xhr.onerror = function() {console.log('There was an error!'); };?
這兒有一點小異樣。盡管我們可以通過onerror得知請求發生了錯誤,但在事件處理時,我們無法從代碼上獲知失敗的任何原因。比如,FireFox在失敗時將responseText置空并返回一個0值作為狀態,這當中并不包含任何錯誤的具體情況。
withCredentials
標準的CORS請求不對cookies做任何事情,既不發送也不改變。如果希望改變這一情況,就需要將withCredentials設置為true。
?
xhr.withCredentials = true;?
另外,服務端在處理這一請求時,也需要將Access-Control-Allow-Credentials設置為true。這一點我們稍后來說。
withCredentials屬性使得請求包含了遠程域的所有cookies,但值得注意的是,這些cookies仍舊遵守“同域”的準則,因此從代碼上你并不能從document.cookies或者回應HTTP頭當中進行讀取。
發送請求
請求通過一個簡單的send()方法進行發送,如果請求當中需要包含任何內容,也只需要將其作為一個參數傳遞給send()即可。一旦服務端配置OK,那么你將只需要處理后續的onload事件,這正像我們平時所熟悉的XHR一樣。
來看一段完整的小代碼:
?
// 創建XHR對象 function createCORSRequest(method, url) {var xhr = new XMLHttpRequest();if ("withCredentials" in xhr) {// 針對Chrome/Safari/Firefox.xhr.open(method, url, true);} else if (typeof XDomainRequest != "undefined") {// 針對IExhr = new XDomainRequest();xhr.open(method, url);} else {// 不支持CORSxhr = null;}return xhr; }// 輔助函數,用于解析返回的內容 function getTitle(text) {return text.match('')[1]; }// 發送CORS請求 function makeCorsRequest() {// bibliographica.org是支持CORS的var url = 'http://bibliographica.org/';var xhr = createCORSRequest('GET', url);if (!xhr) {alert('CORS not supported');return;}// 回應處理xhr.onload = function() {var text = xhr.responseText;var title = getTitle(text);alert('Response from CORS request to ' + url + ': ' + title);};xhr.onerror = function() {alert('Woops, there was an error making the request.');};xhr.send(); }?
服務端
一個CORS請求可能包含多個HTTP頭,甚至有多個請求實際發送,這對于客戶端的開發者來說通常是透明的。因為瀏覽器已經負責實現了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:
?
var url = 'http://alice.com/cors'; var xhr = createCORSRequest('GET', url); xhr.send();?
HTTP請求:
?
GET /cors HTTP/1.1 Origin: http://api.alice.com Host: api.bob.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...?
簡單請求的發送從代碼上來看和普通的XHR沒太大區別,但是HTTP頭當中要求總是包含一個域(Origin)的信息。該域包含協議名、地址以及一個可選的端口。不過這一項實際上由瀏覽器代為發送,并不是開發者代碼可以觸及到的。
HTTP回應:
?
Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8?
在回應中,COR相關的項目全都是以“Access-Control-”作為前綴的,其意義分列如下:
- Access-Control-Allow-Origin(必含)- 不可省略,否則請求按失敗處理。該項控制數據的可見范圍,如果希望數據對任何人都可見,可以填寫“*”。
- Access-Control-Allow-Credentials(可選) – 該項標志著請求當中是否包含cookies信息,只有一個可選值:true(必為小寫)。如果不包含cookies,請略去該項,而不是填寫false。這一項與XmlHttpRequest2對象當中的withCredentials屬性應保持一致,即withCredentials為true時該項也為true;withCredentials為false時,省略該項不寫。反之則導致請求失敗。
- Access-Control-Expose-Headers(可選) – 該項確定XmlHttpRequest2對象當中getResponseHeader()方法所能獲得的額外信息。通常情況下,getResponseHeader()方法只能獲得如下的信息:
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
當你需要訪問額外的信息時,就需要在這一項當中填寫并以逗號進行分隔。不過目前瀏覽器對這一項的實現仍然有一些問題,具體請見文尾的BUG一節。
復雜請求
如果僅僅是簡單請求,那么即便不用CORS也沒有什么大不了,但CORS的復雜請求就令CORS顯得更加有用了。簡單來說,任何不滿足上述簡單請求要求的請求,都屬于復雜請求。比如說你需要發送PUT、DELETE等HTTP動作,或者發送Content-Type: application/json的內容。
復雜請求表面上看起來和簡單請求使用上差不多,但實際上瀏覽器發送了不止一個請求。其中最先發送的是一種“預請求”,此時作為服務端,也需要返回“預回應”作為響應。預請求實際上是對服務端的一種權限請求,只有當預請求成功返回,實際請求才開始執行。
JavaScript:
?
var url = 'http://alice.com/cors'; var xhr = createCORSRequest('PUT', url); xhr.setRequestHeader('X-Custom-Header', 'value'); xhr.send();?
預請求:
?
OPTIONS /cors HTTP/1.1 Origin: http://api.alice.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.bob.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...?
預請求以OPTIONS形式發送,當中同樣包含域,并且還包含了兩項CORS特有的內容:
- Access-Control-Request-Method?– 該項內容是實際請求的種類,可以是GET、POST之類的簡單請求,也可以是PUT、DELETE等等。
- Access-Control-Request-Headers?– 該項是一個以逗號分隔的列表,當中是復雜請求所使用的頭部。
顯而易見,這個預請求實際上就是在為之后的實際請求發送一個權限請求,在預回應返回的內容當中,服務端應當對這兩項進行回復,以讓瀏覽器確定請求是否能夠成功完成。例如,剛才的預請求可能獲得服務端如下的回應:
?
Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8?
來看看預回應當中可能的項目:
- 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(可選) – 以秒為單位的緩存時間。預請求的的發送并非免費午餐,允許時應當盡可能緩存。
一旦預回應如期而至,所請求的權限也都已滿足,則實際請求開始發送。
實際請求:
?
PUT /cors HTTP/1.1 Origin: http://api.alice.com Host: api.bob.com X-Custom-Header: value Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...?
實際回應:
?
Access-Control-Allow-Origin: http://api.bob.com Content-Type: text/html; charset=utf-8?
如果預請求所要求的權限服務端不允許,那么服務端可以直接返回一個普通的HTTP回應,比如:
?
// ERROR - No CORS headers, this is an invalid request! Content-Type: text/html; charset=utf-8?
這樣的返回因為不符合客戶端的需求,因而客戶端會直接將請求以失敗計,雖然不是很美氣,不過正符合我們的實際。此時如果客戶端的onerror事件有監聽函數,那么將會觸發,而瀏覽器的console窗口也會輸出:
?
XMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.?
不過很可惜,瀏覽器并不會給出詳細的錯誤情況,僅僅是告知我們出錯而已。
安全問題
跨域請求始終是網頁安全中一個比較頭疼的問題,CORS提供了一種跨域請求方案,但沒有為安全訪問提供足夠的保障機制,如果你需要信息的絕對安全,不要依賴CORS當中的權限制度,應當使用更多其它的措施來保障,比如OAuth2。
已知問題
CORS是W3C中一項較“新”的方案,以至于各大網頁解析引擎還沒有對其進行完美的實現。下面是截至2011年11月13日時的已知問題:
- getAllResponseHeaders()方法無法獲取Access-Control-Expose-Headers當中要求的信息。在Chrome/Safari當中,僅僅只有簡單的頭部能夠讀取,其他無法獲取;在FireFox當中,無法獲得任何信息。(FireFox Bugzilla/Webkit Bugzilla)
- 在Safari當中,使用GET、POST方法的復雜請求發送時沒有發送預請求的環節。
- onerror觸發時statusText獲取不到任何內容。
- Opera截至11.60仍舊不支持CORS,但在12當中會支持(Opera Core Concerns – CORS goes mainline)。
- CORS的W3C規范
- 為CORS而配置服務器
轉載于:https://www.cnblogs.com/davidwang456/p/6427757.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的利用CORS实现跨域请求--转的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: netflix zuul 1.x与zuu
- 下一篇: netflix ribbon概述