HTTP協議中GET和POST方法的區別


轉載

通常的理解

w3schools關於這個問題的解答:HTTP 方法:GET 對比 POST 列出了一般的理解:

 

方法 GET POST
后退按鈕/刷新 無害 數據會被重新提交(瀏覽器應該告知用戶數據會被重新提交)。
書簽 可收藏為書簽 不可收藏為書簽
緩存 能被緩存 不能緩存
編碼類型 application/x-www-form-urlencoded application/x-www-form-urlencoded 或 multipart/form-data。為二進制數據使用多重編碼。
歷史 參數保留在瀏覽器歷史中。 參數不會保存在瀏覽器歷史中。
對數據長度的限制 是的。當發送數據時,GET 方法向 URL 添加數據;URL 的長度是受限制的(URL 的最大長度是 2048 個字符)。 無限制。
對數據類型的限制 只允許 ASCII 字符。 沒有限制。也允許二進制數據。
安全性 與 POST 相比,GET 的安全性較差,因為所發送的數據是 URL 的一部分。在發送密碼或其他敏感信息時絕不要使用 GET ! POST 比 GET 更安全,因為參數不會被保存在瀏覽器歷史或 web 服務器日志中。
可見性 數據在 URL 中對所有人都是可見的。 數據不會顯示在 URL 中。

 

后來經同學指出,這里關於URL 的長度是受限制的這一點是不對的,HTTP協議並沒有限制URI的長度,具體的長度是由瀏覽器和系統來約束的。

這個對比只是給出了一些現象上的區別,但並沒有解釋為什么,對於這個問題的理解不能就停在這一層。

理解錯了?

有一篇文章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-Control Header的約束),GET方法的報文主體沒有任何語義。

POST的語義是根據請求負荷(報文主體)對指定的資源做出處理,具體的處理方式視資源類型而不同。POST不安全,不冪等,(大部分實現)不可緩存。為了針對其不可緩存性,有一系列的方法來進行優化,以后有機會再研究(FLAG已經立起)。

還是舉一個通俗栗子吧,在微博這個場景里,GET的語義會被用在「看看我的Timeline上最新的20條微博」這樣的場景,而POST的語義會被用在「發微博、評論、點贊」這樣的場景中。

總結

本文從通常的理解出發,經過一次質疑和又一次反質疑,一波三折,最終通過閱讀RFC規范加深了對於HTTP方法的理解,真是一次令人愉快的探究之旅~我最大的感受就是:不要人雲亦雲,要堅持獨立思考,不要滿足於二手知識,要努力追本溯源。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM