提出問題
在開發前後端分離專案時候,我們總會面臨一個跨域問題。
眾所周知,在以前,跨域可以採用代理、JSONP等方式,而在現代瀏覽器面前,我們有了更好的選擇,CORS
。
我們可以通過伺服器端設定Access-Control-Allow-Origin
響應頭,即可使指定來源像訪問同源介面一樣訪問跨域介面。
在使用CORS
的時候,後臺採用token
檢驗機制,前臺傳送請求必須將token
放到Request Header
中,那麼就需要傳輸自定義Header
資訊,這時候細心的你一定會發現一個問題,在前端ajax請求資料的時候,有時候會向後臺一次性傳送兩次請求,這兩次請求第一次無返回資料,第二次才會返回正確資料。像下圖這個樣子,莫名多出了一個 OPTIONS
的請求:
不用懷疑,這不是你的程式碼有bug,也不是在請求函式中重複呼叫了請求,因為很明顯,兩次的 Request Method
是不一樣的。
如果你也曾因這個問題,困惑過,迷茫過,不知所因。那麼關於這個問題,我將為你給出答案!
對於CORS
跨域,有兩種不同的請求型別。
- 簡單跨域請求
- 複雜跨域請求(帶預檢的跨域請求)。
簡單跨域請求
簡單跨域請求是指滿足以下兩個條件的請求。1、HTTP方法是以下三種方法之一:
HEAD
GET
POST
2、HTTP的頭資訊不超出以下幾種欄位:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
:只限於三個值,application/x-www-form-urlencoded、multipart/form-data、text/plain
簡單跨域請求的部分響應頭如下:
-
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
當你需要訪問額外的資訊時,就需要在這一項當中填寫並以逗號進行分隔。
複雜跨域請求
任何一個不滿足簡單跨域請求要求的請求,即被認為是複雜請求,也稱作帶預檢的跨域請求。
一個複雜請求不止傳送一個包含通訊內容的請求,其中最先傳送的是一種**”預檢”請求**,此時作為服務端,也需要返回**”預回應”**作為響應。”預檢”請求實際上是對服務端的一種許可權請求,只有當”預檢”請求成功返回,實際請求才開始執行。
預請求以OPTIONS
形式傳送,當中同樣包含域,並且還包含了兩項CORS
特有的內容:
Access-Control-Request-Method
– 該項內容是實際請求的種類,可以是GET、POST之類的簡單請求,也可以是PUT、DELETE等等。Access-Control-Request-Headers
– 該項是一個以逗號分隔的列表,當中是複雜請求所使用的頭部。
顯而易見,這個”預檢”請求實際上就是在為之後的實際請求傳送一個許可權請求,在預回應返回的內容當中,服務端應當對這兩項進行回覆,以讓瀏覽器確定請求是否能夠成功完成。一旦預回應如期而至,所請求的許可權也都已滿足,才會發出真實請求,攜帶真實資料。
解決方法
現在問題所在已經很明顯了,那麼面對這種跨域預檢機制造成的多次請求問題,我們可以在後臺設定Access-Control-Max-Age
來控制瀏覽器在多長時間內(單位s)無需在請求時傳送預檢請求,從而減少不必要的預檢請求。
關於CORS的更細緻的問題可以檢視MDN