當一個資源從與該資源自己所在的服務器不一樣的域或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。php
而CORS 容許瀏覽器向跨源服務器,發出跨域請求,從而克服了AJAX只能同源使用的限制。html
CORS是一個W3C標準,它同時須要瀏覽器和服務端的支持,其中瀏覽器端的支持以下html5
瀏覽器基本都支持,所以,想要實現CORS通訊,只要服務器實現了CORS接口便可。web
cors請求根據發出的請求是否知足一些條件,將請求分爲兩種:簡單請求與非簡單請求。編程
若請求知足全部下述條件,則該請求可視爲「簡單請求」:json
對於簡單請求,瀏覽器直接發出一個CORS請求,並在請求頭中加入一個 Origin 段來描述本次請求來自哪一個源(協議 + 域名 + 端口)。以下就是一個簡單請求的請求頭:canvas
GET /resources/public-data/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Referer: http://foo.example/examples/access-control/simpleXSInvocation.html Origin: http://foo.example
而服務器則須要在返回頭中包含 Access-Control-Allow-Origin 段,來代表哪些外域能夠訪問。以下是一個返回頭api
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 00:23:53 GMT Server: Apache/2.0.61 Access-Control-Allow-Origin: * Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: application/xml
其中 Access-Control-Allow-Origin:* 代表該資源能夠被任意外域訪問。若是服務端僅容許來自 http://foo.example 的訪問,該首部字段的內容以下:Access-Control-Allow-Origin: http://foo.example。如今,除了 http://foo.example,其它外域均不能訪問該資源。跨域
CORS請求默認不發送Cookie和HTTP認證信息,若是要發送Cookie,首先須要在 AJAX 請求中打開 withCredentials 屬性,且服務器的返回頭中必須包含 Access-Control-Allow-Credentials:true 。須要注意的是,當請求中包含cookie時,即打開了 withCredentials 後,服務器返回頭中 Access-Control-Allow-Origin 就不能再設置爲 *,而必需要顯示指明容許哪些源訪問。瀏覽器
非簡單請求是指那些包含特殊要求的請求,對於這一類請求,瀏覽器會在正式請求以前發出一個OPTION請求來詢問服務器是否支持該次請求,這個OPTION請求被稱爲‘預檢請求’。在服務端迴應容許後,瀏覽器纔會發出真正的非簡單請求。
以下是一個 預檢請求 的請求頭:
OPTIONS /resources/post-here/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Origin: http://foo.example Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER, Content-Type 除了Origin 字段,還包含了Access-Control-Request-Method 和 Access-Control-Request-Headers 字段,分別代表 CORS 請求會用到的 請求方法 和 額外的請求頭字段。
服務端在收到預檢請求後會作出迴應,以下是預檢請求的返回頭:
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400 Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain Access-Control-Allow-Methods 表示服務端容許的請求方法,Access-Control-Allow-Headers 表示服務端容許的請求頭額外字段,Access-Control-Max-Age 指定本次預檢請求的有效期,有效期內不用發出另外一條預檢請求。
預檢請求完成後,瀏覽器會將實際請求發出,和簡單請求一致。
Origin:預檢請求或實際請求的源站。協議 + 域名 + 端口。不論是否爲跨域請求,ORIGIN 字段老是會被髮送。
Access-Control-Request-Method:預檢請求專用。將實際請求所使用的 HTTP 方法告訴服務器。
Access-Control-Request-Headers:預檢請求專用。將實際請求所攜帶的額外頭字段告訴服務器。
Access-Control-Allow-Origin:指定容許訪問該資源的外域 URI。當請求打開 withCredentials 屬性時,不可返回 ‘*’。
Access-Control-Expose-Headers:容許瀏覽器經過 getResponseHeader 訪問的額外字段。
Access-Control-Max-Age:預檢請求緩存時間。
Access-Control-Allow-Credentials:當請求打開 withCredentials 屬性時,須要返回true。
Access-Control-Allow-Methods:預檢請求專用。指明實際請求所容許使用的 HTTP 方法。
Access-Control-Allow-Headers:預檢請求專用。指明實際請求所容許攜帶的額外頭字段。
Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘XXX’ is therefore not allowed access.
No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘XXXX’ is therefore not allowed access.
以上兩種錯誤都是因爲服務端返回頭沒包含Access-Control-Allow-Origin字段致使的,解決辦法是添加上該字段並讓請求源包含在字段中。
Response to preflight request doesn’t pass access control check: The ‘Access-Control-Allow-Origin’ header contains the invalid value ‘XXX’. Origin ‘XXX’ is therefore not allowed access.
The ‘Access-Control-Allow-Origin’ header contains the invalid value ‘XXX’. Origin ‘XXX’ is therefore not allowed access.
以上兩種錯誤都是因爲服務端返回頭中的Access-Control-Allow-Origin字段不包含請求源,解決辦法是修改該字段使請求源包含在字段中。
Response to preflight request doesn’t pass access control check: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. Origin ‘XXX’ is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. Origin ‘XXX’ is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
以上兩種錯誤都是因爲請求打開 withCredentials 屬性,但服務端將Access-Control-Allow-Origin設置爲了‘’,解決辦法是用精確寫法替換‘’。
Request header field XXX is not allowed by Access-Control-Allow-Headers in preflight response.
這種錯誤是因爲請求頭中包含Access-Control-Request-Headers,但服務器未返回對應的Access-Control-Allow-Headers
什麼狀況下須要cors?
跨域資源共享標準( cross-origin sharing standard )容許在下列場景中使用跨域 HTTP 請求:
XMLHttpRequest
或 Fetch 發起的跨域 HTTP 請求。 @font-face
使用跨域字體資源), 所以,網站就能夠發佈 TrueType 字體資源,並只容許已受權網站進行跨站調用。drawImage
將 Images/video 畫面繪製到 canvas如何實現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值做爲狀態,這當中並不包含任何錯誤的具體狀況。
標準的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") { // 針對IE xhr = new XDomainRequest(); xhr.open(method, url); } else { // 不支持CORS xhr = 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能夠分紅兩種:
一個簡單的請求大體以下:
HEAD
GET
POST
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)GET
、POST
方法的複雜請求發送時沒有發送預請求的環節。onerror
觸發時statusText
獲取不到任何內容。原文連接:https://liuhuihao.com/http/2018/04/28/HTTP%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6-CORS-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.html
原文連接:http://newhtml.net/using-cors/