iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
Software Development

程式設計沒有告訴你的事:30 天破解每一個 Why系列 第 19

POST 傳來的資料到底藏在哪裡?Request Body 又是什麼?

  • 分享至 

  • xImage
  •  

昨天研究 Query String 時,我們看到 Browser 可以把一些資料直接放在網址裡。

例如:

/search?q=book

可以理解成:

Path = /search

Query = q → book

所以如果只是很簡單的搜尋條件、頁碼或篩選資訊,放在網址後面似乎就可以了。

但這時我突然想到一個問題。

平常填寫登入表單時,可能會輸入:

username
password

建立資料時,也可能會傳:

name
email
age

如果今天傳的是一大段 JSON:

{
"name": "Andy",
"age": 20
}

這些資料總不可能全部塞在網址後面吧?

那它們到底放在哪裡?

所以今天想理解的問題就是:

POST 傳來的資料到底藏在哪裡?

Request Body 又是什麼?

我原本以為……

以前寫 Web API 時,我很習慣看到類似:

POST /users

然後 Controller 或 Handler 就可以直接取得使用者傳來的資料。

尤其使用 ASP.NET Core 時,可能只需要定義一個 Model,就可以很自然地取得 JSON 裡的內容。

所以以前很容易覺得:

Client 傳 JSON

Server 得到 C# Object

中間好像沒有什麼特別的事情。

但現在自己從 TCP 和 HTTP 開始拆之後,就知道事情不可能這麼簡單。

就像 Request.Path 不是 Browser 直接傳來的一個 C# Property 一樣,

Request Body 也不是 Browser 直接傳來一個 C# Object。

Browser 真正傳過來的仍然只是符合 HTTP 格式的資料。

Framework 必須自己知道:

Body 從哪裡開始?
Body 有多長?
Body 是什麼格式?
最後要怎麼把它轉成程式真正能使用的資料?

Request Body 到底在哪裡?

前面 Day11 第一次看到 HTTP Request 時,我們已經知道 Request 大致上可以理解成:

Request Line

Headers

空白行

Body(不一定有)

例如一個很簡化的 POST Request 可能長得像:

POST /users HTTP/1.1
Host: 127.0.0.1:5000
Content-Type: application/json
Content-Length: ...

{"name":"Andy"}

如果把它拆開:

POST /users HTTP/1.1

Request Line

Host: ...
Content-Type: ...
Content-Length: ...

Headers

空白行

代表 Headers 結束

{"name":"Andy"}

Body

所以 Body 並不是另外偷偷傳一份資料。

它本來就是 HTTP Request 的其中一部分。

只是它位在 Headers 後面的空白行之後。

GET 和 POST 的差別就是有沒有 Body 嗎?

這裡也很容易產生誤解。

看到 GET 常常只有:

GET /hello HTTP/1.1

而 POST 常常帶著 Body,就可能會直覺認為:

GET = 沒有 Body
POST = 有 Body

但這樣理解太簡化了。

比較好的方式是:

HTTP Method 描述這次 Request 想做什麼,而 Body 則是 Request 可以攜帶的一部分資料。

在實際 Web 開發中,POST、PUT、PATCH 等 Method 很常使用 Body 傳送資料,所以我們會特別容易把 POST 和 Body 聯想到一起。

今天先把重點放在最常見的情況:

POST

Request Body

傳送要建立或處理的資料

例如:

POST /users

Body

{"name":"Andy"}

為什麼不全部放在 Query String?

假設我要建立一個使用者。

如果全部放網址:

/users?name=Andy&age=20&email=...

資料少的時候可能看起來還可以。

但如果資料越來越多:

姓名
年齡
Email
地址
設定
其他欄位

網址就會變得很長。

如果資料本身還有比較複雜的結構,例如:

{
"name": "Andy",
"address": {
"city": "Taipei",
"district": "Xinzhuang"
}
}

用 Query String 表達就會越來越麻煩。

所以 HTTP Request 提供 Body,讓 Client 可以在 Request 裡另外攜帶內容。

可以先簡單理解成:

Path
→ 我要對哪個資源做事情

Query
→ 這次 Request 額外帶的一些條件

Body
→ 這次 Request 真正要傳送的一份內容

當然,真正 HTTP 的使用情境比這更彈性,但目前用這個方式理解最清楚。

Content-Type 為什麼突然變重要?

假設 Body 是:

{"name":"Andy"}

Server 收到後只看到一串 bytes。

那它怎麼知道這是一段 JSON?

靠的就是 Header 裡的:

Content-Type

例如:

Content-Type: application/json

就是在告訴 Server:

後面的內容要按照 JSON 的形式理解。

如果是一般文字,可能會看到:

Content-Type: text/plain

如果是 HTML:

Content-Type: text/html

所以 Day17 學過的 Headers 到今天開始真的變得很重要。

以前看到:

Content-Type

可能只是覺得:

這是一個 Header。

現在才知道它可能直接影響 Framework:

要怎麼理解 Body。

整個概念可以整理成:

Request Body

先看 Content-Type

決定這份資料是什麼格式

再進一步解析

例如:

Content-Type: application/json

Body 當成 JSON

Content-Type: text/plain

Body 當成純文字

Content-Length 又是什麼?

另外一個很重要的 Header 是:

Content-Length

它表示 Message Body 的長度是多少 bytes。

這件事對我們現在自己做 TCP Server 特別重要。

因為目前程式一直是:

建立一個 buffer

stream.Read(...)

拿到一些 bytes

很容易會產生一個錯覺:

呼叫一次 Read,就等於拿到一整份 HTTP Request。

但其實 TCP 本質上是 byte stream。

一次 Read 到多少資料,並不保證剛好等於完整 HTTP Request 的大小。

這件事情在 Request 很短時可能暫時感覺不到。

例如:

GET /hello HTTP/1.1

資料量很小,第一次 Read 很可能就拿到了我們想看的東西。

但是如果 POST Body 很大:

POST /users HTTP/1.1
...
Content-Length: 5000

後面還有 5000 bytes 的 Body。

那一次 Read 不一定就把整個 Body 收完。

所以 Server 不能單純想:

Read 一次

Request 完成

而是必須知道:

Headers 到哪裡結束?

Body 應該有多長?

目前已經收了多少?

還要不要繼續 Read?

這也讓我第一次開始看見:

真正做一個 HTTP Parser,其實比單純 Split 字串複雜很多。

為什麼 Day17 的 Header 解析很重要?

前幾天可能會覺得:

Headers 那麼多,我真的需要全部理解嗎?

但到了 Request Body 後,就開始看到 Headers 和 Body 其實有直接關係。

例如:

Content-Type

告訴 Framework Body 是什麼格式

Content-Length

告訴 Framework Body 有多少 bytes

所以整個 Request Parsing 其實不是每一部分互不相干。

而是:

Request Line
Headers
Body

會互相影響後續處理方式。

Framework 要怎麼找到 Body?

目前先不進入完整實作,但概念上可以想成:

拿到 Raw HTTP Request

找到 Headers 結束的位置

也就是空白行

空白行後面的資料

Body

HTTP/1.1 常見的 Headers 和 Body 之間會用一個空白行分隔。

我們之前一直看到的:

\r\n\r\n

其實就很重要。

可以想成:

Request Line + Headers

\r\n\r\n

Body

因此未來 HttpRequest.Parse() 除了解析:

Method
Path
Version
Headers
Query

還會需要再往下一步:

Body

最後可能變成:

HttpRequest
├── Method
├── Path
├── Version
├── Headers
├── Query
└── Body

HttpRequest 又變得更完整了

Day16 時,我們想像的 HttpRequest 還很簡單:

Method
Path
Version

Day17:

Method
Path
Version
Headers

Day18:

Method
Path
Version
Headers
Query

到了 Day19:

Method
Path
Version
Headers
Query
Body

這幾天一路拆下來後,我才開始理解:

一個看起來很普通的 Request 物件,背後其實已經替開發者整理了很多 HTTP 細節。

平常只看到:

request.Body

但在它出現以前,Framework 可能已經處理了:

TCP bytes

HTTP 格式

Headers

Body 邊界

Body 長度

最後才建立 request.Body

官方怎麼說?

HTTP Message 可以包含 Header Fields 和 Message Content,而 Content-Length 可以用來表示內容長度。

在 ASP.NET Core 中,HttpRequest 也提供 Body、ContentType、ContentLength 等 Request 相關資訊。

這和我們這幾天逐步建立 HttpRequest 的方向很接近。

只是我們現在做的是最小化的學習版本。

真正的 Framework 還必須處理更多細節,例如不同的 Request Framing、Streaming、大型 Body、安全限制等問題。

所以今天的目的不是直接做出完整 HTTP Server。

而是理解:

為什麼平常一個看似簡單的 request.Body,背後其實需要很多底層工作。

以前的我:

POST

傳資料

Server 得到資料

現在的我:

Client

POST Request

TCP bytes

Request Line

Headers

空白行

Body

Framework Parsing

request.Body

再依照 Content-Type 進一步理解資料

也就是說:

Body 不是突然出現在 Framework 裡的一個變數。

它原本就是 HTTP Request 中的一段資料。

Framework 的工作是找到它、讀完整它,再整理成程式容易使用的形式。

今天最大的發現是:

TCP 的 Read 和 HTTP 的 Request 並不是同一個邊界。

以前很容易把:

stream.Read()

理解成:

讀取一個 Request。

但現在開始知道:

stream.Read()

比較接近:

這次從 TCP Stream 讀到了「一些 bytes」。

至於這些 bytes:

是不是完整 Request?
Headers 有沒有讀完?
Body 有沒有讀完?

是 HTTP 層還需要自己判斷的事情。

所以:

TCP

負責傳 bytes

HTTP

定義這些 bytes 應該怎麼組成 Request

Framework

負責把這些資料解析成我們能使用的物件

這三個層次又更清楚了一點。

今天的答案:

Client 傳送 POST Request 時,要傳送的資料可以放在 HTTP Request Body。

Framework 收到 TCP 資料後,必須先解析 HTTP 結構,找到 Headers 結束的位置,再依照 Request 的 framing 資訊確認 Body,最後才能把資料整理成 request.Body。

與 Mini Web Framework 的關聯

目前我們想像中的 HttpRequest 已經逐漸變成:

HttpRequest
├── Method
├── Path
├── Version
├── Headers
├── Query
└── Body

而整個流程也開始變成:

Browser

TCP bytes

HTTP Request

HttpRequest.Parse()

Method
Path
Headers
Query
Body

Routing

Handler

HttpResponse

Browser

這也代表 Mini Web Framework 開始從:

「可以收到一些 HTTP 文字」

往:

「真的理解一個 HTTP Request」

前進。

今天暫時不實作

今天和前幾天一樣,先把 Request Body 的概念想清楚。

之後補實作時,不會只是單純在 rawRequest 上做一次 Split,就假裝完成 Body Parsing。

因為現在已經知道:

TCP Read 不保證一次取得整個 Request。

所以實作時要更小心處理:

Headers 是否完整
Body 應該有多長
目前已經讀到多少 bytes

這會比前面的 Method、Path、Query Parsing 更接近真正的 HTTP Server 問題。

結尾

今天原本只是想知道:

POST 的資料到底放在哪裡?

結果一路追下去,卻又回到了 TCP。

Request Body 本身其實不難理解。

它就是 HTTP Request 中可以攜帶內容的部分。

真正困難的是:

Server 到底怎麼知道它讀完整了?

做到這裡我才更清楚理解,HTTP Framework 替我們處理的不只是「把 JSON 轉成 Object」。

在那之前,它還必須先把網路收到的 bytes,正確辨認成一個完整的 HTTP Request。

然後才有:

Headers
Body
Content-Type
Content-Length

這些我們平常很習慣使用的資訊。

也就是:

TCP 給 Framework bytes

HTTP 告訴 Framework這些 bytes 的格式

Framework 再把它們整理成 HttpRequest

最後,開發者才可以很簡單地取得 request.Body。

現在 Request 已經慢慢完整了。

我們知道:

Method
Path
Headers
Query
Body

但還有一個問題。

假設 Body 裡收到的是:

{"name":"Andy","age":20}

目前它對 Framework 來說可能仍然只是一串文字或 bytes。

那為什麼平常 ASP.NET Core 可以直接讓我們拿到一個 C# Model?

例如:

User user

裡面直接就有:

Name = "Andy"
Age = 20

中間到底發生了什麼?


上一篇
網址後面的 ?name=Andy,Framework 到底怎麼看懂?
下一篇
JSON 明明只是一串文字,Framework 為什麼可以把它變成 C# 物件?
系列文
程式設計沒有告訴你的事:30 天破解每一個 Why20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言