欧美成人精品手机在线观看_69视频国产_动漫精品第一页_日韩中文字幕网 - 日本欧美一区二区

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

【W(wǎng)eb開(kāi)發(fā)】萬(wàn)字長(zhǎng)文:GET 和 POST 到底有什么區(qū)別?

admin
2025年3月6日 11:15 本文熱度 831

最近在一次初級(jí)前端的面試中,問(wèn)到了這個(gè)問(wèn)題:“你覺(jué)得 GET 和 POST 到底有什么區(qū)別?”,候選人是這么回答:”get是從服務(wù)器上獲取數(shù)據(jù),post是向服務(wù)器傳送數(shù)據(jù)“。

這個(gè)回答應(yīng)該和很多讀者心目中的答案一致,但實(shí)際上不準(zhǔn)確。

這個(gè)問(wèn)題看上去很初級(jí),但實(shí)際上涉及到的技術(shù)點(diǎn)還是挺多的。今天就來(lái)給大家分享一篇文章,給大家展開(kāi)講講,爭(zhēng)取讓大家徹底弄懂。


HTTP最早被用來(lái)做瀏覽器與服務(wù)器之間交互HTML和表單的通訊協(xié)議;后來(lái)又被被廣泛的擴(kuò)充到接口格式的定義上。

所以在討論GET和POST區(qū)別的時(shí)候,需要現(xiàn)確定下到底是瀏覽器使用的GET/POST還是用HTTP作為接口傳輸協(xié)議的場(chǎng)景。

瀏覽器的GET和POST 

這里特指瀏覽器中Ajax的HTTP請(qǐng)求,即從HTML和瀏覽器誕生就一直使用的HTTP協(xié)議中的GET/POST。瀏覽器用GET請(qǐng)求來(lái)獲取一個(gè)html頁(yè)面/圖片/css/js等資源;用POST來(lái)提交一個(gè)<form>表單,并得到一個(gè)結(jié)果的網(wǎng)頁(yè)。

瀏覽器將GET和POST定義為:

GET

“讀取“一個(gè)資源。比如Get到一個(gè)html文件。反復(fù)讀取不應(yīng)該對(duì)訪問(wèn)的數(shù)據(jù)有副作用。比如”GET一下,用戶就下單了,返回訂單已受理“,這是不可接受的。沒(méi)有副作用被稱為“冪等“(Idempotent)。

因?yàn)镚ET因?yàn)槭亲x取,就可以對(duì)GET請(qǐng)求的數(shù)據(jù)做緩存。這個(gè)緩存可以做到瀏覽器本身上(徹底避免瀏覽器發(fā)請(qǐng)求),也可以做到代理上(如nginx),或者做到server端(用Etag,至少可以減少帶寬消耗)

POST

在頁(yè)面里<form> 標(biāo)簽會(huì)定義一個(gè)表單。點(diǎn)擊其中的submit元素會(huì)發(fā)出一個(gè)POST請(qǐng)求讓服務(wù)器做一件事。這件事往往是有副作用的,不冪等的。

不冪等也就意味著不能隨意多次執(zhí)行,因此也就不能緩存。

比如通過(guò)POST下一個(gè)單,服務(wù)器創(chuàng)建了新的訂單,然后返回訂單成功的界面。這個(gè)頁(yè)面不能被緩存。試想一下,如果POST請(qǐng)求被瀏覽器緩存了,那么下單請(qǐng)求就可以不向服務(wù)器發(fā)請(qǐng)求,而直接返回本地緩存的“下單成功界面”,卻又沒(méi)有真的在服務(wù)器下單,那是一件多么滑稽的事情。

因?yàn)镻OST可能有副作用,所以瀏覽器實(shí)現(xiàn)為不能把POST請(qǐng)求保存為書簽。想想,如果點(diǎn)一下書簽就下一個(gè)單,是不是很恐怖?。

此外如果嘗試重新執(zhí)行POST請(qǐng)求,瀏覽器也會(huì)彈一個(gè)框提示下這個(gè)刷新可能會(huì)有副作用,詢問(wèn)要不要繼續(xù)。

在chrome中嘗試重新提交表單會(huì)彈框

當(dāng)然,服務(wù)器的開(kāi)發(fā)者完全可以把GET實(shí)現(xiàn)為有副作用;把POST實(shí)現(xiàn)為沒(méi)有副作用。

只不過(guò)這和瀏覽器的預(yù)期不符。把GET實(shí)現(xiàn)為有副作用是個(gè)很可怕的事情。我依稀記得很久之前百度貼吧有一個(gè)因?yàn)槭褂肎ET請(qǐng)求可以修改管理員的權(quán)限而造成的安全漏洞。反過(guò)來(lái),把沒(méi)有副作用的請(qǐng)求用POST實(shí)現(xiàn),瀏覽器該彈框還是會(huì)彈框,對(duì)用戶體驗(yàn)好處改善不大。

但是后邊可以看到,將HTTP POST作為接口的形式使用時(shí),就沒(méi)有這種彈框了。

于是把一個(gè)POST請(qǐng)求實(shí)現(xiàn)為冪等就有實(shí)際的意義:POST冪等能讓很多業(yè)務(wù)的前后端交互更順暢,以及避免一些因?yàn)榍岸薭ug,觸控失誤等帶來(lái)的重復(fù)提交。將一個(gè)有副作用的操作實(shí)現(xiàn)為冪等必須得從業(yè)務(wù)上能定義出怎么就算是“重復(fù)”。如提交數(shù)據(jù)中增加一個(gè)dedupKey在一個(gè)交易會(huì)話中有效,或者利用提交的數(shù)據(jù)里可以天然當(dāng)dedupKey的字段。這樣萬(wàn)一用戶強(qiáng)行重復(fù)提交,服務(wù)器端可以做一次防護(hù)。

GET和POST攜帶數(shù)據(jù)的格式也有區(qū)別。

當(dāng)瀏覽器發(fā)出一個(gè)GET請(qǐng)求時(shí),就意味著要么是用戶自己在瀏覽器的地址欄輸入,要不就是點(diǎn)擊了htmla標(biāo)簽。所以其實(shí)并不是 GET 只能用 url,而是瀏覽器直接發(fā)出的 GET 只能由一個(gè)url觸發(fā)。GET上要在url之外帶一些參數(shù)就只能依靠url上附帶querystring。但是HTTP協(xié)議本身并沒(méi)有這個(gè)限制。

瀏覽器的POST請(qǐng)求都來(lái)自表單提交。每次提交,表單的數(shù)據(jù)被瀏覽器用編碼到HTTP請(qǐng)求的body里。

瀏覽器發(fā)出的POST請(qǐng)求的body主要有有兩種格式:

  • 一種是application/x-www-form-urlencoded 用來(lái)傳輸簡(jiǎn)單的數(shù)據(jù),大概就是key1=value1&key2=value2這樣的格式。
  • 另外一種是傳文件,會(huì)采用multipart/form-data格式。

采用后者是因?yàn)?/span>application/x-www-form-urlencoded的編碼方式對(duì)于文件這種二進(jìn)制的數(shù)據(jù)非常低效。

瀏覽器在POST一個(gè)表單時(shí),url上也可以帶參數(shù),只要<form action="url">里的url帶querystring就行。只不過(guò)表單里面的那些用<input> 等標(biāo)簽經(jīng)過(guò)用戶操作產(chǎn)生的數(shù)據(jù)都在會(huì)在body里。

因此我們一般會(huì)泛泛的說(shuō)“GET請(qǐng)求沒(méi)有body,只有url,請(qǐng)求數(shù)據(jù)放在url的querystring中;POST請(qǐng)求的數(shù)據(jù)在body中“。但這種情況僅限于瀏覽器發(fā)請(qǐng)求的場(chǎng)景。

接口中的GET和POST 

這里是指通過(guò)瀏覽器的Ajax api,或者 iOS/Android App 的 http client,java的 commons-httpclient/okhttp或者是curlpostman之類的工具發(fā)出來(lái)的GET和POST請(qǐng)求。

此時(shí)GET/POST不光能用在前端和后端的交互中,還能用在后端各個(gè)子服務(wù)的調(diào)用中(即當(dāng)一種RPC協(xié)議使用)。

盡管RPC有很多協(xié)議,比如thrift,grpc,但是http本身已經(jīng)有大量的現(xiàn)成的支持工具可以使用,并且對(duì)人類很友好,容易debug。HTTP協(xié)議在微服務(wù)中的使用是相當(dāng)普遍的。

當(dāng)用HTTP實(shí)現(xiàn)接口發(fā)送請(qǐng)求時(shí),就沒(méi)有瀏覽器中那么多限制了,只要是符合HTTP格式的就可以發(fā)。HTTP請(qǐng)求的格式,大概是這樣的一個(gè)字符串(為了美觀,我在\r\n后都換行一下):

<METHOD> <URL> HTTP/1.1\r\n
<Header1>: <HeaderValue1>\r\n
<Header2>: <HeaderValue2>\r\n
...
<HeaderN>: <HeaderValueN>\r\n
\r\n
<Body Data....>

其中的“<METHOD>"可以是GET也可以是POST,或者其他的HTTP Method,如PUT、DELETE、OPTION……。

從協(xié)議本身看,并沒(méi)有什么限制說(shuō)GET一定不能沒(méi)有body,POST就一定不能把參放到<URL>querystring上。

因此其實(shí)可以更加自由的去利用格式。

比如Elastic Search_search api就用了帶body的GET;也可以自己開(kāi)發(fā)接口讓POST一半的參數(shù)放在url的querystring里,另外一半放body里;你甚至還可以讓所有的參數(shù)都放Header里——可以做各種各樣的定制,只要請(qǐng)求的客戶端和服務(wù)器端能夠約定好。

當(dāng)然,太自由也帶來(lái)了另一種麻煩,開(kāi)發(fā)人員不得不每次討論確定參數(shù)是放url的path里,querystring里,body里,header里這種問(wèn)題,太低效了。

于是就有了一些列接口規(guī)范/風(fēng)格。其中名氣最大的當(dāng)屬REST。

REST充分運(yùn)用GET、POST、PUT和DELETE,約定了這4個(gè)接口分別獲取、創(chuàng)建、替換和刪除“資源”,REST最佳實(shí)踐還推薦在請(qǐng)求體使用json格式。

這樣僅僅通過(guò)看HTTP的method就可以明白接口是什么意思,并且解析格式也得到了統(tǒng)一。

json相對(duì)于x-www-form-urlencoded的優(yōu)勢(shì)在于:

1)可以有嵌套結(jié)構(gòu);

2)可以支持更豐富的數(shù)據(jù)類型。

通過(guò)一些框架,json可以直接被服務(wù)器代碼映射為業(yè)務(wù)實(shí)體。用起來(lái)十分方便。但是如果是寫一個(gè)接口支持上傳文件,那么還是multipart/form-data格式更合適。

REST中GET和POST不是隨便用的。

在REST中, 【GET】 + 【資源定位符】被專用于獲取資源或者資源列表,比如:

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

與瀏覽器的場(chǎng)景類似,REST GET也不應(yīng)該有副作用,于是可以被反復(fù)無(wú)腦調(diào)用。瀏覽器(包括瀏覽器的Ajax請(qǐng)求)對(duì)于這種GET也可以實(shí)現(xiàn)緩存(如果服務(wù)器端提示了明確需要Caching);但是如果用非瀏覽器,有沒(méi)有緩存完全看客戶端的實(shí)現(xiàn)了。當(dāng)然,也可以從整個(gè)App角度,也可以完全繞開(kāi)瀏覽器的緩存機(jī)制,實(shí)現(xiàn)一套業(yè)務(wù)定制的緩存框架。

okhttp中控制Cache的類

REST 【POST】+ 【資源定位符】則用于“創(chuàng)建一個(gè)資源”,比如:

POST http://foo.com/books
{
  "title": "大寬寬的碎碎念",
  "author": "大寬寬",
  ...
}

這里你就能留意到瀏覽器中用來(lái)實(shí)現(xiàn)表單提交的POST,和REST里實(shí)現(xiàn)創(chuàng)建資源的POST語(yǔ)義上的不同。

順便講下REST POST和REST PUT的區(qū)別。有些api是使用PUT作為創(chuàng)建資源的Method。PUT與POST的區(qū)別在于,PUT的實(shí)際語(yǔ)義是“replace”replace。REST規(guī)范里提到PUT的請(qǐng)求體應(yīng)該是完整的資源,包括id在內(nèi)。比如上面的創(chuàng)建一本書的api也可以定義為:

PUT http://foo.com/books
{
  "id": "BOOK:affe001bbe0556a",
  "title": "大寬寬的碎碎念",
  "author": "大寬寬",
  ...
}

服務(wù)器應(yīng)該先根據(jù)請(qǐng)求提供的id進(jìn)行查找,如果存在一個(gè)對(duì)應(yīng)id的元素,就用請(qǐng)求中的數(shù)據(jù)整體替換已經(jīng)存在的資源;如果沒(méi)有,就用“把這個(gè)id對(duì)應(yīng)的資源從【空】替換為【請(qǐng)求數(shù)據(jù)】“。直觀看起來(lái)就是“創(chuàng)建”了。

與PUT相比,POST更像是一個(gè)“factory”,通過(guò)一組必要的數(shù)據(jù)創(chuàng)建出完整的資源。
至于到底用PUT還是POST創(chuàng)建資源,完全要看是不是提前可以知道資源所有的數(shù)據(jù)(尤其是id),以及是不是完整替換。比如對(duì)于AWS S3這樣的對(duì)象存儲(chǔ)服務(wù),當(dāng)想上傳一個(gè)新資源時(shí),其id就是“ObjectName”可以提前知道;同時(shí)這個(gè)api也總是完整的replace整個(gè)資源。這時(shí)的api用PUT的語(yǔ)義更合適;而對(duì)于那些id是服務(wù)器端自動(dòng)生成的場(chǎng)景,POST更合適一些。

有點(diǎn)跑題,就此打住。

AWS S3 創(chuàng)建一個(gè)Object的API描述

回到接口這個(gè)主題,上面僅僅粗略介紹了REST的情況。但是現(xiàn)實(shí)中總是有REST的變體,也可能用非REST的協(xié)議(比如JSON-RPC、SOAP等),每種情況中的GET和POST又會(huì)有所不同。

關(guān)于安全性 

我們常聽(tīng)到GET不如POST安全,因?yàn)镻OST用body傳輸數(shù)據(jù),而GET用url傳輸,更加容易看到。但是從攻擊的角度,無(wú)論是GET還是POST都不夠安全,因?yàn)镠TTP本身是明文協(xié)議每個(gè)HTTP請(qǐng)求和返回的每個(gè)byte都會(huì)在網(wǎng)絡(luò)上明文傳播,不管是url,header還是body。這完全不是一個(gè)“是否容易在瀏覽器地址欄上看到“的問(wèn)題。

為了避免傳輸中數(shù)據(jù)被竊取,必須做從客戶端到服務(wù)器的端端加密。業(yè)界的通行做法就是https——即用SSL協(xié)議協(xié)商出的密鑰加密明文的http數(shù)據(jù)。這個(gè)加密的協(xié)議和HTTP協(xié)議本身相互獨(dú)立。如果是利用HTTP開(kāi)發(fā)公網(wǎng)的站點(diǎn)/App,要保證安全,https是最最基本的要求。

當(dāng)然,端端加密并不一定非得用https。比如國(guó)內(nèi)金融領(lǐng)域都會(huì)用私有網(wǎng)絡(luò),也有GB的加密協(xié)議SM系列。但除了軍隊(duì),金融等特殊機(jī)構(gòu)之外,似乎并沒(méi)有必要自己發(fā)明一套類似于ssl的協(xié)議。

回到HTTP本身,的確GET請(qǐng)求的參數(shù)更傾向于放在url上,因此有更多機(jī)會(huì)被泄漏。比如攜帶私密信息的url會(huì)展示在地址欄上,還可以分享給第三方,就非常不安全了。此外,從客戶端到服務(wù)器端,有大量的中間節(jié)點(diǎn),包括網(wǎng)關(guān),代理等。他們的access log通常會(huì)輸出完整的url,比如nginx的默認(rèn)access log就是如此。如果url上攜帶敏感數(shù)據(jù),就會(huì)被記錄下來(lái)。但請(qǐng)注意,就算私密數(shù)據(jù)在body里,也是可以被記錄下來(lái)的,因此如果請(qǐng)求要經(jīng)過(guò)不信任的公網(wǎng),避免泄密的唯一手段就是https。這里說(shuō)的“避免access log泄漏“僅僅是指避免可信區(qū)域中的http代理的默認(rèn)行為帶來(lái)的安全隱患。比如你是不太希望讓自己公司的運(yùn)維同學(xué)從公司主網(wǎng)關(guān)的log里看到用戶的密碼吧。

另外,上面講過(guò),如果是用作接口,GET實(shí)際上也可以帶body,POST也可以在url上攜帶數(shù)據(jù)。所以實(shí)際上到底怎么傳輸私密數(shù)據(jù),要看具體場(chǎng)景具體分析。當(dāng)然,絕大多數(shù)場(chǎng)景,用POST + body里寫私密數(shù)據(jù)是合理的選擇。一個(gè)典型的例子就是“登錄”:

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

安全是一個(gè)巨大的主題,有由很多細(xì)節(jié)組成的一個(gè)完備體系,比如返回私密數(shù)據(jù)的mask,XSS,CSRF,跨域安全,前端加密,釣魚,salt,…… POST和GET在安全這件事上僅僅是個(gè)小角色。因此單獨(dú)討論P(yáng)OST和GET本身哪個(gè)更安全意義并不是太大。只要記得一般情況下,私密數(shù)據(jù)傳輸用POST + body就好。

關(guān)于編碼 

常見(jiàn)的說(shuō)法有,比如GET的參數(shù)只能支持ASCII,而POST能支持任意binary,包括中文。但其實(shí)從上面可以看到,GET和POST實(shí)際上都能用url和body。因此所謂編碼確切地說(shuō)應(yīng)該是http中url用什么編碼,body用什么編碼。

先說(shuō)下url。url只能支持ASCII的說(shuō)法源自于RFC1738

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

實(shí)際上這里規(guī)定的僅僅是一個(gè)ASCII的子集\[a-zA-Z0-9$-\_.+!\*'(),\]。它們是可以“不經(jīng)編碼”在url中使用。比如盡管空格也是ASCII字符,但是不能直接用在url里。

那這個(gè)“編碼”是什么呢?如果有了特殊符號(hào)和中文怎么辦呢?一種叫做percent encoding的編碼方法就是干這個(gè)用的。

這也就是為啥我們偶爾看到url里有一坨%和16位數(shù)字組成的序列。

使用Percent Encoding,即使是binary data,也是可以通過(guò)編碼后放在URL上的。

但要特別注意,這個(gè)編碼方式只管把字符轉(zhuǎn)換成URL可用字符,但是卻不管字符集編碼(比如中文到底是用UTF8還是GBK)這塊早期一直都相當(dāng)亂,也沒(méi)有什么統(tǒng)一規(guī)范。比如有時(shí)跟網(wǎng)頁(yè)編碼一樣,有的是操作系統(tǒng)的編碼一樣。最要命的是瀏覽器的地址欄是不受開(kāi)發(fā)者控制的。這樣,對(duì)于同樣一個(gè)帶中文的url,如果有的瀏覽器一定要用GBK(比如老的IE8),有的一定要用UTF8(比如chrome)。后端就可能認(rèn)不出來(lái)。對(duì)此常用的辦法是避免讓用戶輸入這種帶中文的url。如果有這種形式的請(qǐng)求,都改成用戶界面上輸入,然后通過(guò)Ajax發(fā)出的辦法。Ajax發(fā)出的編碼形式開(kāi)發(fā)者是可以100%控制的。

不過(guò)目前基本上utf8已經(jīng)大一統(tǒng)了。現(xiàn)在的開(kāi)發(fā)者除非是被國(guó)家規(guī)定要求一定要用GB系列編碼的場(chǎng)景,基本上不會(huì)再遇到這類問(wèn)題了。

順便說(shuō)一句,盡管在瀏覽器地址欄可以看到中文。但這種url在發(fā)送請(qǐng)求過(guò)程中,瀏覽器會(huì)把中文用字符編碼+Percent Encode翻譯為真正的url,再發(fā)給服務(wù)器。瀏覽器地址欄里的中文只是想讓用戶體驗(yàn)好些而已。

再討論下Body。

HTTP Body相對(duì)好些,因?yàn)橛袀€(gè)Content-Type來(lái)比較明確的定義。比如:

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

這里Content-Type會(huì)同時(shí)定義請(qǐng)求body的格式(application/x-www-form-urlencoded)和字符編碼(UTF-8)。

所以body和url都可以提交中文數(shù)據(jù)給后端,但是POST的規(guī)范好一些,相對(duì)不容易出錯(cuò),容易讓開(kāi)發(fā)者安心。對(duì)于GET+url的情況,只要不涉及到在老舊瀏覽器的地址欄輸入url,也不會(huì)有什么太大的問(wèn)題。

回到POST,瀏覽器直接發(fā)出的POST請(qǐng)求就是表單提交,而表單提交只有application/x-www-form-urlencoded針對(duì)簡(jiǎn)單的key-value場(chǎng)景;和multipart/form-data,針對(duì)只有文件提交,或者同時(shí)有文件和key-value的混合提交表單的場(chǎng)景。

如果是Ajax或者其他HTTP Client發(fā)出去的POST請(qǐng)求,其body格式就非常自由了,常用的有json,xml,文本,csv……甚至是你自己發(fā)明的格式。只要前后端能約定好即可。

瀏覽器的POST需要發(fā)兩個(gè)請(qǐng)求嗎? 

上文中的"HTTP 格式“清楚的顯示了HTTP請(qǐng)求可以被大致分為“請(qǐng)求頭”和“請(qǐng)求體”兩個(gè)部分。使用HTTP時(shí)大家會(huì)有一個(gè)約定,即所有的“控制類”信息應(yīng)該放在請(qǐng)求頭中,具體的數(shù)據(jù)放在請(qǐng)求體里“。于是服務(wù)器端在解析時(shí),總是會(huì)先完全解析全部的請(qǐng)求頭部。這樣,服務(wù)器端總是希望能夠了解請(qǐng)求的控制信息后,就能決定這個(gè)請(qǐng)求怎么進(jìn)一步處理,是拒絕,還是根據(jù)content-type去調(diào)用相應(yīng)的解析器處理數(shù)據(jù),或者直接用zero copy轉(zhuǎn)發(fā)。

比如在用Java寫服務(wù)時(shí),請(qǐng)求處理代碼總是能從HttpSerlvetRequest里getParameter/Header/url。這些信息都是請(qǐng)求頭里的,框架直接就解析了。而對(duì)于請(qǐng)求體,只提供了一個(gè)inputstream,如果開(kāi)發(fā)人員覺(jué)得應(yīng)該進(jìn)一步處理,就自己去讀取和解析請(qǐng)求體。這就能體現(xiàn)出服務(wù)器端對(duì)請(qǐng)求頭和請(qǐng)求體的不同處理方式。

舉個(gè)實(shí)際的例子,比如寫一個(gè)上傳文件的服務(wù),請(qǐng)求url中包含了文件名稱,請(qǐng)求體中是個(gè)尺寸為幾百兆的壓縮二進(jìn)制流。服務(wù)器端接收到請(qǐng)求后,就可以先拿到請(qǐng)求頭部,查看用戶是不是有權(quán)限上傳,文件名是不是符合規(guī)范等。如果不符合,就不再處理請(qǐng)求體的數(shù)據(jù)了,直接丟棄。而不用等到整個(gè)請(qǐng)求都處理完了再拒絕。

為了進(jìn)一步優(yōu)化,客戶端可以利用HTTP的Continued協(xié)議來(lái)這樣做:客戶端總是先發(fā)送所有請(qǐng)求頭給服務(wù)器,讓服務(wù)器校驗(yàn)。如果通過(guò)了,服務(wù)器回復(fù)“100 - Continue”,客戶端再把剩下的數(shù)據(jù)發(fā)給服務(wù)器。如果請(qǐng)求被拒了,服務(wù)器就回復(fù)個(gè)400之類的錯(cuò)誤,這個(gè)交互就終止了。這樣,就可以避免浪費(fèi)帶寬傳請(qǐng)求體。但是代價(jià)就是會(huì)多一次Round Trip。如果剛好請(qǐng)求體的數(shù)據(jù)也不多,那么一次性全部發(fā)給服務(wù)器可能反而更好。

基于此,客戶端就能做一些優(yōu)化,比如內(nèi)部設(shè)定一次POST的數(shù)據(jù)超過(guò)1KB就先只發(fā)“請(qǐng)求頭”,否則就一次性全發(fā)。客戶端甚至還可以做一些Adaptive的策略,統(tǒng)計(jì)發(fā)送成功率,如果成功率很高,就總是全部發(fā)等等。不同瀏覽器,不同的客戶端(curl,postman)可以有各自的不同的方案。不管怎樣做,優(yōu)化目的總是在提高數(shù)據(jù)吞吐和降低帶寬浪費(fèi)上做一個(gè)折衷。

因此到底是發(fā)一次還是發(fā)N次,客戶端可以很靈活的決定。因?yàn)椴还茉趺窗l(fā)都是符合HTTP協(xié)議的,因此我們應(yīng)該視為這種優(yōu)化是一種實(shí)現(xiàn)細(xì)節(jié),而不用扯到GET和POST本身的區(qū)別上。更不要當(dāng)個(gè)什么世紀(jì)大發(fā)現(xiàn)。

到底什么算請(qǐng)求體 

看完了上面的內(nèi)容后,讀者也許會(huì)對(duì)“什么是請(qǐng)求體”感到困惑不已,比如x-www-form-endocded編碼的body算不算“請(qǐng)求體”呢?

從HTTP協(xié)議的角度,“請(qǐng)求頭”就是Method + URL(含querystring)+ Headers;再后邊的都是請(qǐng)求體。

但是從業(yè)務(wù)角度,如果你把一次請(qǐng)求立即為一個(gè)調(diào)用的話。比如上面的

POST http://foo.com/books
{
  "title": "大寬寬的碎碎念",
  "author": "大寬寬",
  ...
}

用Java寫大概等價(jià)于

createBook("大寬寬的碎碎念", "大寬寬");

那么這一行函數(shù)名和兩個(gè)參數(shù)都可以看作是一個(gè)請(qǐng)求,不區(qū)分頭和體。即便用HTTP協(xié)議實(shí)現(xiàn),title和author編碼到了HTTP請(qǐng)求體中。Java的HttpServletRequest支持用getParameter方法獲取x-www-url-form-encoded中的數(shù)據(jù),表達(dá)的意思就是“請(qǐng)求“的”參數(shù)“。

對(duì)于HTTP,需要區(qū)分【頭】和【體】,Http Request和Http Response都這么區(qū)分。Http這么干主要用作

  • 對(duì)于HTTP代理
    • 支持轉(zhuǎn)發(fā)規(guī)則,比如nginx先要解析請(qǐng)求頭,拿到URL和Header才能決定怎么做(轉(zhuǎn)發(fā)proxy_pass,重定向redirect,rewrite后重新判斷……)
    • 需要用請(qǐng)求頭的信息記錄log。盡管請(qǐng)求體里的數(shù)據(jù)也可以記錄,但一般只記錄請(qǐng)求頭的部分?jǐn)?shù)據(jù)。
    • 如果代理規(guī)則不涉及到請(qǐng)求體,那么請(qǐng)求體的字節(jié)流可以直接轉(zhuǎn)發(fā),無(wú)需解析。而解析需要額外的內(nèi)存和CPU。(具體的轉(zhuǎn)發(fā)形式要看是否是chunked編碼,代理buffering是否開(kāi)啟等)
    • ……
  • 對(duì)于HTTP服務(wù)器
    • 可以通過(guò)請(qǐng)求頭進(jìn)行ACL控制,比如看看Athorization頭里的數(shù)據(jù)是否能讓認(rèn)證通過(guò)
    • 可以做一些攔截,比如看到Content-Length里的數(shù)太大,或者Content-Type自己不支持,或者Accept要求的格式自己無(wú)法處理,就直接返回失敗了。
    • 如果body的數(shù)據(jù)很大,利用Stream API,可以方便支持一塊一塊的處理數(shù)據(jù),而不是一次性全部讀取出來(lái)再操作,以至于占用大量?jī)?nèi)存。
    • ……

但從高一級(jí)的業(yè)務(wù)角度,我們?cè)谝獾钠鋵?shí)是【請(qǐng)求】和【返回】。當(dāng)我們?cè)谡f(shuō)“請(qǐng)求頭”這三個(gè)字時(shí),也許實(shí)際的意思是【請(qǐng)求】。而用HTTP實(shí)現(xiàn)【請(qǐng)求】時(shí),可能僅僅用到【HTTP的請(qǐng)求頭】(比如大部分GET請(qǐng)求),也可能是【HTTP請(qǐng)求頭】+【HTTP請(qǐng)求體】(比如用POST實(shí)現(xiàn)一次下單)。

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

關(guān)于URL的長(zhǎng)度 

因?yàn)樯厦嫣岬搅瞬徽撌荊ET和POST都可以使用URL傳遞數(shù)據(jù),所以我們常說(shuō)的“GET數(shù)據(jù)有長(zhǎng)度限制“其實(shí)是指”URL的長(zhǎng)度限制“。

HTTP協(xié)議本身對(duì)URL長(zhǎng)度并沒(méi)有做任何規(guī)定。實(shí)際的限制是由客戶端/瀏覽器以及服務(wù)器端決定的。

先說(shuō)瀏覽器。不同瀏覽器不太一樣。比如我們常說(shuō)的2048個(gè)字符的限制,其實(shí)是IE8的限制。并且原始文檔的說(shuō)的其實(shí)是“URL的最大長(zhǎng)度是2083個(gè)字符,path的部分最長(zhǎng)是2048個(gè)字符“。見(jiàn)https://support.microsoft.com/en-us/help/208427/maximum-url-length-is-2-083-characters-in-internet-explorer。IE8之后的IE URL限制我沒(méi)有查到明確的文檔,但有些資料稱IE 11的地址欄只能輸入法2047個(gè)字符,但是允許用戶點(diǎn)擊html里的超長(zhǎng)URL。我沒(méi)實(shí)驗(yàn),哪位有興趣可以試試。

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

Safari,F(xiàn)irefox等瀏覽器也有自己的限制,但都比IE大的多,這里就不挨個(gè)列出了。

然而新的IE已經(jīng)開(kāi)始使用Chrome的內(nèi)核了,也就意味著“瀏覽器端URL的長(zhǎng)度限制為2048字符”這種說(shuō)法會(huì)慢慢成為歷史。

其他的客戶端,比如Java的,js的http client大多數(shù)也并沒(méi)有限制URL最大有多長(zhǎng)。

除了瀏覽器,服務(wù)器這邊也有限制,比如apache的LimieRequestLine指令。

apache實(shí)際上限制的是HTTP請(qǐng)求第一行“Request Line“的長(zhǎng)度,即那一行。

?再比如nginx用large_client_header_buffers 指令來(lái)分配請(qǐng)求頭中的很長(zhǎng)數(shù)據(jù)的buffer。這個(gè)buffer可以用來(lái)處理url,header value等。

Tomcat的限制是web.xml里maxHttpHeaderSize來(lái)設(shè)置的,控制的是整個(gè)“請(qǐng)求頭”的總長(zhǎng)度。

為啥要限制呢?如果寫過(guò)解析一段字符串的代碼就能明白,解析的時(shí)候要分配內(nèi)存。對(duì)于一個(gè)字節(jié)流的解析,必須分配buffer來(lái)保存所有要存儲(chǔ)的數(shù)據(jù)。而URL這種東西必須當(dāng)作一個(gè)整體看待,無(wú)法一塊一塊處理,于是就處理一個(gè)請(qǐng)求時(shí)必須分配一整塊足夠大的內(nèi)存。如果URL太長(zhǎng),而并發(fā)又很高,就容易擠爆服務(wù)器的內(nèi)存;同時(shí),超長(zhǎng)URL的好處并不多,我也只有處理老系統(tǒng)的URL時(shí)因?yàn)椴桓遗鲈瓉?lái)的邏輯,又得追加更多數(shù)據(jù),才會(huì)使用超長(zhǎng)URL。

對(duì)于開(kāi)發(fā)者來(lái)說(shuō),使用超長(zhǎng)的URL完全是給自己埋坑,需要同時(shí)要考慮前后端,以及中間代理每一個(gè)環(huán)節(jié)的配置。此外,超長(zhǎng)URL會(huì)影響搜索引擎的爬蟲,有些爬蟲甚至無(wú)法處理超過(guò)2000個(gè)字節(jié)的URL。這也就意味著這些URL無(wú)法被搜到,坑爹啊。

其實(shí)并沒(méi)有太大必要弄清楚精確的URL最大長(zhǎng)度限制。我個(gè)人的經(jīng)驗(yàn)是,只要某個(gè)要開(kāi)發(fā)的資源/api的URL長(zhǎng)度有可能達(dá)到2000個(gè)bytes以上,就必須使用body來(lái)傳輸數(shù)據(jù),除非有特殊情況。至于到底是GET + body還是POST + body可以看情況決定。

留意,1個(gè)漢字字符經(jīng)過(guò)UTF8編碼 + percent encoding后會(huì)變成9個(gè)字節(jié),別算錯(cuò)哦。

總結(jié) 

上面講了一大堆,是希望讀者不要死記硬背GET和POST的區(qū)別,而是能從更廣的層面去看待和思考這個(gè)問(wèn)題。

最后,協(xié)議都是人定的。只要客戶端和服務(wù)器能彼此認(rèn)同,就能工作。在常規(guī)的情況下,用符合規(guī)范的方式去實(shí)現(xiàn)系統(tǒng)可以減少很多工作量——大家都約定好了,就不要折騰了。但是,總會(huì)有一些情況用常規(guī)規(guī)范不合適,不滿足需求。這時(shí)思路也不能被規(guī)范限制死,更不要死摳RFC。這些規(guī)范也許不能處理你遇到的特殊問(wèn)題。比如:

  • Elastic Search的_search接口使用GET,卻用body來(lái)表達(dá)查詢,因?yàn)椴樵兒軓?fù)雜,用querystring很麻煩,必須用json格式才舒服,在請(qǐng)求體用json編碼更加容易,不用折騰percent encoding。
  • 用POST寫一個(gè)接口下單時(shí)可能也要考慮冪等,因?yàn)榍岸丝赡軐?shí)現(xiàn)“下單按鍵”有bug,造成用戶一次點(diǎn)擊發(fā)出N個(gè)請(qǐng)求。你不能說(shuō)因?yàn)镻OST by design應(yīng)該是不冪等就不管了。

協(xié)議是死的,人是活的。遇到實(shí)際的問(wèn)題時(shí)靈活的運(yùn)用手上的工具滿足需求就好。

最后 

原文地址:https://www.zhihu.com/question/28586791/answer/767316172


該文章在 2025/3/6 11:15:37 編輯過(guò)
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開(kāi)發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved