作者:楊光鏈接:https://www.zhihu.com/question/28586791/answer/145424285來源:知乎著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。剛好最近比較系統的研究了一下HTTP協議里GET和POST方法的區別,以下內容摘抄自我的博客:HTTP協議中GET和POST方法的區別 - Sunshinevvv's Blog** ,本文記錄了我的探究歷程,沒有耐心的童鞋可以直接翻到最后看結論:語義之爭。
------------------以下是博客正文-----------------
HTTP協議中GET和POST方法的區別已經是老生常談了,也是面試熱門問題,我之前對此也只有一個粗淺的印象,這里來認真探討一下。
通常的理解
w3schools關于這個問題的解答:HTTP 方法:GET 對比 POST** 列出了一般的理解,比如:
GET后退按鈕/刷新無害,POST數據會被重新提交(瀏覽器應該告知用戶數據會被重新提交)。GET書簽可收藏,POST為書簽不可收藏。GET能被緩存,POST不能緩存 。GET編碼類型application/x-www-form-url,POST編碼類型encodedapplication/x-www-form-urlencoded 或 multipart/form-data。為二進制數據使用多重編碼。GET歷史參數保留在瀏覽器歷史中。POST參數不會保存在瀏覽器歷史中。GET對數據長度有限制,當發送數據時,GET 方法向 URL 添加數據;URL 的長度是受限制的(URL 的最大長度是 2048 個字符)。POST無限制。GET只允許 ASCII 字符。POST沒有限制。也允許二進制數據。與 POST 相比,GET 的安全性較差,因為所發送的數據是 URL 的一部分。在發送密碼或其他敏感信息時絕不要使用 GET !POST 比 GET 更安全,因為參數不會被保存在瀏覽器歷史或 web 服務器日志中。GET的數據在 URL 中對所有人都是可見的。POST的數據不會顯示在 URL 中。
這個對比整體沒什么毛病,但只是給出了一些現象上的區別,但并沒有解釋為什么,對于這個問題的理解不能就停在這一層。
理解錯了?
有一篇文章99%的人理解錯 HTTP 中 GET 與 POST 的區別**,否定了上述回答:“很遺憾,這不是我們要的回答!”,作者說:
GET和POST本質上就是TCP鏈接,并無差別。但是由于HTTP的規定和瀏覽器/服務器的限制,導致他們在應用過程中體現出一些不同。 GET和POST還有一個重大區別,簡單的說:GET產生一個TCP數據包;POST產生兩個TCP數據包。對于GET方式的請求,瀏覽器會把http header和data一并發送出去,服務器響應200(返回數據); 而對于POST,瀏覽器先發送header,服務器響應100 continue,瀏覽器再發送data,服務器響應200 ok(返回數據)。
都講到TCP了,感覺很高大上有木有,起碼當時看到這篇文章的我是信了的。
反轉??
但是在逛知乎時又看到了這篇文章:聽說『99% 的人都理解錯了 HTTP 中 GET 與 POST 的區別』??,指出了前文的兩個錯誤:
100 continue 只有在請求里帶了Expect: 100-continueheader 的時候才有意義。When the request contains an Expect header field that includes a 100-continue expectation, the 100 response indicates that the server wishes to receive the request payload body, as described in Section 5.1.1. The client ought to continue sending the request and discard the 100 response. If the request did not contain an Expect header field containing the 100-continue expectation, the client can simply discard this interim response.
我們通常在討論 GET vs POST 的時候,實際上討論的是 specification,而不是 implementation。什么是 specification?說白了就是相關的 RFC。implementation 則是所有實現了 specification 中描述的代碼/庫/產品,比如 curl,Python 的 requests 庫,或者 Chrome。POST 請求怎么發送,根本就不是這段 RFC 在討論的事情。RFC 中只說明了 100 continue 和 Expect header 的聯系,比如你想在 GET 請求里帶 body,一樣可以發送 Expect: 100-continue 并等待 100 continue,這是符合標準的。也就是說,『XHR 發送兩個 TCP packets』是關于 implementation 的知識,而不是關于 specification 的知識。你不能說『Chrome 在 AJAX POST 的時候會發兩個 TCP packets,GET 只會發一個』是 GET 和 POST 的區別,正如你不能因為北京 PM 2.5 經常爆表就說國家關于工業廢氣排放的標準有問題。
說得似乎更有道理,而且也搬出了RFC,specification,implementation這些高端詞匯,這下子我這個吃瓜群眾再也坐不住了,決定親自去研究一下。
RFC探秘
首先,什么是RFC呢?Wiki上面的定義是:
征求意見稿(英語:Request For Comments,縮寫為RFC),是由互聯網工程任務組(IETF)發布的一系列備忘錄。文件收集了有關互聯網相關信息,以及UNIX和互聯網社區的軟件文件,以編號排定。目前RFC文件是由互聯網協會(ISOC)贊助發行。
簡單理解RFC就是互聯網的規范,我們通常所說的「協議」就是以RFC的形式存在,而現行的HTTP/1.1規范的RFC有如下幾個: RFC7230**, RFC7231**, RFC7232**, RFC7233**, RFC7234**,RFC7235**。 其中RFC7231里的Section 4. Request Methods涉及到了幾個HTTP方法,接下來仔細閱讀這一章節。
The request method token is the primary source of request semantics; it indicates the purpose for which the client has made this request and what is expected by the client as a successful result.
這里牽涉到一個很重要的詞語:semantic 「語義」,那么什么是語義呢?這一篇文章給出了解釋:語法和語義的區別**。
一種語言是合法句子的集合。什么樣的句子是合法的呢?可以從兩方面來判斷:語法和語義。語法是和文法結構有關,然而語義是和按照這個結構所組合的單詞符號的意義有關。合理的語法結構并不表明語義是合法的。例如我們常說:我上大學,這個句子是符合語法規則的,也符合語義規則。但是大學上我,雖然符合語法規則,但沒有什么意義,所以說是不符合語義的。
對于HTTP請求來說,語法是指請求響應的格式,比如請求第一行必須是 方法名 URI 協議/版本 這樣的格式,具體內容可以參見之前寫的《圖解HTTP》讀書筆記里面的內容,凡是符合這個格式的請求都是合法的。
語義則定義了這一類型的請求具有什么樣的性質。比如GET的語義就是「獲取資源」,POST的語義是「處理資源」,那么在具體實現這兩個方法時,就必須考慮其語義,做出符合其語義的行為。
當然在符合語法的前提下實現違背語義的行為也是可以做到的,比如使用GET方法修改用戶信息,POST獲取資源列表,這樣就只能說這個請求是「合法」的,但不是「符合語義」的。 寫到這里突然聯想到XML里面的兩個概念:Well Formed和Valid,似乎也正是語法和語義的理念呢。
上文說到方法是請求語義的主要來源,也即是還有次要來源,一些請求Header可以進一步修飾請求的語義,比如一個帶上了 Range Header的GET請求就變成了部分請求。
RFC7231里定義了HTTP方法的幾個性質:
Safe - 安全這里的「安全」和通常理解的「安全」意義不同,如果一個方法的語義在本質上是「只讀」的,那么這個方法就是安全的。客戶端向服務端的資源發起的請求如果使用了是安全的方法,就不應該引起服務端任何的狀態變化,因此也是無害的。 此RFC定義,GET, HEAD, OPTIONS 和 TRACE 這幾個方法是安全的。但是這個定義只是規范,并不能保證方法的實現也是安全的,服務端的實現可能會不符合方法語義,正如上文說過的使用GET修改用戶信息的情況。引入安全這個概念的目的是為了方便網絡爬蟲和緩存,以免調用或者緩存某些不安全方法時引起某些意外的后果。User Agent(瀏覽器)應該在執行安全和不安全方法時做出區分對待,并給用戶以提示。
Idempotent - 冪等冪等的概念是指同一個請求方法執行多次和僅執行一次的效果完全相同。按照RFC規范,PUT,DELETE和安全方法都是冪等的。同樣,這也僅僅是規范,服務端實現是否冪等是無法確保的。引入冪等主要是為了處理同一個請求重復發送的情況,比如在請求響應前失去連接,如果方法是冪等的,就可以放心地重發一次請求。這也是瀏覽器在后退/刷新時遇到POST會給用戶提示的原因:POST語義不是冪等的,重復請求可能會帶來意想不到的后果。
Cacheable - 可緩存性 顧名思義就是一個方法是否可以被緩存,此RFC里GET,HEAD和某些情況下的POST都是可緩存的,但是絕大多數的瀏覽器的實現里僅僅支持GET和HEAD。關于緩存的更多內容可以去看RFC7234。
在這三個特性里一直在強調同一個事情,那就是協議不等于實現:協議規定安全在實現里不一定安全,協議規定冪等在實現里不一定冪等,協議規定可緩存在實現里不一定可緩存。這其實就是上面那個作者提到的specification和implementation的關系。
語義之爭
走到這一步,其實就明白了要理解這兩個方法的區別,本質上是 「語義」的對比而不是「語法」的對比,是「Specification」的對比而不是「Implementation」的對比 。
關于這兩種方法的語義,RFC7231里原文已經寫得很好了:
The GET method requests transfer of a current selected representation for the target resource. GET is the primary mechanism of information retrieval and the focus of almost all performance optimizations. Hence, when people speak of retrieving some identifiable information via HTTP, they are generally referring to making a GET request.A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request.
The POST method requests that the target resource process the representation enclosed in the request according to the resource’s own specific semantics.
勉強渣翻一下,再加上點自己的理解:
GET的語義是請求獲取指定的資源。GET方法是安全、冪等、可緩存的(除非有 Cache-ControlHeader的約束),GET方法的報文主體沒有任何語義。
POST的語義是根據請求負荷(報文主體)對指定的資源做出處理,具體的處理方式視資源類型而不同。POST不安全,不冪等,(大部分實現)不可緩存。為了針對其不可緩存性,有一系列的方法來進行優化,以后有機會再研究(FLAG已經立起)。
還是舉一個通俗栗子吧,在微博這個場景里,GET的語義會被用在「看看我的Timeline上最新的20條微博」這樣的場景,而POST的語義會被用在「發微博、評論、點贊」這樣的場景中。