請(qǐng)求參數(shù)、表單參數(shù)、url參數(shù)、header參數(shù)、Cookie參數(shù)?一文講懂

最近在工作中對(duì) http 的請(qǐng)求參數(shù)解析有了進(jìn)一步的認(rèn)識(shí),寫(xiě)個(gè)小短文記錄一下。

回顧下自己的情況,大概就是:有點(diǎn)點(diǎn)網(wǎng)絡(luò)及編程基礎(chǔ),只需要加深一點(diǎn)點(diǎn)對(duì) HTTP 協(xié)議的理解就能弄明白了。

先分享一個(gè)小故事:我至今仍清晰地記得大三實(shí)習(xí)時(shí)的第一個(gè)工作任務(wù),我需要調(diào)用其他部門(mén)提供的 api 去完成某項(xiàng)業(yè)務(wù)。

那個(gè) api 文檔只告訴了我請(qǐng)求參數(shù)需要傳什么,沒(méi)有提及用什么方式傳,比如這樣:

其實(shí)如果有經(jīng)驗(yàn)的話(huà),直接在請(qǐng)求體或 url 里填參數(shù)試一下就知道了;另一個(gè)是新人有時(shí)候不太敢問(wèn)問(wèn)題,其實(shí)只要向同事確認(rèn)一下就好的。

然而由于當(dāng)時(shí)我掌握的編程知識(shí)有限,只會(huì)用表單提交數(shù)據(jù)。所以當(dāng)我下載完同事安利的 api 調(diào)用調(diào)試工具 postman 后,我就在網(wǎng)上查怎么用 postman 發(fā)送表單數(shù)據(jù),結(jié)果折騰了好久 api 還是沒(méi)能調(diào)通。

當(dāng)天晚上我向老同學(xué)求助,他問(wèn)我上課是不是又睡過(guò)去了?

我說(shuō)你怎么知道?

他說(shuō)當(dāng)然咯,你上課睡覺(jué)不學(xué)習(xí)又不是一天兩天的事情......

后來(lái)他告訴我得好好學(xué)一下 http 協(xié)議,看看可以在協(xié)議的哪些位置放請(qǐng)求參數(shù)。

一個(gè)簡(jiǎn)單的 http 服務(wù)器還原

那么,在正式講解之前,我們先簡(jiǎn)單搭建一個(gè) http 服務(wù)器,阿菌沿用經(jīng)典的 python 版云你好服務(wù)器進(jìn)行講解。

云你好服務(wù)器的代碼很簡(jiǎn)單,服務(wù)器首先會(huì)獲取 name 用戶(hù)名這個(gè)參數(shù),如果用戶(hù)傳了這個(gè)參數(shù),就返回 Hello xxx,xxx 指的是 name 用戶(hù)名;如果用戶(hù)沒(méi)有傳這個(gè)參數(shù)則返回 Hello World

# 云你好服務(wù)源碼
from flask import Flask
from flask import request

app = Flask(__name__)

# 云你好服務(wù) API 接口
@app.get("/api/hello")
def hello():
    # 看用戶(hù)是否傳遞了參數(shù) name
    name = request.args.get("name", "")
    # 如果傳了參數(shù)就向目標(biāo)對(duì)象打招呼,輸出 Hello XXX,否則輸出 Hello World
    return f"Hello {name}" if name else "Hello World"

# 啟動(dòng)云你好服務(wù)
if __name__ == '__main__':
    app.run()

為了快速開(kāi)發(fā)(大伙可以下載一個(gè) python 把這個(gè)代碼跑一下,用自己的語(yǔ)言實(shí)現(xiàn)一個(gè)類(lèi)似的服務(wù)器也是可以的),阿菌這里使用了 flask 框架構(gòu)建后端服務(wù)。

在具體獲取參數(shù)的時(shí)候,我選擇了在 request.args 中獲取參數(shù)。這里提前劇透一下:在 flask 框架中,request.args 指的是從 url 中獲取參數(shù)(不過(guò)這是我們后面講解的內(nèi)容,大家有個(gè)印象就好)

抓包查看 http 報(bào)文

有了 http 服務(wù)器后,我們開(kāi)始深入講解 http 協(xié)議,em...個(gè)人覺(jué)得只在學(xué)校上課看教材學(xué)計(jì)算機(jī)網(wǎng)絡(luò)好像還欠缺了點(diǎn)啥,比較推薦大家下載一個(gè)像 Wireshark 這樣的網(wǎng)絡(luò)抓包軟件,動(dòng)手拆解網(wǎng)絡(luò)包,深入學(xué)習(xí)各種網(wǎng)絡(luò)協(xié)議。抓取網(wǎng)絡(luò)包的示例視頻

為了搞清楚什么是請(qǐng)求參數(shù)、表單參數(shù)、url 參數(shù)、Header 參數(shù)、Cookie 參數(shù),我們先發(fā)一個(gè) http 請(qǐng)求,然后抓取這個(gè)請(qǐng)求的網(wǎng)絡(luò)包,看看一份 http 報(bào)文會(huì)攜帶哪些信息。

呼應(yīng)開(kāi)頭,用戶(hù)阿菌是個(gè)只會(huì)發(fā)表單數(shù)據(jù)的萌新,他使用 postman 向云你好 api 發(fā)送了一個(gè) post 請(qǐng)求:

劇情發(fā)展正常,我們沒(méi)能得到 Hello 阿菌(服務(wù)器會(huì)到 url 中獲取參數(shù),咱們用表單形式提交,所以獲取不到)

由于咱們對(duì)請(qǐng)求體這個(gè)概念比較模糊,接下來(lái)我們重新發(fā)一個(gè)一模一樣的請(qǐng)求,并且通過(guò) Wireshark 抓包看一下:

可以看到強(qiáng)大的 Wireshark 幫助我們把請(qǐng)求抓取了下來(lái),并把整個(gè)網(wǎng)絡(luò)包的鏈路層協(xié)議,IP層協(xié)議,傳輸層協(xié)議,應(yīng)用層協(xié)議全都解析好了。

由于咱們小碼農(nóng)一般都忙于解決應(yīng)用層問(wèn)題,所以我們把目光聚焦于高亮的 Hypertext Transfer Protocol 超文本傳輸協(xié)議,也就是大名鼎鼎的 HTTP 協(xié)議。

首先我們查看一下 HTTP 報(bào)文的完整內(nèi)容:

image.png

可以看到,http 協(xié)議大概是這么組成的:

  • 第一行是請(qǐng)求的方式,比如 GET / POST / DELETE / PUT
  • 請(qǐng)求方式后面跟的是請(qǐng)求的路徑,一般把這個(gè)叫 URI(統(tǒng)一資源標(biāo)識(shí)符)

補(bǔ)充:URL 是統(tǒng)一資源定位符,見(jiàn)名知義,因?yàn)橐ㄎ?,所以要指定協(xié)議甚至是位置,比如這樣:http://localhost:5000/api/hello

  • 請(qǐng)求路徑后面跟的是 HTTP 的版本,比如這里是 HTTP/1.1

完整的第一行如下:

POST /api/hello HTTP/1.1

第二行的 User-Agent 則用于告訴對(duì)方發(fā)起請(qǐng)求的客戶(hù)端是啥,比如咱們用 Postman 發(fā)起的請(qǐng)求,Postman 就會(huì)自動(dòng)把這個(gè)參數(shù)設(shè)置為它自己:

User-Agent: PostmanRuntime/7.28.4

第三行的 Accept 用于告訴對(duì)方我們希望收到什么類(lèi)型的數(shù)據(jù),這里默認(rèn)是能接受所有類(lèi)型的數(shù)據(jù):

Accept: */*

第四行就非常值得留意,Postman-Token 是 Postman 自己傳的參數(shù),這個(gè)我們放到下面講!

Postman-Token: ddd72e1a-0d63-4bad-a18e-22e38a5de3fc

第五行是請(qǐng)求的主機(jī),網(wǎng)絡(luò)上的一個(gè)服務(wù)一般用 ip 加端口作為唯一標(biāo)識(shí):

Host: 127.0.0.1:5000

第六行指定的是咱們請(qǐng)求發(fā)起方可以理解的壓縮方式:

Accept-Encoding: gzip, deflate, br

第七行告訴對(duì)方處理完當(dāng)前請(qǐng)求后不要關(guān)閉連接:

Connection: keep-alive

第八行告訴對(duì)方咱們請(qǐng)求體的內(nèi)容格式,這個(gè)是本文的側(cè)重點(diǎn)啦!比如我們這里指定的是一般瀏覽器的原生表單格式:

Content-Type: application/x-www-form-urlencoded

好了,下面大家要留意了,第九行的 Content-Length 給出的是請(qǐng)求體的大小。

而請(qǐng)求體,會(huì)放在緊跟著的一個(gè)空行之后。比如本請(qǐng)求的請(qǐng)求體內(nèi)容是以 key=value 形式填充的,也就是我們表單參數(shù)的內(nèi)容了:

Content-Length: 23

name=%E9%98%BF%E8%8F%8C

看到這里我們先簡(jiǎn)單小結(jié)一下,想要告訴服務(wù)器我們發(fā)送的是表單數(shù)據(jù),一共需要兩步:

  1. Content-Type 設(shè)置為 application/x-www-form-urlencoded
  2. 在請(qǐng)求體中按照 key=value 的形式填寫(xiě)請(qǐng)求參數(shù)

什么是協(xié)議?進(jìn)一步了解 http

好了,接下來(lái)我們進(jìn)一步講解,大家試想一下,網(wǎng)絡(luò)應(yīng)用,其實(shí)就是端到端的交互,最常見(jiàn)的就是服務(wù)端和客戶(hù)端交互模型:客戶(hù)端發(fā)一些參數(shù)數(shù)據(jù)給服務(wù)端,通過(guò)這些參數(shù)數(shù)據(jù)告訴服務(wù)端它想得到什么或想干什么,服務(wù)端根據(jù)客戶(hù)端傳遞的參數(shù)數(shù)據(jù)作出處理。

傳輸層協(xié)議通過(guò) ip 和端口號(hào)幫我們定位到了具體的服務(wù)應(yīng)用,具體怎么交互是由我們程序員自己定義的。

大概在 30 年前,英國(guó)計(jì)算機(jī)科學(xué)家蒂姆·伯納斯-李定義了原始超級(jí)文本傳輸協(xié)議(HTTP),后續(xù)我們的 web 應(yīng)用大都延續(xù)采用了他定義的這套標(biāo)準(zhǔn),當(dāng)然這套標(biāo)準(zhǔn)也在不斷地進(jìn)行迭代。

許多文獻(xiàn)資料會(huì)把 http 協(xié)議描述得比較晦澀,加上協(xié)議這個(gè)詞聽(tīng)起來(lái)有點(diǎn)高大上,初學(xué)者入門(mén)學(xué)習(xí)的時(shí)候往往感覺(jué)不太友好。

其實(shí)協(xié)議說(shuō)白了就是一種格式,就好比我們寫(xiě)書(shū)信,約定要先頂格寫(xiě)個(gè)敬愛(ài)的 xxx,然后寫(xiě)個(gè)你好,然后換一個(gè)段落再寫(xiě)正文,可能最后還得加上日期署名等等。

我們只要按照格式寫(xiě)信,老師就能一眼看出來(lái)我們?cè)趯?xiě)信;只要我們按協(xié)議格式發(fā)請(qǐng)求數(shù)據(jù),服務(wù)器就能一眼看出來(lái)我們想要得到什么或想干什么。

當(dāng)然,老師是因?yàn)槔显缇蛯W(xué)過(guò)書(shū)信格式,所以他才能看懂書(shū)信格式;服務(wù)端程序也一樣,我們要預(yù)先編寫(xiě)好 http 協(xié)議的解析邏輯,然后我們的服務(wù)器才能根據(jù)解析邏輯去獲取一個(gè) http 請(qǐng)求中的各種東西。

當(dāng)然這個(gè)解析 http 協(xié)議的邏輯不是誰(shuí)都能寫(xiě)出來(lái)的,就算能寫(xiě)出來(lái),也未必寫(xiě)得好,所以我們會(huì)使用厲害的人封裝好的腳手架,比如 java 里的 spring 全套、Go 語(yǔ)言里的 Gin 等等。

回到我們開(kāi)頭給出的示例:

from flask import Flask
from flask import request

app = Flask(__name__)

# 云你好服務(wù) API 接口
@app.get("/api/hello")
def hello():
    # 看用戶(hù)是否傳遞了參數(shù) name
    name = request.args.get("name", "")
    # 如果傳了參數(shù)就向目標(biāo)對(duì)象打招呼,輸出 Hello XXX,否則輸出 Hello World
    return f"Hello {name}" if name else "Hello World"

# 啟動(dòng)云你好服務(wù)
if __name__ == '__main__':
    app.run()

阿菌的示例使用了 python 里的 flask 框架,在處理邏輯中使用了 request.args 獲取請(qǐng)求參數(shù),而 args 封裝的就是框架從 url 中獲取參數(shù)的邏輯。比如我們發(fā)送請(qǐng)求的 url 為:

http://127.0.0.1:5000/api/hello?name=ajun

框架會(huì)幫助我們從 url 中的 ? 后面開(kāi)始截取,然后把 name=ajun 這些參數(shù)存放到 args 里。

切換一下,假設(shè)我們是云你好服務(wù)提供者,我們希望用戶(hù)通過(guò)表單參數(shù)的形式使用云你好服務(wù),我們只要把獲取 name 參數(shù)的方式改成從表單參數(shù)里獲取就可以了,flask 在 request.form 里封裝了表單參數(shù)(關(guān)于框架是怎么在數(shù)行 http 請(qǐng)求中封裝參數(shù)的,大家可以看自己使用的框架的具體邏輯,估計(jì)區(qū)別不大,只是存在一些語(yǔ)言特性上的差異):

@app.post("/api/hello")
def hello():
    # 看用戶(hù)是否傳遞了參數(shù) name
    name = request.form.get("name", "")
    # 如果傳了參數(shù)就向目標(biāo)對(duì)象打招呼,輸出 Hello XXX,否則輸出 Hello World
    return f"Hello {name}" if name else "Hello World"

思考:我們可以在 http 協(xié)議中傳遞什么參數(shù)?

最后,我們解釋本文的標(biāo)題,其實(shí)想要明白各種參數(shù)之間的區(qū)別,我們可以換一個(gè)角度思考:

咱們可以在一份 http 報(bào)文的哪些位置傳遞參數(shù)?

接下來(lái)回顧一下一個(gè) http 請(qǐng)求的內(nèi)容:

POST /api/hello HTTP/1.1
User-Agent: PostmanRuntime/7.28.4
Accept: */*
Postman-Token: fbf75035-a647-46dc-adc0-333751a9399e
Host: 127.0.0.1:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 23

name=%E9%98%BF%E8%8F%8C

大家看,咱們的 http 報(bào)文,也就是基于傳輸層之上的應(yīng)用層報(bào)文,大概就長(zhǎng)上面這樣。

我們考慮兩種情況,第一種情況,我們基于別人已經(jīng)開(kāi)發(fā)好的腳手架開(kāi)發(fā) http 服務(wù)器。

由于框架會(huì)基于 http 協(xié)議進(jìn)行解析,所以框架會(huì)幫助我們解析好請(qǐng)求 url,各種 Header 頭(比如:Cookie 等),以及具體的響應(yīng)內(nèi)容都幫我們封裝解析好了(比如按照 key=value 的方式去讀取請(qǐng)求體)。

那當(dāng)我們開(kāi)發(fā)服務(wù)端的時(shí)候,就可以指定從 url、header、響應(yīng)體中獲取參數(shù)了,比如:

  • url 參數(shù):指的就是 url 中 ? 后面攜帶的 key value 形式參數(shù)
  • header 參數(shù):指的就是各個(gè) header 頭,我們甚至可以自定義 header,比如 Postman-Token 就是 postman 這個(gè)軟件自己攜帶的,我們服務(wù)端如果需要的話(huà)是可以指定獲取這個(gè)參數(shù)的
  • Cookie 參數(shù):其實(shí)就是名字為 Cookie 的請(qǐng)求頭
  • 表單參數(shù):指的就是 Content-Type 為 application/x-www-form-urlencoded 下請(qǐng)求體的內(nèi)容,如果我們的表單需要傳文件,還會(huì)有其他的 Content-Type
  • json 參數(shù):指的就是 Content-Type 為 application/json 下請(qǐng)求體的內(nèi)容(當(dāng)然服務(wù)端可以不根據(jù) Content-Type 直接解析請(qǐng)求體,但按照協(xié)議的規(guī)范工程項(xiàng)目或許會(huì)更好維護(hù))

綜上所述,請(qǐng)求參數(shù)就是對(duì)上面各種類(lèi)型的參數(shù)的一個(gè)總稱(chēng)了。

大家會(huì)發(fā)現(xiàn),不管什么 url 參數(shù)、header 參數(shù)、Cookie 參數(shù)、表單參數(shù),其實(shí)就是換著法兒,按照一定的格式把數(shù)據(jù)放到應(yīng)用層報(bào)文中。關(guān)鍵在于我們的服務(wù)端程序和客戶(hù)端程序按照一種什么樣的約定去傳遞和獲取這些參數(shù)。這就是協(xié)議吧~

還有另一種情況,當(dāng)然這只是開(kāi)玩笑了,比如以后哪位大佬或者哪家企業(yè)定義了一種新的數(shù)據(jù)傳輸標(biāo)準(zhǔn),推廣至全球,比如叫 hppt 協(xié)議,這樣是完全可以自己給各種形式參數(shù)下定義取名字的。這可能就是為啥我們說(shuō)一流的企業(yè)、大佬制定標(biāo)準(zhǔn),接下來(lái)的圍繞標(biāo)準(zhǔn)研發(fā)技術(shù),進(jìn)而是基于技術(shù)賣(mài)產(chǎn)品,最后是圍繞產(chǎn)品提供服務(wù)了。

一旦標(biāo)準(zhǔn)制定了,整個(gè)行業(yè)都圍繞這個(gè)標(biāo)準(zhǔn)轉(zhuǎn)了,而且感覺(jué)影響會(huì)越來(lái)越深遠(yuǎn)......

文章參考-鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容