之前有人跟我們說,出去面試的時候,有時候會遇到一些讓人頭疼的問題,比如有一次去字節面試,面試官就問了一個讓他很奇怪的問題:“為啥POST請求有時候會發送兩次呢?”這個問題聽起來挺玄乎的,但其實用大白話來說,原因還挺簡單的。咱們這就來聊聊這個事兒。
首先,得明白啥是POST請求。POST請求就是咱們在網上干點啥事兒,比如提交個表單、上傳個文件啥的,得跟服務器說:“嘿,我這兒有點東西,你給我處理一下。”這時候,瀏覽器就會發個POST請求給服務器。
那么,為啥有時候會發兩次呢?這其實跟瀏覽器的“預檢”機制有關系。簡單來說,就是瀏覽器在正式發請求之前,會先問問服務器:“嘿,我要發請求了,你準備好了嗎?能接收嗎?”這個過程就叫做“預檢請求”,也叫OPTIONS請求。
為啥要有這個預檢呢?因為有時候咱們發的請求可能比較復雜,比如請求頭里面帶了點特殊的東西,或者請求方法是PUT、DELETE這種不常用的。這時候,瀏覽器就會有點擔心,怕服務器不理解或者不接受這個請求,所以就先發個簡單的OPTIONS請求去問問。
如果服務器說:“沒問題,你來吧!”那瀏覽器就會再發一次正式的POST請求。所以,咱們就看到了兩次請求:一次是預檢的OPTIONS請求,一次是正式的POST請求。
那么,舉個實際的例子來說明一下。假設你正在開發一個網頁應用,其中有一個功能是讓用戶能夠上傳圖片。當用戶選擇了一張圖片并點擊上傳按鈕時,瀏覽器就會發送一個POST請求到服務器,以便將圖片上傳到服務器上。
但是,如果這個請求中包含了一些特殊的請求頭,比如自定義的X-Requested-With頭,或者請求的內容類型(Content-Type)不是常見的application/x-www-form-urlencoded、multipart/form-data或text/plain,那么瀏覽器就會先發送一個OPTIONS請求進行預檢。
X-Requested-With
application/x-www-form-urlencoded
multipart/form-data
text/plain
這個OPTIONS請求會詢問服務器是否接受這種類型的請求。如果服務器響應說可以接受,那么瀏覽器才會繼續發送正式的POST請求,將圖片上傳到服務器上。
預檢請求和正式請求之間有一些明顯的區別。首先,它們的目的不同。預檢請求的目的是詢問服務器是否接受某種類型的請求,而正式請求則是實際執行某種操作,比如上傳文件、提交表單等。其次,它們的請求方法不同。預檢請求總是使用OPTIONS方法,而正式請求則可以使用GET、POST、PUT、DELETE等方法。最后,它們的請求頭和請求體也可能不同。預檢請求通常只包含一些基本的請求頭,而正式請求則可能包含更多的請求頭和請求體數據。
所以,先總結一下,POST請求有時候會發送兩次,是因為瀏覽器為了保險起見,先發個預檢請求去問問服務器:“我能發這個請求嗎?”得到允許后,再發正式的請求。這樣,就能確保咱們的請求能夠順利到達服務器,并得到正確的處理了。
好了,現在大家明白了吧,以后面試遇到這個問題就可以很好的回答清楚了,說白了不是發送兩次post請求,而是在一些特殊的情況下,如果post請求要帶一些特殊的請求頭,平時不太常見,瀏覽器就會發擔心服務端沒法接收和處理,那如果服務端沒法處理,自己何必還發送post請求攜帶大量的表單數據、文件數據、圖片數據過去呢?這不是吃飽了沒事干么,對吧!
所以此時才會先發個options預檢請求過去,帶一些請求頭,問一下服務端后面能不能處理正式的請求。另外的話,對于發options預檢請求的情況還有一種比較特殊的,就是同源策略下的跨域訪問,這個也是有可能會提前發options預檢請求的。
瀏覽器的同源策略,簡單來說,就是瀏覽器為了安全起見,設置的一個規矩。這個規矩規定,來自不同源的網頁之間不能直接進行交互。這里的“源”指的是協議(比如http或https)、域名(比如www.example.com)和端口號(比如80或443)這三者的組合。只要這三者中有任何一個不同,就認為是不同的源。
那不同的源肯定不能互相瞎請求啊,比如說瀏覽器請求服務器A返回了一個網頁,結果這個服務器A跑的A系統的網頁里,有一個請求要去請求服務器B上部署的B系統,那這種就很奇怪了,對瀏覽器來說你們是兩個服務器,也就是兩個不同的系統,干什么要互相瞎訪問,有沒有可能A系統是一個涉灰系統在搞破壞,盜取數據,對不對?
舉個日常的例子,就像你有兩個家,一個在北京,一個在上海。雖然都是你的家,但因為地理位置不同(就像協議、域名、端口號不同),所以你不能直接從北京的家跑到上海的家去拿東西,除非你走特殊通道(就像跨域資源共享CORS)。
那CORS的話,全稱是跨域資源共享(Cross-Origin Resource Sharing),是瀏覽器和服務器之間的一種約定,用來解決跨域請求的問題。簡單來說,CORS就是一種機制,讓服務器告訴瀏覽器:“嘿,這個網頁雖然來自不同的源,但它是安全的,你可以讓它訪問我的資源。”
就是說服務器A的網頁里的JS代碼去請求服務器B了,那就是跨域了,那跨域發起的請求就是CORS請求了。
那瀏覽器發跨域請求的時候,會不回發options預檢請求呢?其實也是會的,因為當瀏覽器發起跨域請求時,并不是所有的請求都會直接發送。有時候,瀏覽器也是會先發出一個OPTIONS請求,這個請求就像是瀏覽器在問服務器:“嘿,我這兒有一個網頁想發個請求到你的地盤,但是網頁和你的話,咱倆不是同一個源的,你得先告訴我,你同不同意?”這個過程就叫做跨域請求之前的預檢請求。
那么,跨域請求的時候,什么情況下會發出OPTIONS預檢請求呢?主要有以下幾種情況:
1、請求方法不是GET、HEAD、POST:因為GET、HEAD、POST這三種方法被認為是“簡單”的,通常不會對服務器造成太大影響,所以瀏覽器會直接發送請求。但如果是PUT、DELETE等方法,瀏覽器就會先發個OPTIONS請求問問,就怕有人要搞破壞。
對于GET、HEAD、POST這種簡單請求,瀏覽器是沒有預檢請求的,他會直接發送請求到服務器,并在請求頭中包含一個Origin字段,表明這個請求的來源。服務器收到請求后,會根據請求頭中的Origin字段來決定是否允許這個跨域請求。如果允許,服務器會在響應頭中包含Access-Control-Allow-Origin字段,并返回相應的資源。
但是大部分情況下,很多后端系統如果用的是MVC框架的話,都會默認禁止CORS跨域請求,所以這個時候如果你發現后端系統異常了,配置一下MVC框架,打開CORS跨域請求支持就可以了,那就可以處理跨域請求了。
2、請求頭包含了自定義字段:比如你在請求頭里加了個X-Token,瀏覽器就會覺得:“咦,這個請求頭看起來不簡單,我得先問問服務器同不同意。”
X-Token
3、Content-Type不是常見的三種類型:常見的Content-Type有application/x-www-form-urlencoded、multipart/form-data和text/plain。如果你用了其他類型,比如application/json,瀏覽器也會先發個OPTIONS請求。
application/json
再總結一下,當瀏覽器覺得一個跨域請求“不簡單”時,就會先發個OPTIONS請求去預檢一下,看看服務器同不同意。如果服務器同意了,瀏覽器才會繼續發送正式的請求。這就像你去別人家做客,如果不是很熟,你可能會先打個電話問問對方方不方便,得到同意后再去。
好了,今天咱們從一個面試題問post為什么發兩次請求開始引入,用大白話重點聊了聊瀏覽器的options預檢請求問題,并且舉了一些例子,尤其是講了一些同源策略、跨域請求、預檢請求之間的關系,相信大家也學到了不少東西,以后面試遇到一定要加油!
最后給大家提供一個找到適用POST請求API的平臺:
冪簡集成是國內領先的API集成管理平臺,專注于為開發者提供全面、高效、易用的API集成解決方案。冪簡API平臺可以通過以下兩種方式找到所需API:通過關鍵詞搜索API(例如,輸入’人臉識別‘這類品類詞,更容易找到結果)、或者從API Hub分類頁進入尋找。
此外,冪簡集成博客會編寫API入門指南、多語言API對接指南、API測評等維度的文章,讓開發者快速使用目標API。
原文轉自 微信公眾號@石杉的架構筆記