這是在我們的測試進階社群的微信討論群里有人討論的一個面試題:
Http 請求的 post 和 put的區別是什麼?
很快有人回答「put請求單個數據」,後面提問人又去網上查了資料,查出來 put 和 post的區別是 「put 同樣的請求,前一條會被後一條覆蓋」。
接著另一位群友又發表了不同意見,並直接在群里上傳了一個截圖:
大家對這個問題看法各異,查到的資料也都不一樣。那麼到底Http 請求的 post 和 put的區別是什麼呢?特別提一下,我們不能把一些中文資料里的通俗解釋當作標準答案。在網上查資料,我們需要培養一定的鑒別能力。
首先,這裡有一個誤區,很多沒寫過 web 應用的同學認為這兩個方法的功能區別是固有的,一定存在的。但實際上, http 請求的 post 和 put分別具體實現什麼樣的功能,都是由程序員在寫服務端代碼時決定的。一個 post 請求和一個 put 請求上攜帶的信息量是一樣的,同樣的 http 請求頭(header),同樣的 http 請求體(body),唯一不同的是請求方法名,一個叫 post,一個叫 put。理論上來說程序員完全可以實現功能一樣的處理函數來處理 post 請求和 put請求。
例1:程序員其實完全可以實現相同的 post 和 put請求的處理函數,以下使用的是hug框架。
import hug @hug.post() def post_a_user(user): return {"your_input":user+"X"}
@hug.put() def put_a_user(user): return {"your_input":user+"X"}
但是,既然攜帶的信息量相同,具體功能也是程序員自行決定,為什麼 http 協議里要有 post 方法 和 put 方法兩個方法呢?原來,在 http協議里,對 post 和 put是有官方建議的使用方法的(其實對所有http請求方法都有官方建議用法。)網際網路的網路協議是在RFC里定義的,在網上,我們可以查到定義http協議的RFC。在w3c的網站上,我們可以找到HTTP協議相關的RFC:https://www.w3.org/Protocols
最早的是1999年的 RFC2616和 RFC2617,其中RFC2616里9.6節專門寫了 Post 和 Put 的區別。
以下是RFC2616在網路上流傳的中文版中對 Post 和 Put的區別的描述:
POST方法和PUT方法請求最根本的區別是請求URI(Request-URI)的含義不同。POST請求里的URI指示一個能處理請求實體的資源(譯註:此資源可能是一段程序,如jsp里的servlet) 。此資源可能是一個數據接收過程,一個網關(gateway,譯註:網關和代理伺服器的區別是:網關可以進行協議轉換,而代理伺服器不能,只是起代理的作用,比如緩存伺服器其實就是一個代理伺服器),或者一個單獨接收注釋的實體。而PUT方法請求里有一個實體一一用戶代理知道URI意指什麼,並且伺服器不能把此請求應用於其他URI指定的資源。如果伺服器期望請求被應用於一個不同的URI,那麼它必須發送301(永久移動了)響應;用戶代理可以自己決定是否重定向請求。
但之後RFC 2616在2014年被廢棄,改為 RFC 723X系列。
在RFC 7231 (https://tools.ietf.org/html/rfc7231)里4.3.3 Post 和4.3.4 Put 分別講解了這兩個方法。其中,也有對 Post 和 Put 區別的官方描述:但是很遺憾,我暫時搜不到RFC 7231中文翻譯版。以下只能是我個人嘗試按照自己理解翻譯一下了,翻譯水平有限,建議大家還是看原版。
POST方法和PUT方法請求最根本的區別是發起請求的目的不同。post 請求的目的是根據資源自身的語義來處理這個資源(譯註:我理解這個意思就是說,post請求可以根據實際請求的資源來決定到底怎麼處理,原文中4.3.3節給了一些例子。我會把我的翻譯版附在本文末尾。)。put請求的目的是用來替換整個目標資源。put 請求具有 冪等性(idempotent)。(冪等性的意思在這個RFC的4.2.2里說了「同樣的請求,不管發多少次,每次伺服器處理完之後的結果,都和只發一次是一樣的。」這裡我舉個例子幫助大家理解:你的伺服器端存放了一個文章收藏夾,你通過一個put請求來修改收藏夾里的文章名字,這個 put 請求的請求體里包含了整個文章收藏夾里所有文章的名字。當服務端處理這個請求時,只要從 put 請求里拿出這個文章名字列表,整個存到伺服器上,就是一個完整的新的收藏夾。那麼這樣的put 請求,無論你是發一次,還是發多次,假設伺服器都能成功處理,那麼最終對伺服器造成的影響都是一樣的:即伺服器端原有的收藏夾里的文章列表被替換成了put請求裡帶的收藏夾的文章列表。)
除了冪等性(idempotent),官方文檔里還有其他的一些詳細介紹,比如緩存性,post 是可以被緩存的,put 卻不行。RFC文檔里put 請求的介紹特別長。我猜你如果在面試時能跟面試官講一講冪等性之類的http方法的特性,再挑剔的面試官也會滿意的吧。而除了這個官方給的最根本區別以外,我們還可以結合實際工作經驗中用到的 post 和 put 來談一談這兩者的區別。畢竟,官方只是提供了這兩個請求方法,然後要求大家去用,但程序員真實使用的web 框架未必要求大家百分百按照 http協議來對請求做處理。
那如果你做的項目里根本沒用到這些方法,或者你們的開發把服務端邏輯寫得很有個性沒按協要求來。。。 那可以試試來參加我們的項目實戰,這次的項目實戰里用的 Github api 可是會用到大多數 http 請求方法的。而且 Github 的http 介面本身實現也比較規範,屬於比較有參考價值的。
最後附上我翻譯的一個半的RFC7231的小節(post的翻完了,put的翻了一小半。。實在太長,需要的可以閱讀原文):
4.3.3 post
服務端在處理時post方法發出的請求時,應該按照請求的具體資源的具體語義來對該資源做相應的處理。例如,以下情況一般使用 Post 方法:
服務端根據這些數據處理的實際結果來選擇一個Http響應狀態碼。除了206 (Partial Content), 304 (Not Modified), and 416 (Range Not Satisfiable) 這三個以外的 http響應狀態碼都可以作為 Post 請求的響應狀態碼。
如果post請求希望創建一個或多個資源,並且服務端把這些資源成功創建了,那麼應該(原文大寫的SHOULD)返回一個 201 (Created) 並且在響應頭(response header)里加入Location欄位,值為創建出來的資源的id。並返回「a representation that describes the status of the request while referring to the new resource(s)」 (譯註:這一段每個詞我都認識,但連起來我就看不懂了。。。)
Post 請求的響應在響應里明確包含了freshness信息時,這個響應是可緩存的。然而 Post 請求的緩存機制並沒有被廣泛實現和應用。假如服務端真的希望客戶端能夠利用緩存機制來緩存一個post請求的響應里的信息(也就是這個post創建出來的資源的URI),並且在後面的get 請求里重用這些被緩存的信息,那麼服務端可以返回一個 狀態碼為200(OK)的響應,並且在響應頭裡包含 Content-location這個欄位,其值為 post請求創建的資源URI。
如果post請求希望創建一個或多個資源,但服務端發現這些資源本來就存在,服務端可以回復一個303 (See Other),表示希望客戶端進行重定向。同時在響應頭裡加入Location欄位,值為現有的這個資源的id。
4.3.4 put
put方法用於創建或者替換整個資源,請求體(body)里包含這個資源的完整描述。一個 put 請求如果成功了,那麼理論上來說,對同樣的 url 做一個 get 請求,應該能得到一個 200 (OK) 的響應。但實際上,並不能保證get 到200的響應。因為,可能同時有其他人也在對這個資源做操作,那麼就可能在get請求抵達伺服器之前,這個資源已經被別人改掉了。所以說, put 請求成功,只能代表在這個請求被伺服器處理的那個時刻上,這個資源被創建或替換成請求體里的樣子了。(譯註:比如我用put請求修改一個小組裡的所有用戶列表,我的請求體里包含了小組裡所有用戶的用戶名列表,那麼在伺服器處理我這個請求,並告訴我成功了的那個時刻,這個小組的用戶名一定和我發的put請求里的用戶名列表是一樣的。但是同時有可能有別的人也在嘗試改這個小組的信息,那麼我緊接著發一個 get 請求,可能get到的小組裡的用戶名列表跟我剛才put的已經不一樣了。)
如果put請求希望創建一個或多個資源,並且服務端把這資源成功創建了,那麼服務端必須(原文大寫的MUST)給用戶返回一個狀態碼為 201 (Created) 的響應。而如果這個請求是用來修改某個資源的狀態的,那麼服務端 必須(MUST)返回一個狀態碼為 200 (OK) 的響應或者一個狀態碼為 204 (No Content)的響應。 200 和204 都表示成功完成了這個請求。
服務端應該(SHOULD)忽略 put請求頭裡無法識別出含義的欄位,而不應該把header里的這些欄位也存在資源里。
(關於 PUT的 描述太長了,後面還有整整兩頁沒翻,以後有空接著翻譯吧。。。或者哪位讀者感興趣可以翻一下。)
推薦閱讀:
TAG:HTTP |