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

最近在工作中對 http 的請求參數(shù)解析有了進一步的認識,寫個小短文記錄一下。

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

先分享一個小故事:我至今仍清晰地記得大三實習時的第一個工作任務,我需要調用其他部門提供的 api 去完成某項業(yè)務。

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

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

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

當天晚上我向老同學求助,他問我上課是不是又睡過去了?

我說你怎么知道?

他說當然咯,你上課睡覺不學習又不是一天兩天的事情......

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

一個簡單的 http 服務器還原

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

云你好服務器的代碼很簡單,服務器首先會獲取 name 用戶名這個參數(shù),如果用戶傳了這個參數(shù),就返回 Hello xxx,xxx 指的是 name 用戶名;如果用戶沒有傳這個參數(shù)則返回 Hello World

# 云你好服務源碼
from flask import Flask
from flask import request

app = Flask(__name__)

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

# 啟動云你好服務
if __name__ == '__main__':
    app.run()

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

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

抓包查看 http 報文

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

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

呼應開頭,用戶阿菌是個只會發(fā)表單數(shù)據(jù)的萌新,他使用 postman 向云你好 api 發(fā)送了一個 post 請求:

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

由于咱們對請求體這個概念比較模糊,接下來我們重新發(fā)一個一模一樣的請求,并且通過 Wireshark 抓包看一下:

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

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

首先我們查看一下 HTTP 報文的完整內容:

image.png

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

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

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

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

完整的第一行如下:

POST /api/hello HTTP/1.1

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

User-Agent: PostmanRuntime/7.28.4

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

Accept: */*

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

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

第五行是請求的主機,網(wǎng)絡上的一個服務一般用 ip 加端口作為唯一標識:

Host: 127.0.0.1:5000

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

Accept-Encoding: gzip, deflate, br

第七行告訴對方處理完當前請求后不要關閉連接:

Connection: keep-alive

第八行告訴對方咱們請求體的內容格式,這個是本文的側重點啦!比如我們這里指定的是一般瀏覽器的原生表單格式:

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

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

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

Content-Length: 23

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

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

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

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

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

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

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

許多文獻資料會把 http 協(xié)議描述得比較晦澀,加上協(xié)議這個詞聽起來有點高大上,初學者入門學習的時候往往感覺不太友好。

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

我們只要按照格式寫信,老師就能一眼看出來我們在寫信;只要我們按協(xié)議格式發(fā)請求數(shù)據(jù),服務器就能一眼看出來我們想要得到什么或想干什么。

當然,老師是因為老早就學過書信格式,所以他才能看懂書信格式;服務端程序也一樣,我們要預先編寫好 http 協(xié)議的解析邏輯,然后我們的服務器才能根據(jù)解析邏輯去獲取一個 http 請求中的各種東西。

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

回到我們開頭給出的示例:

from flask import Flask
from flask import request

app = Flask(__name__)

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

# 啟動云你好服務
if __name__ == '__main__':
    app.run()

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

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

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

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

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

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

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

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

接下來回顧一下一個 http 請求的內容:

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 報文,也就是基于傳輸層之上的應用層報文,大概就長上面這樣。

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

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

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

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

綜上所述,請求參數(shù)就是對上面各種類型的參數(shù)的一個總稱了。

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

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

一旦標準制定了,整個行業(yè)都圍繞這個標準轉了,而且感覺影響會越來越深遠......

文章參考-鏈接

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容