作者:楊光鏈接:https://www.zhihu.com/question/28586791/answer/145424285來源:知乎著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。剛好最近比較系統(tǒng)的研究了一下HTTP協(xié)議里GET和POST方法的區(qū)別,以下內(nèi)容摘抄自我的博客:HTTP協(xié)議中GET和POST方法的區(qū)別 - Sunshinevvv's Blog** ,本文記錄了我的探究歷程,沒有耐心的童鞋可以直接翻到最后看結(jié)論:語義之爭。
------------------以下是博客正文-----------------
HTTP協(xié)議中GET和POST方法的區(qū)別已經(jīng)是老生常談了,也是面試熱門問題,我之前對此也只有一個粗淺的印象,這里來認(rèn)真探討一下。
通常的理解
w3schools關(guān)于這個問題的解答:HTTP 方法:GET 對比 POST** 列出了一般的理解,比如:
GET后退按鈕/刷新無害,POST數(shù)據(jù)會被重新提交(瀏覽器應(yīng)該告知用戶數(shù)據(jù)會被重新提交)。GET書簽可收藏,POST為書簽不可收藏。GET能被緩存,POST不能緩存 。GET編碼類型application/x-www-form-url,POST編碼類型encodedapplication/x-www-form-urlencoded 或 multipart/form-data。為二進(jìn)制數(shù)據(jù)使用多重編碼。GET歷史參數(shù)保留在瀏覽器歷史中。POST參數(shù)不會保存在瀏覽器歷史中。GET對數(shù)據(jù)長度有限制,當(dāng)發(fā)送數(shù)據(jù)時,GET 方法向 URL 添加數(shù)據(jù);URL 的長度是受限制的(URL 的最大長度是 2048 個字符)。POST無限制。GET只允許 ASCII 字符。POST沒有限制。也允許二進(jìn)制數(shù)據(jù)。與 POST 相比,GET 的安全性較差,因為所發(fā)送的數(shù)據(jù)是 URL 的一部分。在發(fā)送密碼或其他敏感信息時絕不要使用 GET !POST 比 GET 更安全,因為參數(shù)不會被保存在瀏覽器歷史或 web 服務(wù)器日志中。GET的數(shù)據(jù)在 URL 中對所有人都是可見的。POST的數(shù)據(jù)不會顯示在 URL 中。
這個對比整體沒什么毛病,但只是給出了一些現(xiàn)象上的區(qū)別,但并沒有解釋為什么,對于這個問題的理解不能就停在這一層。
理解錯了?
有一篇文章99%的人理解錯 HTTP 中 GET 與 POST 的區(qū)別**,否定了上述回答:“很遺憾,這不是我們要的回答!”,作者說:
GET和POST本質(zhì)上就是TCP鏈接,并無差別。但是由于HTTP的規(guī)定和瀏覽器/服務(wù)器的限制,導(dǎo)致他們在應(yīng)用過程中體現(xiàn)出一些不同。 GET和POST還有一個重大區(qū)別,簡單的說:GET產(chǎn)生一個TCP數(shù)據(jù)包;POST產(chǎn)生兩個TCP數(shù)據(jù)包。對于GET方式的請求,瀏覽器會把http header和data一并發(fā)送出去,服務(wù)器響應(yīng)200(返回數(shù)據(jù)); 而對于POST,瀏覽器先發(fā)送header,服務(wù)器響應(yīng)100 continue,瀏覽器再發(fā)送data,服務(wù)器響應(yīng)200 ok(返回數(shù)據(jù))。
都講到TCP了,感覺很高大上有木有,起碼當(dāng)時看到這篇文章的我是信了的。
反轉(zhuǎn)??
但是在逛知乎時又看到了這篇文章:聽說『99% 的人都理解錯了 HTTP 中 GET 與 POST 的區(qū)別』??,指出了前文的兩個錯誤:
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?說白了就是相關(guān)的 RFC。implementation 則是所有實現(xiàn)了 specification 中描述的代碼/庫/產(chǎn)品,比如 curl,Python 的 requests 庫,或者 Chrome。POST 請求怎么發(fā)送,根本就不是這段 RFC 在討論的事情。RFC 中只說明了 100 continue 和 Expect header 的聯(lián)系,比如你想在 GET 請求里帶 body,一樣可以發(fā)送 Expect: 100-continue 并等待 100 continue,這是符合標(biāo)準(zhǔn)的。也就是說,『XHR 發(fā)送兩個 TCP packets』是關(guān)于 implementation 的知識,而不是關(guān)于 specification 的知識。你不能說『Chrome 在 AJAX POST 的時候會發(fā)兩個 TCP packets,GET 只會發(fā)一個』是 GET 和 POST 的區(qū)別,正如你不能因為北京 PM 2.5 經(jīng)常爆表就說國家關(guān)于工業(yè)廢氣排放的標(biāo)準(zhǔn)有問題。
說得似乎更有道理,而且也搬出了RFC,specification,implementation這些高端詞匯,這下子我這個吃瓜群眾再也坐不住了,決定親自去研究一下。
RFC探秘
首先,什么是RFC呢?Wiki上面的定義是:
征求意見稿(英語:Request For Comments,縮寫為RFC),是由互聯(lián)網(wǎng)工程任務(wù)組(IETF)發(fā)布的一系列備忘錄。文件收集了有關(guān)互聯(lián)網(wǎng)相關(guān)信息,以及UNIX和互聯(lián)網(wǎng)社區(qū)的軟件文件,以編號排定。目前RFC文件是由互聯(lián)網(wǎng)協(xié)會(ISOC)贊助發(fā)行。
簡單理解RFC就是互聯(lián)網(wǎng)的規(guī)范,我們通常所說的「協(xié)議」就是以RFC的形式存在,而現(xiàn)行的HTTP/1.1規(guī)范的RFC有如下幾個: RFC7230**, RFC7231**, RFC7232**, RFC7233**, RFC7234**,RFC7235**。 其中RFC7231里的Section 4. Request Methods涉及到了幾個HTTP方法,接下來仔細(xì)閱讀這一章節(jié)。
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 「語義」,那么什么是語義呢?這一篇文章給出了解釋:語法和語義的區(qū)別**。
一種語言是合法句子的集合。什么樣的句子是合法的呢?可以從兩方面來判斷:語法和語義。語法是和文法結(jié)構(gòu)有關(guān),然而語義是和按照這個結(jié)構(gòu)所組合的單詞符號的意義有關(guān)。合理的語法結(jié)構(gòu)并不表明語義是合法的。例如我們常說:我上大學(xué),這個句子是符合語法規(guī)則的,也符合語義規(guī)則。但是大學(xué)上我,雖然符合語法規(guī)則,但沒有什么意義,所以說是不符合語義的。
對于HTTP請求來說,語法是指請求響應(yīng)的格式,比如請求第一行必須是 方法名 URI 協(xié)議/版本 這樣的格式,具體內(nèi)容可以參見之前寫的《圖解HTTP》讀書筆記里面的內(nèi)容,凡是符合這個格式的請求都是合法的。
語義則定義了這一類型的請求具有什么樣的性質(zhì)。比如GET的語義就是「獲取資源」,POST的語義是「處理資源」,那么在具體實現(xiàn)這兩個方法時,就必須考慮其語義,做出符合其語義的行為。
當(dāng)然在符合語法的前提下實現(xiàn)違背語義的行為也是可以做到的,比如使用GET方法修改用戶信息,POST獲取資源列表,這樣就只能說這個請求是「合法」的,但不是「符合語義」的。 寫到這里突然聯(lián)想到XML里面的兩個概念:Well Formed和Valid,似乎也正是語法和語義的理念呢。
上文說到方法是請求語義的主要來源,也即是還有次要來源,一些請求Header可以進(jìn)一步修飾請求的語義,比如一個帶上了 Range Header的GET請求就變成了部分請求。
RFC7231里定義了HTTP方法的幾個性質(zhì):
Safe - 安全這里的「安全」和通常理解的「安全」意義不同,如果一個方法的語義在本質(zhì)上是「只讀」的,那么這個方法就是安全的??蛻舳讼蚍?wù)端的資源發(fā)起的請求如果使用了是安全的方法,就不應(yīng)該引起服務(wù)端任何的狀態(tài)變化,因此也是無害的。 此RFC定義,GET, HEAD, OPTIONS 和 TRACE 這幾個方法是安全的。但是這個定義只是規(guī)范,并不能保證方法的實現(xiàn)也是安全的,服務(wù)端的實現(xiàn)可能會不符合方法語義,正如上文說過的使用GET修改用戶信息的情況。引入安全這個概念的目的是為了方便網(wǎng)絡(luò)爬蟲和緩存,以免調(diào)用或者緩存某些不安全方法時引起某些意外的后果。User Agent(瀏覽器)應(yīng)該在執(zhí)行安全和不安全方法時做出區(qū)分對待,并給用戶以提示。
Idempotent - 冪等冪等的概念是指同一個請求方法執(zhí)行多次和僅執(zhí)行一次的效果完全相同。按照RFC規(guī)范,PUT,DELETE和安全方法都是冪等的。同樣,這也僅僅是規(guī)范,服務(wù)端實現(xiàn)是否冪等是無法確保的。引入冪等主要是為了處理同一個請求重復(fù)發(fā)送的情況,比如在請求響應(yīng)前失去連接,如果方法是冪等的,就可以放心地重發(fā)一次請求。這也是瀏覽器在后退/刷新時遇到POST會給用戶提示的原因:POST語義不是冪等的,重復(fù)請求可能會帶來意想不到的后果。
Cacheable - 可緩存性 顧名思義就是一個方法是否可以被緩存,此RFC里GET,HEAD和某些情況下的POST都是可緩存的,但是絕大多數(shù)的瀏覽器的實現(xiàn)里僅僅支持GET和HEAD。關(guān)于緩存的更多內(nèi)容可以去看RFC7234。
在這三個特性里一直在強調(diào)同一個事情,那就是協(xié)議不等于實現(xiàn):協(xié)議規(guī)定安全在實現(xiàn)里不一定安全,協(xié)議規(guī)定冪等在實現(xiàn)里不一定冪等,協(xié)議規(guī)定可緩存在實現(xiàn)里不一定可緩存。這其實就是上面那個作者提到的specification和implementation的關(guān)系。
語義之爭
走到這一步,其實就明白了要理解這兩個方法的區(qū)別,本質(zhì)上是 「語義」的對比而不是「語法」的對比,是「Specification」的對比而不是「Implementation」的對比 。
關(guān)于這兩種方法的語義,RFC7231里原文已經(jīng)寫得很好了:
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的語義是根據(jù)請求負(fù)荷(報文主體)對指定的資源做出處理,具體的處理方式視資源類型而不同。POST不安全,不冪等,(大部分實現(xiàn))不可緩存。為了針對其不可緩存性,有一系列的方法來進(jìn)行優(yōu)化,以后有機(jī)會再研究(FLAG已經(jīng)立起)。
還是舉一個通俗栗子吧,在微博這個場景里,GET的語義會被用在「看看我的Timeline上最新的20條微博」這樣的場景,而POST的語義會被用在「發(fā)微博、評論、點贊」這樣的場景中。