GET和POST

 

w3school給出的比較

下面的表格比較了兩種 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 中。

 

 

從HTTP自己來說GET與POST的區別

徵求意見稿(英語:Request For Comments,縮寫爲RFC),是由互聯網工程任務組(IETF)發佈的一系列備忘錄。

RFC7231裏定義了HTTP方法的幾個性質:

  1. Safe - 安全 這裏的「安全」和一般理解的「安全」意義不一樣,若是一個方法的語義在本質上是「只讀」的,那麼這個方法就是安全的。客戶端向服務端的資源發起的請求若是使用了是安全的方法,就不該該引發服務端任何的狀態變化,所以也是無害的。 此RFC定義,GET, HEAD, OPTIONS 和 TRACE 這幾個方法是安全的。 可是這個定義只是規範,並不能保證方法的實現也是安全的,服務端的實現可能會不符合方法語義,正如上文說過的使用GET修改用戶信息的狀況。 引入安全這個概念的目的是爲了方便網絡爬蟲和緩存,以避免調用或者緩存某些不安全方法時引發某些意外的後果。User Agent(瀏覽器)應該在執行安全和不安全方法時作出區分對待,並給用戶以提示。

  2. Idempotent - 冪等 冪等的概念是指同一個請求方法執行屢次和僅執行一次的效果徹底相同。按照RFC規範,PUT,DELETE和安全方法都是冪等的。一樣,這也僅僅是規範,服務端實現是否冪等是沒法確保的。 引入冪等主要是爲了處理同一個請求重複發送的狀況,好比在請求響應前失去鏈接,若是方法是冪等的,就能夠放心地重發一次請求。這也是瀏覽器在後退/刷新時遇到POST會給用戶提示的緣由:POST語義不是冪等的,重複請求可能會帶來意想不到的後果。

  3. Cacheable - 可緩存性 顧名思義就是一個方法是否能夠被緩存,此RFC裏GET,HEAD和某些狀況下的POST都是可緩存的,可是絕大多數的瀏覽器的實現裏僅僅支持GET和HEAD。關於緩存的更多內容能夠去看RFC7234。

 

語法與語義

對語法和語義的理解。語法是和文法結構有關,然而語義是和按照這個結構所組合的單詞符號的意義有關。合理的語法結構並不代表語義是合法的。例如咱們常說:我上大學,這個句子是符合語法規則的,也符合語義規則。可是大學上我,雖然符合語法規則,但沒有什麼意義,因此說是不符合語義的。

HTTP請求報文的格式:

從語法上來講,只要符合該格式的請求就是符合語法的。語義上來講,GET的語義就是「獲取資源」,POST的語義是「處理資源」,那麼在具體實現這兩個方法時,就必須考慮其語義,作出符合其語義的行爲,若是使用GET方法建立用戶信息,POST獲取資源列表,這樣就只能說這個請求是「合法」的,但不是「符合語義」的。

 

瀏覽器的GET和POST

這裏特指瀏覽器中Ajax的HTTP請求,即從HTML和瀏覽器誕生就一直使用的HTTP協議中的GET/POST。瀏覽器用GET請求來獲取一個html頁面/圖片/css/js等資源;用POST來提交一個<form>表單,並獲得一個結果的網頁。

GET

「讀取「一個資源。好比Get到一個html文件。反覆讀取不該該對訪問的數據有反作用。好比」GET一下,用戶就下單了,返回訂單已受理「,這是不可接受的。沒有反作用被稱爲「冪等「。

POST

在頁面裏<form> 標籤會定義一個表單。點擊其中的submit元素會發出一個POST請求讓服務器作一件事。這件事每每是有反作用的,不冪等的。不冪等也就意味着不能隨意屢次執行。所以也就不能緩存,不能保存書籤等。

 

GET和POST攜帶數據的格式也有區別。當瀏覽器發出一個GET請求時,就意味着要麼是用戶本身在瀏覽器的地址欄輸入,要不就是點擊了html裏a標籤的href中的url。因此其實並非GET只能用url,而是瀏覽器直接發出的GET只能由一個url觸發。因此沒辦法,GET上要在url以外帶一些參數就只能依靠url上附帶querystring。可是HTTP協議自己並無這個限制。

瀏覽器的POST請求都來自表單提交。每次提交,表單的數據被瀏覽器用編碼到HTTP請求的body裏。瀏覽器發出的POST請求的body主要有有兩種格式,一種是application/x-www-form-urlencoded用來傳輸簡單的數據,大概就是"key1=value1&key2=value2"這樣的格式。另一種是傳文件,會採用multipart/form-data格式。採用後者是由於application/x-www-form-urlencoded的編碼方式對於文件這種二進制的數據很是低效。

 

接口中的GET和POST

這裏是指經過瀏覽器的Ajax api,或者iOS/Android的App的http client,java的commons-httpclient/okhttp或者是curl,postman之類的工具發出來的GET和POST請求。

當用HTTP實現接口發送請求時,就沒有瀏覽器中那麼多限制了,只要是符合HTTP格式的就能夠發。其中的「請求方法」能夠是GET也能夠是POST,或者其餘的HTTP Method,如PUT、DELETE、OPTION……。從協議自己看,並無什麼限制說GET必定不能沒有body,POST就必定不能把參放到<URL>的querystring上。所以其實能夠更加自由的去利用格式。好比Elastic Search的_search api就用了帶body的GET;也能夠本身開發接口讓POST一半的參數放在url的querystring裏,另一半放body裏;你甚至還可讓全部的參數都放Header裏——能夠作各類各樣的定製,只要請求的客戶端和服務器端可以約定好。

固然,太自由也帶來了另外一種麻煩,因而就有了一些列接口規範/風格。其中名氣最大的當屬REST。REST充分運用GET、POST、PUT和DELETE,約定了這4個接口分別獲取、建立、替換和刪除「資源」,REST最佳實踐還推薦在請求體使用json格式。這樣僅僅經過看HTTP的method就能夠明白接口是什麼意思,而且解析格式也獲得了統一。

json相對於x-www-form-urlencoded的優點在於1)能夠有嵌套結構;以及 2)能夠支持更豐富的數據類型。經過一些框架,json能夠直接被服務器代碼映射爲業務實體。用起來十分方便。可是若是是寫一個接口支持上傳文件,那麼仍是multipart/form-data格式更合適。

 

REST接口規範

在REST中, 【GET】 + 【資源定位符】被專用於獲取資源或者資源列表,以下。與瀏覽器的場景相似,REST GET也不該該有反作用,因而能夠被反覆無腦調用。

 GET http://foo.com/books          獲取書籍列表
 GET http://foo.com/books/:bookId 根據bookId獲取一本具體的書

REST 【POST】+ 【資源定位符】則用於「建立一個資源」,以下。這裏就能留意到瀏覽器中用來實現表單提交的POST,和REST裏實現建立資源的POST語義上的不一樣。

 POST http://foo.com/books
 {
  "title": "西遊記",
  "author": "施耐庵",
  ...
 }

REST POST和REST PUT的區別有些api是使用PUT做爲建立資源的Method。PUT與POST的區別在於,PUT的實際語義是「replace」replace。REST規範裏提到PUT的請求體應該是完整的資源,包括id在內。好比上面的建立一本書的api也能夠定義爲:

 PUT http://foo.com/books
 {
 "id": "BOOK:affe001bbe0556a",
 "title": "西遊記",
 "author": "施耐庵",
 ...
 }

服務器應該先根據請求提供的id進行查找,若是存在一個對應id的元素,就用請求中的數據總體替換已經存在的資源;若是沒有,就用「把這個id對應的資源從【空】替換爲【請求數據】「。直觀看起來就是「建立」了。與PUT相比,POST更像是一個「factory」,經過一組必要的數據建立出完整的資源。

 

 

 

關於安全性

咱們常聽到GET不如POST安全,由於POST用body傳輸數據,而GET用url傳輸,更加容易看到。可是從攻擊的角度,不管是GET仍是POST都不夠安全,由於HTTP自己是明文協議每一個HTTP請求和返回的每一個byte都會在網絡上明文傳播,無論是url,header仍是body。這徹底不是一個「是否容易在瀏覽器地址欄上看到「的問題。

爲了不傳輸中數據被竊取,必須作從客戶端到服務器的端端加密。業界的通行作法就是https——即用SSL協議協商出的密鑰加密明文的http數據。這個加密的協議和HTTP協議自己相互獨立。若是是利用HTTP開發公網的站點/App,要保證安全,https是最最基本的要求。

固然,端端加密並不必定非得用https。好比國內金融領域都會用私有網絡,也有GB的加密協議SM系列。但除了軍隊,金融等特殊機構以外,彷佛並無必要本身發明一套相似於ssl的協議。

回到HTTP自己,的確GET請求的參數更傾向於放在url上,所以有更多機會被泄漏。好比攜帶私密信息的url會展現在地址欄上,還能夠分享給第三方,就很是不安全了。此外,從客戶端到服務器端,有大量的中間節點,包括網關,代理等。他們的access log一般會輸出完整的url,好比nginx的默認access log就是如此。若是url上攜帶敏感數據,就會被記錄下來。但請注意,就算私密數據在body裏,也是能夠被記錄下來的,所以若是請求要通過不信任的公網,避免泄密的惟一手段就是https。這裏說的「避免access log泄漏「僅僅是指避免可信區域中的http代理的默認行爲帶來的安全隱患。好比你是不太但願讓本身公司的運維同窗從公司主網關的log裏看到用戶的密碼吧。

另外,上面講過,若是是用做接口,GET實際上也能夠帶body,POST也能夠在url上攜帶數據。因此實際上到底怎麼傳輸私密數據,要看具體場景具體分析。固然,絕大多數場景,用POST + body裏寫私密數據是合理的選擇。一個典型的例子就是「登陸」:

 POST http://foo.com/user/login
 {
  "username": "dakuankuan",
  "passowrd": "12345678"
 }

安全是一個巨大的主題,有由不少細節組成的一個完備體系,好比返回私密數據的mask,XSS,CSRF,跨域安全,前端加密,釣魚,salt,…… POST和GET在安全這件事上僅僅是個小角色。所以單獨討論POST和GET自己哪一個更安全意義並非太大。只要記得通常狀況下,私密數據傳輸用POST + body就好。

關於編碼

常見的說法有,好比GET的參數只能支持ASCII,而POST能支持任意binary,包括中文。但其實從上面能夠看到,GET和POST實際上都能用url和body。所以所謂編碼確切地說應該是http中url用什麼編碼,body用什麼編碼。

先說下url。url只能支持ASCII的說法源自於RFC1738

Thus, only alphanumerics, the special characters "$-_.+!*'(),", and reserved characters used for their reserved purposes may be used unencoded within a URL.

實際上這裏規定的僅僅是一個ASCII的子集[a-zA-Z0-9$-_.+!*'(),]。它們是能夠「不經編碼」在url中使用。好比儘管空格也是ASCII字符,可是不能直接用在url裏。

那這個「編碼」是什麼呢?若是有了特殊符號和中文怎麼辦呢?一種叫作percent encoding的編碼方法就是幹這個用的:https://en.wikipedia.org/wiki/Percent-encodingen.wikipedia.org

這也就是爲啥咱們偶爾看到url裏有一坨%和16位數字組成的序列。

使用Percent Encoding,即便是binary data,也是能夠經過編碼後放在URL上的。

但要特別注意,這個編碼方式只管把字符轉換成URL可用字符,可是卻無論字符集編碼(好比中文究竟是用UTF8仍是GBK)這塊早期一直都至關亂,也沒有什麼統一規範。好比有時跟網頁編碼同樣,有的是操做系統的編碼同樣。最要命的是瀏覽器的地址欄是不受開發者控制的。這樣,對於一樣一個帶中文的url,若是有的瀏覽器必定要用GBK(好比老的IE8),有的必定要用UTF8(好比chrome)。後端就可能認不出來。對此經常使用的辦法是避免讓用戶輸入這種帶中文的url。若是有這種形式的請求,都改爲用戶界面上輸入,而後經過Ajax發出的辦法。Ajax發出的編碼形式開發者是能夠100%控制的。

不過目前基本上utf8已經大一統了。如今的開發者除非是被國家規定要求必定要用GB系列編碼的場景,基本上不會再遇到這類問題了。

關於url的編碼,阮一峯的一篇文章有比較詳細的解釋:http://www.ruanyifeng.com/blog/2010/02/url_encoding.html

順便說一句,儘管在瀏覽器地址欄能夠看到中文。但這種url在發送請求過程當中,瀏覽器會把中文用字符編碼+Percent Encode翻譯爲真正的url,再發給服務器。瀏覽器地址欄裏的中文只是想讓用戶體驗好些而已。

再討論下Body。HTTP Body相對好些,由於有個Content-Type來比較明確的定義。好比:

 POST xxxxxx HTTP/1.1
 ...
 Content-Type: application/x-www-form-urlencoded ; charset=UTF-8

這裏Content-Type會同時定義請求body的格式(application/x-www-form-urlencoded)和字符編碼(UTF-8)。

因此body和url均可以提交中文數據給後端,可是POST的規範好一些,相對不容易出錯,容易讓開發者安心。對於GET+url的狀況,只要不涉及到在老舊瀏覽器的地址欄輸入url,也不會有什麼太大的問題。

回到POST,瀏覽器直接發出的POST請求就是表單提交,而表單提交只有application/x-www-form-urlencoded針對簡單的key-value場景;和multipart/form-data,針對只有文件提交,或者同時有文件和key-value的混合提交表單的場景。

若是是Ajax或者其餘HTTP Client發出去的POST請求,其body格式就很是自由了,經常使用的有json,xml,文本,csv……甚至是你本身發明的格式。只要先後端能約定好便可。

瀏覽器的POST須要發兩個請求嗎?

上文中的"HTTP 格式「清楚的顯示了HTTP請求能夠被大體分爲「請求頭」和「請求體」兩個部分。使用HTTP時你們會有一個約定,即全部的「控制類」信息應該放在請求頭中,具體的數據放在請求體裏「。因而服務器端在解析時,老是會先徹底解析所有的請求頭部。這樣,服務器端老是但願可以瞭解請求的控制信息後,就能決定這個請求怎麼進一步處理,是拒絕,仍是根據content-type去調用相應的解析器處理數據,或者直接用zero copy轉發。

好比在用Java寫服務時,請求處理代碼老是能從HttpSerlvetRequest裏getParameter/Header/url。這些信息都是請求頭裏的,框架直接就解析了。而對於請求體,只提供了一個inputstream,若是開發人員以爲應該進一步處理,就本身去讀取和解析請求體。這就能體現出服務器端對請求頭和請求體的不一樣處理方式。

舉個實際的例子,好比寫一個上傳文件的服務,請求url中包含了文件名稱,請求體中是個尺寸爲幾百兆的壓縮二進制流。服務器端接收到請求後,就能夠先拿到請求頭部,查看用戶是否是有權限上傳,文件名是否是符合規範等。若是不符合,就再也不處理請求體的數據了,直接丟棄。而不用等到整個請求都處理完了再拒絕。

爲了進一步優化,客戶端能夠利用HTTP的Continued協議來這樣作:客戶端老是先發送全部請求頭給服務器,讓服務器校驗。若是經過了,服務器回覆「100 - Continue」,客戶端再把剩下的數據發給服務器。若是請求被拒了,服務器就回復個400之類的錯誤,這個交互就終止了。這樣,就能夠避免浪費帶寬傳請求體。可是代價就是會多一次Round Trip。若是恰好請求體的數據也很少,那麼一次性所有發給服務器可能反而更好。

基於此,客戶端就能作一些優化,好比內部設定一次POST的數據超過1KB就先只發「請求頭」,不然就一次性全發。客戶端甚至還能夠作一些Adaptive的策略,統計發送成功率,若是成功率很高,就老是所有發等等。不一樣瀏覽器,不一樣的客戶端(curl,postman)能夠有各自的不一樣的方案。無論怎樣作,優化目的老是在提升數據吞吐和下降帶寬浪費上作一個折衷。

所以究竟是發一次仍是發N次,客戶端能夠很靈活的決定。由於無論怎麼發都是符合HTTP協議的,所以咱們應該視爲這種優化是一種實現細節,而不用扯到GET和POST自己的區別上。更不要當個什麼世紀大發現。

到底什麼算請求體

看完了上面的內容後,讀者也許會對「什麼是請求體」感到困惑不已,好比x-www-form-endocded編碼的body算不算「請求體」呢?

從HTTP協議的角度,「請求頭」就是Method + URL(含querystring)+ Headers;再後邊的都是請求體。

可是從業務角度,若是你把一次請求當即爲一個調用的話。好比上面的

 POST http://foo.com/books
 {
  "title": "西遊記",
  "author": "施耐庵",
  ...
 }

那麼這一行函數名和兩個參數均可以看做是一個請求,不區分頭和體。即使用HTTP協議實現,title和author編碼到了HTTP請求體中。Java的HttpServletRequest支持用getParameter方法獲取x-www-url-form-encoded中的數據,表達的意思就是「請求「的」參數「。

對於HTTP,須要區分【頭】和【體】,Http Request和Http Response都這麼區分。Http這麼幹主要用做

  • 對於HTTP代理

    • 支持轉發規則,好比nginx先要解析請求頭,拿到URL和Header才能決定怎麼作(轉發proxy_pass,重定向redirect,rewrite後從新判斷……)

    • 須要用請求頭的信息記錄log。儘管請求體裏的數據也能夠記錄,但通常只記錄請求頭的部分數據。

    • 若是代理規則不涉及到請求體,那麼請求體就能夠不用從內核態的page cache複製一份到用戶態了,能夠直接zero copy轉發。這對於上傳文件的場景極爲有效。

    • ……

  • 對於HTTP服務器

    • 能夠經過請求頭進行ACL控制,好比看看Athorization頭裏的數據是否能讓認證經過

    • 能夠作一些攔截,好比看到Content-Length裏的數太大,或者Content-Type本身不支持,或者Accept要求的格式本身沒法處理,就直接返回失敗了。

    • 若是body的數據很大,利用Stream API,能夠方便支持一塊一塊的處理數據,而不是一次性所有讀取出來再操做,以致於佔用大量內存。

    • ……

但從高一級的業務角度,咱們在乎的實際上是【請求】和【返回】。當咱們在說「請求頭」這三個字時,也許實際的意思是【請求】。而用HTTP實現【請求】時,可能僅僅用到【HTTP的請求頭】(好比大部分GET請求),也多是【HTTP請求頭】+【HTTP請求體】(好比用POST實現一次下單)。

總之,這裏有兩層,不要混哦。

關於URL的長度

由於上面提到了不管是GET和POST均可以使用URL傳遞數據,因此咱們常說的「GET數據有長度限制「實際上是指」URL的長度限制「。

HTTP協議自己對URL長度並無作任何規定。實際的限制是由客戶端/瀏覽器以及服務器端決定的。

先說瀏覽器。不一樣瀏覽器不太同樣。好比咱們常說的2048個字符的限制,實際上是IE8的限制。而且原始文檔的說的實際上是「URL的最大長度是2083個字符,path的部分最長是2048個字符「。見https://support.microsoft.com/en-us/help/208427/maximum-url-length-is-2-083-characters-in-internet-explorer。IE8以後的IE URL限制我沒有查到明確的文檔,但有些資料稱IE 11的地址欄只能輸入法2047個字符,可是容許用戶點擊html裏的超長URL。我沒實驗,哪位有興趣能夠試試。

Chrome的URL限制是2MB,見https://chromium.googlesource.com/chromium/src/+/master/docs/security/url_display_guidelines/url_display_guidelines.md

Safari,Firefox等瀏覽器也有本身的限制,但都比IE大的多,這裏就不挨個列出了。

然而新的IE已經開始使用Chrome的內核了,也就意味着「瀏覽器端URL的長度限制爲2048字符」這種說法會慢慢成爲歷史。

其餘的客戶端,好比Java的,js的http client大多數也並無限制URL最大有多長。

除了瀏覽器,服務器這邊也有限制,好比apache的LimieRequestLine指令。

apache實際上限制的是HTTP請求第一行「Request Line「的長度,即<METHOD><URL> <VERSION>那一行。

再好比nginx用large_client_header_buffers 指令來分配請求頭中的很長數據的buffer。這個buffer能夠用來處理url,header value等。

Tomcat的限制是web.xml裏maxHttpHeaderSize來設置的,控制的是整個「請求頭」的總長度。

爲啥要限制呢?若是寫過解析一段字符串的代碼就能明白,解析的時候要分配內存。對於一個字節流的解析,必須分配buffer來保存全部要存儲的數據。而URL這種東西必須看成一個總體看待,沒法一塊一塊處理,因而就處理一個請求時必須分配一整塊足夠大的內存。若是URL太長,而併發又很高,就容易擠爆服務器的內存;同時,超長URL的好處並很少,我也只有處理老系統的URL時由於不敢碰原來的邏輯,又得追加更多數據,纔會使用超長URL。

對於開發者來講,使用超長的URL徹底是給本身埋坑,須要同時要考慮先後端,以及中間代理每個環節的配置。此外,超長URL會影響搜索引擎的爬蟲,有些爬蟲甚至沒法處理超過2000個字節的URL。這也就意味着這些URL沒法被搜到,坑爹啊。

其實並無太大必要弄清楚精確的URL最大長度限制。我我的的經驗是,只要某個要開發的資源/api的URL長度有可能達到2000個bytes以上,就必須使用body來傳輸數據,除非有特殊狀況。至於究竟是GET + body仍是POST + body能夠看狀況決定。

留意,1個漢字字符通過UTF8編碼 + percent encoding後會變成9個字節,別算錯哦。

 

 

參考連接:

https://www.zhihu.com/question/28586791/answer/145424285

https://www.zhihu.com/question/28586791/answer/767316172

https://www.w3school.com.cn/tags/html_ref_httpmethods.asp

相關文章
相關標籤/搜索