你所不知道的跨域資源共享(CORS)

本文同步更新於www.devsai.comjavascript

寫在前面

有沒有一看到講跨域資源共享的就不想再看的了,網上的跨域資源共享的博文,三天兩頭的就出一篇。java

既然你已經進來看了,還請你稍稍忍耐下,繼續往下看,或許你會發現和以前看到的有不同的收穫。git

其實,以前看過我寫的文章的同窗可能知道,我寫過一篇關於《跨域及跨域資源共享》(沒有看過的同窗,能夠從這進去)。比較全面的介紹了跨域的多種解決方案,以及說明了跨域資源共享.github

大家會不會想:那既然已經寫過了,爲何又寫一篇? 是否是博主已經沒啥東西可寫了。web

別急,接下來,讓我跟大家慢慢道來。跨域

大家所知道的

看過以前寫的《跨域及跨域資源共享》或看過多篇CORS文章的同窗能夠選擇性的跳過這一小段了。app

就像大家看到過的相關的文章,講跨域資源共享,通常講其原理時,一定要講到跨域資源共享的請求有兩種(也有不少沒有講到):cors

簡單請求 (Simple Request)
預檢測請求 (Preflight Request)異步

而後就會進一步的講到,何時發只發簡單請求,又何時會在發真實的請求前,先發預檢測請求,廣泛的都是這麼說的(包括我以前寫的也是)async

如下幾種狀況時都知足時是簡單請求

request header 是簡單的請求頭

Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
等等非自定義的請求頭

request method 是下面的請求類型

HEAD
GET
POST

Content-Type 只限三個值

application/x-www-form-urlencoded、multipart/form-data、text/plain

若是不知足以上條件的都會先發送預檢測請求,即爲OPTIONS請求類型的請求

幾乎都是這麼說的,差異只是描述方式不一樣,比例下面的別人寫的:

什麼? 不信, 那你隨便搜索幾篇相關的文章看看。

那麼,這有什麼問題

先來作個例子吧。

假設要實現帶進度條的上傳功能,接口不是同域上的,服務端已經給配置了支持跨域資源共享的響應頭,那咱們直接用XmlHttpRequest就能夠了

javaScript代碼大概以下:

var xhr = new XmlHttpRequest();
xhr.onreadystatechange = function() {
    // do something
}

xhr.upload.onprogress = function(){
    // do something
}

xhr.open('POST','http://127.0.0.1/upload');
var fd = new FormData();
fd.append('file',file);
xhr.send(fd);複製代碼

而後上傳文件並查看下請求

What? 爲何會有兩個請求啊。 是否是它不知足簡單請求的要求(已不記得簡單請求的同窗往上再看看)

那麼,咱們來看看該真實請求的請求頭

簡單請求要求:

要求點 實際內容 是否知足
請求方式 POST 知足
請求頭 都是非自定義的請求頭 知足
Content-Type multipart/form-data 知足

不是都知足了嗎?那爲何,會有兩個請求,爲何在發送真實請求前還發了OPTIONS方式的請求。

爲何!!!整我的感受都很差了!!!

變個魔術

把上面的javaScript代碼改動下:

var xhr = new XmlHttpRequest();
xhr.onreadystatechange = function() {
    // do something
}

xhr.open('POST','http://127.0.0.1/upload');
var fd = new FormData();
fd.append('file',file);
xhr.send(fd);複製代碼

去掉了xhr.upload.onprogress,上傳後再來看下請求及請求頭:

只有一個請求,請求頭內容還都同樣。 (這究竟是怎麼回事...有種不再相信愛情的趕覺了!!!)

看到這裏的同窗,有木有以爲博主在坑大家,放了兩張相同的請求頭截圖就想糊弄。

俗話說得好,不試不知道,一試嚇一跳,要不,大家也親自試試,一試便知真假。

再次雙手奉上demo(喜歡的順便點個贊哦~)。

分析問題

從上面的兩小段JS看出,只是去除了上傳的進度信息事件。也就是說加了進度事件就多發了個預檢測請求。

那麼,還有沒有其餘的事件了?添加其餘的事件會不會也會發送預檢測請求呢?

事件有onerror,onloadstart等等。通過博主的測試,上述答案是確定的,添加其餘事件後,確實也會發生預檢測請求。

追求真理的同窗們,在博主的demo裏改改試試吧。

如今已經知道了問題的所在,由上傳相關事件致使了跨域請求多發了預檢測請求,說好的簡單請求(Simple Request)呢~

博主抱着對問題刨根問底的精神,再次查看cors相關文檔,找到了以下的內容:

  • If the following conditions are true, follow the simple cross-origin request algorithm:

    • The request method is a simple method and the force preflight flag is unset.

    • Each of the author request headers is a simple header or author request headers is empty.

經過這段,咱們知道,原來除了咱們所知道的簡單請求的幾大特徵外,還提到了force preflight flag,這是什麼鬼?

難道是由於設置了它? 那麼何時設置了force preflight flag?

上面咱們知道了由於上傳事件致使了發送預檢測請求,會不會是上傳監聽事件的時候給設置了force preflight flag
而後在XmlHttpRequest level 2 中的找到了相關的內容,以證明個人猜想是正確的。

有下面幾段內容:

force preflight flag
The upload events flag.

從這段能夠知道,force preflight flagupload events flag是對應的,看到這裏就知道了,只要upload events flag被設置true

那麼就等於force preflight flag被設置了true,這時,無論請求的類型的是否是simple method,也無論請求頭是否是simple header,都會先發送預檢測請求。

接下來,咱們再來看看upload events flag會在什麼狀況下被設置呢?

If the asynchronous flag is true and one or more event listeners are registered on the XMLHttpRequestUpload object set the upload events flag to true. Otherwise, set the upload events flag to false.

原來,當asynchronous flagtrue而且XMLHttpRequestUpload(即示例中的xhr.upload)的一個或多個事件被監聽的時候,upload listener flag就會被設置了。

這也正如以前測試的,當加了xhr.upload.onprogress後,出現了預檢測請求。

到這裏總算水落石出了。

這裏還須要說明的一點是,the asynchronous flag就是xhr.open()的第三個參數,當未設置第三參數時,默認爲異步,也就是the asynchronous flagtrue

若是第三個參數設置爲false,那麼即便有上傳的監聽事件也不會發送預檢測請求(Preflight Reuqest)

總結

之後還會不會義正詞嚴的在別人面前說,只要是知足幾大條件(是非自定義的請求頭,是GETorPOSTorHEAD,或Content-Type是那三種值的)就是簡單請求 ,就不會發生預檢測請求。

經過本文可知,並不是知足這幾大條件就必定是簡單請求的,
應該要加個前置條件,是不是在上傳請求中跨域,是不是異步的,是否監聽了上傳事件。

看到這,可能你想說,寫這麼多有啥用,對實際開發有幫助嗎?或許沒什麼實際的幫助吧,又或許你也不會碰到吧。

但,最起碼當你碰到的時候,你看到了兩個請求,再看了下代碼,你已經內心就有數了,知道這是怎麼一回事了。

一直認爲,作技術的對碰到的問題要知其然,更要知其因此然。

相關文章
相關標籤/搜索