HTTP 協議中 GET 和 POST 方法的區別已經是老生常談了,也是面試熱門問題,我之前對此只有一個粗淺的印象,這裏來認真探討一下。
通常的理解
w3schools 關於這個問題的解答:HTTP 方法:GET 對比 POST 列出了一般的理解:
方法
GET
POST
後退按鈕/重新整理
無害
資料會被重新提交(瀏覽器應該告知使用者資料會被重新提交)。
書籤
可收藏為書籤
不可收藏為書籤
WordPress 加速緩存
能被 WordPress 加速緩存
不能 WordPress 加速緩存
編碼型別
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 修改使用者資訊的情況。
引入安全這個概念的目的是為了方便互聯網爬蟲和 WordPress 加速緩存,以免呼叫或者 WordPress 加速緩存某些不安全方法時引起某些意外的後果。 User Agent(瀏覽器)應該在執行安全和不安全方法時做出區分對待,並給使用者以提示。
Idempotent – 冪等
冪等的概念是指同一個請求方法執行多次和僅執行一次的效果完全相同。按照 RFC 規範,PUT,DELETE 和安全方法都是冪等的。同樣,這也僅僅是規範,服務端實現是否冪等是無法確保的。
引入冪等主要是為了處理同一個請求重複傳送的情況,比如在請求響應前失去連線,如果方法是冪等的,就可以放心地重發一次請求。這也是瀏覽器在後退/重新整理時遇到 POST 會給使用者提示的原因:POST 語義不是冪等的,重複請求可能會帶來意想不到的後果。
Cacheable – 可 WordPress 加速緩存性 顧名思義就是一個方法是否可以被 WordPress 加速緩存,此 RFC 裏 GET,HEAD 和某些情況下的 POST 都是可 WordPress 加速緩存的,但是絕大多數的瀏覽器的實現裏僅僅支援 GET 和 HEAD 。關於 WordPress 加速緩存的更多內容可以去看 RFC7234 。
在這三個特性裏一直在強調同一個事情,那就是協議不等於實現:協議規定安全在實現裏不一定安全,協議規定冪等在實現裏不一定冪等,協議規定可 WordPress 加速緩存在實現裏不一定可 WordPress 加速緩存。這其實就是上面那個作者提到的 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 方法是安全、冪等、可 WordPress 加速緩存的(除非有 Cache-ControlHeader 的約束),GET 方法的報文主體沒有任何語義。
POST 的語義是根據請求負荷(報文主體)對指定的資源做出處理,具體的處理方式視資源型別而不同。 POST 不安全,不冪等,(大部分實現)不可 WordPress 加速緩存。為了針對其不可 WordPress 加速緩存性,有一系列的方法來進行優化,以後有機會再研究(FLAG 已經立起)。
還是舉一個通俗栗子吧,在微博這個場景裏,GET 的語義會被用在「看看我的 Timeline 上最新的 20 條微博」這樣的場景,而 POST 的語義會被用在「發微博、評論、點贊」這樣的場景中。
總結
本文從通常的理解出發,經過一次質疑和又一次反質疑,一波三折,最終通過閲讀 RFC 規範加深了對於 HTTP 方法的理解,真是一次令人愉快的探究之旅~我最大的感受就是:不要人云亦云,要堅持獨立思考,不要滿足於二手知識,要努力追本溯源。