今天工作的時(shí)候吴叶,項(xiàng)目經(jīng)理反饋了一個(gè)問(wèn)題:Network中出現(xiàn)了兩個(gè)相同的請(qǐng)求阐虚,而我代碼中只請(qǐng)求了一次。鞍雎薄实束?又踩坑了奥秆?在研究了一番之后,發(fā)現(xiàn)還有這種操作咸灿?构订!
出現(xiàn)的狀況
Network中出現(xiàn)了兩個(gè)相同的請(qǐng)求(如圖),兩個(gè)發(fā)起了同樣的請(qǐng)求析显,花的時(shí)間卻不同鲫咽,一個(gè)55ms,一個(gè)花了294ms谷异。
什么情況啊锦聊?研究了一番歹嘹,我發(fā)現(xiàn)有一個(gè)地方是不同的,Request Method孔庭!
請(qǐng)求時(shí)間短的Request Method是OPTIONS尺上,并且返回值為空。
請(qǐng)求時(shí)間長(zhǎng)的Request Method是GET圆到。
為什么出現(xiàn)這種情況
原以為ajax請(qǐng)求只有HTTP的Request Method只有GET與POST兩種怎抛,后來(lái)發(fā)現(xiàn)還有HEAD、PUT芽淡、DELETE马绝、OPTIONS……的區(qū)別。
本地環(huán)境跑公司項(xiàng)目的時(shí)候挣菲,每次POST之前富稻,為啥瀏覽器還偷偷給我來(lái)一次沒(méi)有返回的OPTIONS請(qǐng)求?
原來(lái)白胀,瀏覽器在某些請(qǐng)求中椭赋,在正式通信前會(huì)增加一次HTTP查詢(xún)請(qǐng)求,稱(chēng)為"預(yù)檢"請(qǐng)求(preflight)或杠。
瀏覽器先詢(xún)問(wèn)服務(wù)器哪怔,當(dāng)前網(wǎng)頁(yè)所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些HTTP動(dòng)詞和頭信息字段向抢。只有得到肯定答復(fù)认境,瀏覽器才會(huì)發(fā)出正式的XMLHttpRequest請(qǐng)求,否則就報(bào)錯(cuò)笋额。
CORS了解一下
CORS是一個(gè)W3C標(biāo)準(zhǔn)元暴,全稱(chēng)是"跨域資源共享"(Cross-origin resource sharing)。
它允許瀏覽器向跨源服務(wù)器兄猩,發(fā)XMLHttpRequest請(qǐng)求茉盏,從而克服了AJAX只能同源使用的限制鉴未。
CORS需要瀏覽器和服務(wù)器同時(shí)支持。目前鸠姨,所有瀏覽器都支持該功能铜秆,IE瀏覽器不能低于IE10。
整個(gè)CORS通信過(guò)程讶迁,都是瀏覽器自動(dòng)完成连茧,不需要用戶(hù)參與。對(duì)于開(kāi)發(fā)者來(lái)說(shuō)巍糯,CORS通信與同源的AJAX通信沒(méi)有差別啸驯,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn)AJAX請(qǐng)求跨源祟峦,就會(huì)自動(dòng)添加一些附加的頭信息罚斗,有時(shí)還會(huì)多出一次附加的請(qǐng)求,但用戶(hù)不會(huì)有感覺(jué)宅楞。
因此针姿,實(shí)現(xiàn)CORS通信的關(guān)鍵是服務(wù)器。只要服務(wù)器實(shí)現(xiàn)了CORS接口厌衙,就可以跨源通信距淫。
兩種請(qǐng)求
瀏覽器將CORS請(qǐng)求分為兩類(lèi):簡(jiǎn)單請(qǐng)求(simple request)和非簡(jiǎn)單請(qǐng)求(not-so-simple request)。
同時(shí)滿(mǎn)足以下條件婶希,就是簡(jiǎn)單請(qǐng)求:
(1) 請(qǐng)求方法是以下三種方法之一: HEAD GET POST
(2)HTTP的頭信息不超出以下幾種字段:Accept Accept-Language Content-Language Last-Event-ID
Content-Type:只限于三個(gè)值application/x-www-form-urlencoded榕暇、multipart/form-data、text/plain
簡(jiǎn)單請(qǐng)求
對(duì)于簡(jiǎn)單請(qǐng)求饲趋,瀏覽器直接發(fā)出CORS請(qǐng)求拐揭。具體來(lái)說(shuō),就是在頭信息之中奕塑,增加一個(gè)Origin字段堂污。
Origin字段用來(lái)說(shuō)明,本次請(qǐng)求來(lái)自哪個(gè)源(協(xié)議 + 域名 + 端口)龄砰。服務(wù)器根據(jù)這個(gè)值盟猖,決定是否同意這次請(qǐng)求。
如果Origin指定的源换棚,不在許可范圍內(nèi)式镐,服務(wù)器會(huì)返回一個(gè)正常的HTTP回應(yīng)。瀏覽器發(fā)現(xiàn)固蚤,這個(gè)回應(yīng)的頭信息沒(méi)有包含Access-Control-Allow-Origin字段(詳見(jiàn)下文)娘汞,就知道出錯(cuò)了,從而拋出一個(gè)錯(cuò)誤夕玩,被XMLHttpRequest的onerror回調(diào)函數(shù)捕獲你弦。注意惊豺,這種錯(cuò)誤無(wú)法通過(guò)狀態(tài)碼識(shí)別,因?yàn)镠TTP回應(yīng)的狀態(tài)碼有可能是200禽作。
如果Origin指定的域名在許可范圍內(nèi)尸昧,服務(wù)器返回的響應(yīng),會(huì)多出幾個(gè)頭信息字段旷偿。都以Access-Control- 開(kāi)頭:
(1)Access-Control-Allow-Origin
該字段是必須的烹俗。它的值要么是請(qǐng)求時(shí)Origin字段的值,要么是一個(gè)*萍程,表示接受任意域名的請(qǐng)求幢妄。
需要注意的是,如果要發(fā)送Cookie茫负,Access-Control-Allow-Origin就不能設(shè)為星號(hào)磁浇,必須指定明確的、與請(qǐng)求網(wǎng)頁(yè)一致的域名朽褪。同時(shí),Cookie依然遵循同源政策无虚,只有用服務(wù)器域名設(shè)置的Cookie才會(huì)上傳缔赠,其他域名的Cookie并不會(huì)上傳,且(跨源)原網(wǎng)頁(yè)代碼中的document.cookie也無(wú)法讀取服務(wù)器域名下的Cookie友题。
(2)Access-Control-Allow-Credentials
該字段可選嗤堰。它的值是一個(gè)布爾值,表示是否允許發(fā)送Cookie度宦。默認(rèn)情況下踢匣,Cookie不包括在CORS請(qǐng)求之中。設(shè)為true戈抄,即表示服務(wù)器明確許可离唬,Cookie可以包含在請(qǐng)求中,一起發(fā)給服務(wù)器划鸽。這個(gè)值也只能設(shè)為true输莺,如果服務(wù)器不要瀏覽器發(fā)送Cookie,刪除該字段即可裸诽。
(3)Access-Control-Expose-Headers
該字段可選嫂用。CORS請(qǐng)求時(shí),XMLHttpRequest對(duì)象的getResponseHeader()方法只能拿到6個(gè)基本字段:Cache-Control丈冬、Content-Language嘱函、Content-Type、Expires埂蕊、Last-Modified往弓、Pragma疏唾。如果想拿到其他字段,就必須在Access-Control-Expose-Headers里面指定亮航。
非簡(jiǎn)單請(qǐng)求
非簡(jiǎn)單請(qǐng)求是那種對(duì)服務(wù)器有特殊要求的請(qǐng)求荸实,比如請(qǐng)求方法是PUT或DELETE,或者Content-Type字段的類(lèi)型是application/json缴淋。
非簡(jiǎn)單請(qǐng)求的CORS請(qǐng)求准给,會(huì)在正式通信之前,增加一次HTTP查詢(xún)請(qǐng)求重抖,稱(chēng)為"預(yù)檢"請(qǐng)求(preflight)露氮。
瀏覽器先詢(xún)問(wèn)服務(wù)器,當(dāng)前網(wǎng)頁(yè)所在的域名是否在服務(wù)器的許可名單之中钟沛,以及可以使用哪些HTTP動(dòng)詞和頭信息字段畔规。只有得到肯定答復(fù),瀏覽器才會(huì)發(fā)出正式的XMLHttpRequest請(qǐng)求恨统,否則就報(bào)錯(cuò)叁扫。
在頁(yè)面域名與接口域名不一致的情況下,就出現(xiàn)了每次請(qǐng)求前先發(fā)送一個(gè)options請(qǐng)求的問(wèn)題畜埋。
OPTIONS請(qǐng)求頭信息中莫绣,除了Origin字段,還至少會(huì)多兩個(gè)特殊字段:
(1)Access-Control-Request-Method
該字段是必須的悠鞍,用來(lái)列出瀏覽器的CORS請(qǐng)求會(huì)用到哪些HTTP方法对室。
(2)Access-Control-Request-Headers
該字段是一個(gè)逗號(hào)分隔的字符串,指定瀏覽器CORS請(qǐng)求會(huì)額外發(fā)送的頭信息字段咖祭。
至于其他亂七八糟的字段掩宜,現(xiàn)在的我還用不到也不懂,將會(huì)慢慢深入了解么翰。
服務(wù)器收到預(yù)檢請(qǐng)求后牺汤,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后硬鞍,確認(rèn)允許跨源請(qǐng)求慧瘤,就可以做出回應(yīng)。
上面的HTTP回應(yīng)中固该,關(guān)鍵的是Access-Control-Allow-Origin字段锅减,表示http://lizard.qa.nt.ctripcorp.com可以請(qǐng)求數(shù)據(jù)。該字段也可以設(shè)為星號(hào)伐坏,表示同意任意跨源請(qǐng)求怔匣。
如果瀏覽器否定了"預(yù)檢"請(qǐng)求,會(huì)返回一個(gè)正常的HTTP回應(yīng),但是沒(méi)有任何CORS相關(guān)的頭信息字段每瞒。這時(shí)金闽,瀏覽器就會(huì)認(rèn)定,服務(wù)器不同意預(yù)檢請(qǐng)求剿骨,因此觸發(fā)一個(gè)錯(cuò)誤代芜,被XMLHttpRequest對(duì)象的onerror回調(diào)函數(shù)捕獲∨ɡ控制臺(tái)會(huì)打印出如下的報(bào)錯(cuò)信息挤庇。
XMLHttpRequest cannot load http://lizard.qa.nt.ctripcorp.com
Origin http://lizard.qa.nt.ctripcorp.com is not allowed by Access-Control-Allow-Origin.
其他字段中Access-Control-Max-Age 用來(lái)指定本次預(yù)檢請(qǐng)求的有效期,單位為秒贷掖。該字段可選嫡秕。
與JSONP的對(duì)比
CORS與JSONP的使用目的相同,但是比JSONP更強(qiáng)大苹威。
JSONP只支持GET請(qǐng)求昆咽,JSONP的優(yōu)勢(shì)在于支持老舊瀏覽器。
最后解決問(wèn)題的方法
解決Network出現(xiàn)兩次相同請(qǐng)求的問(wèn)題使用簡(jiǎn)單請(qǐng)求就不會(huì)遇到了牙甫。