iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Software Development

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

HTTP Headers 那麼多,Framework 到底怎麼把它們解析出來?

  • 分享至 

  • xImage
  •  

昨天,我第一次建立了自己的 HttpResponse。

原本所有 Response 相關的資訊都散落在 Program.cs 裡,包括 StatusCode、StatusText、ContentType、Body、HTTP 格式、Encoding,以及最後真正把資料送回 Browser 的 NetworkStream.Write()。

整理之後,這些東西開始被集中到 HttpResponse 裡。

做到這裡後,我再回頭看 Request,突然覺得很奇怪。

因為目前 Request 還是靠 Program.cs 自己處理。

Server 收到 Browser 傳來的資料後,會先把 bytes 轉成字串,再取得 Request 的第一行,接著用空白切開,最後才分別取得 Method、Path 和 Version。

也就是說,Response 已經是一個有結構的物件,但 Request 還是一堆散落的字串處理。

那既然 Framework 可以把 Response 包成 HttpResponse,Request 為什麼不能也變成 HttpRequest?

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

Framework 為什麼需要自己的 Request 物件?

💭 我原本以為……

以前使用 ASP.NET Core 時,我很習慣直接取得 Request.Method、Request.Path,甚至可以讀取 Headers、Query 或 Body。

所以以前會有一種感覺,好像 Browser 傳來的 Request 本來就已經是一個整理好的物件。

但前幾天自己從 Socket 開始實作後,才發現其實完全不是這樣。

Browser 真正送來的是符合 HTTP 格式的資料,例如:

GET /products HTTP/1.1
Host: 127.0.0.1:5000
User-Agent: …
Accept: …

而 Server 最開始收到的甚至還不是這些文字,而是一串 bytes。

我們必須先把 bytes 轉成字串,再從原始 Request 中找到第一行,最後透過 Split 把 GET、/products、HTTP/1.1 拆出來。

所以現在才真正理解:

Request.Path 並不是 Browser 直接傳來的一個 C# Property。

它其實是 Framework 在解析原始 HTTP Request 之後,自己整理出來的結果。

回頭看現在的 Program.cs

目前 Program.cs 同時負責很多事情。

它要等待 TCP Connection、讀取 bytes、把 bytes 轉成 string、找出 Request Line、拆解 Request Line、取得 Method、Path、Version,接著還要 Routing,最後再建立 HttpResponse。

昨天我們才把 Response 的工作拆出去。

現在 Request 這邊卻還全部留在 Program.cs 裡。

尤其是現在的解析方式,只有我們自己知道:

parts[0] 代表 Method
parts[1] 代表 Path
parts[2] 代表 Version

如果未來其他地方也想取得 Path,難道每個地方都要重新記得 parts[1] 嗎?

這樣顯然不是一個很好的設計。

🧠 如果有 HttpRequest,會變成什麼?

我想要的是,把原本的:

GET /products HTTP/1.1

經過解析後,整理成一個 HttpRequest。

HttpRequest 裡面至少可以先有三個資訊:

Method = GET
Path = /products
Version = HTTP/1.1

這樣其他程式就不需要知道 Request Line 要怎麼切,也不需要知道 Path 剛好是 parts[1]。

Routing 只需要直接取得:

request.Path

就可以了。

也就是原本的流程:

原始 HTTP

一堆 string 和 array

Routing

可以慢慢變成:

原始 HTTP

HttpRequest

Routing

這樣整個流程就會清楚很多。

HttpRequest 第一版應該包含什麼?

目前其實不用一開始就把所有 Request 功能全部做完。

先從 Day12 已經理解過的三個資訊開始就可以:

Method
Path
Version

例如 Browser 傳來:

GET /hello HTTP/1.1

最後可以整理成:

Method = GET
Path = /hello
Version = HTTP/1.1

這件事情跟 Day12 做過的解析本質上是一樣的。

真正不同的是,以前這些資料只是散落在三個變數裡,現在開始把它們視為「同一個 HTTP Request 的資訊」。

那 Parsing 應該放在哪裡?

如果建立了 HttpRequest,但 Program.cs 還是自己負責所有 Split,那其實也只完成一半。

比較理想的做法應該是:

raw HTTP Request

HttpRequest.Parse(…)

HttpRequest

也就是把原始 Request 交給 HttpRequest 自己解析。

Program.cs 不需要知道第一行怎麼取得、不需要知道空白怎麼切,也不需要知道哪個 index 代表 Path。

它只需要知道:

把原始 Request 交出去,最後拿回一個整理好的 HttpRequest。

為什麼 Parse() 可以是 static?

之後實作時,我預計會使用:

public static HttpRequest Parse(string rawRequest)

這裡的 static 代表這個方法屬於 HttpRequest 這個 Type,而不是某一個已經存在的 HttpRequest 物件。

所以可以直接使用:

HttpRequest.Parse(rawRequest)

這個設計放在目前的情境很合理。

因為一開始我們手上只有 rawRequest,還沒有一個 HttpRequest 物件。

流程會是:

現在還沒有 HttpRequest

手上只有 rawRequest

呼叫 HttpRequest.Parse(…)

建立出 HttpRequest

所以 Parse() 很適合當成「從原始 HTTP 資料建立 Request 物件」的入口。

📚 官方怎麼說?

Microsoft ASP.NET Core 中本身就有 HttpRequest。

HttpRequest 代表一個 HTTP Request,裡面可以取得 Method、Path、Headers、Host 等資訊。

真正的 ASP.NET Core HttpRequest 當然遠遠比我們現在準備做的版本完整。

它還包含 Headers、Host、Query、Scheme、Body、Cookies、ContentType、ContentLength 等資訊。

所以今天我們建立自己的 HttpRequest,並不是想直接複製 ASP.NET Core。

而是想從最小版本開始理解:

為什麼 Framework 需要把原始 HTTP 資料,整理成一個 Request 抽象。

為什麼不直接永遠使用原始字串?

理論上,當然可以。

例如每次想知道 Path,就重新從 rawRequest 中找到第一行,再 Split,最後取得第二個位置。

這樣確實也能執行。

但問題是,如果整個 Framework 都這樣做,HTTP Parsing 的細節就會散落到不同地方。

Router 可能需要知道 HTTP 格式。

Handler 可能也需要知道 HTTP 格式。

其他功能如果想讀 Method 或 Header,也可能又自己解析一次。

最後就會變成很多地方都依賴 HTTP 原始格式。

如果有 HttpRequest,就可以把流程改成:

Raw HTTP

HttpRequest Parsing

HttpRequest

其他元件使用

這樣只有負責 Parsing 的地方需要知道原始 HTTP 的細節。

Router、Handler 或其他 Framework 元件,只需要知道 HttpRequest 提供了哪些資訊。

🧠 今天的認知更新

以前的我會把整個流程想成:

Browser 傳 Request

Framework 得到 Request

現在我開始可以把這句話拆得更細:

Browser

TCP

bytes

原始 HTTP Request

Parsing

HttpRequest

Routing / Handler

所以 Request 物件其實是 Framework 建立出來的一層抽象。

Browser 並沒有把一個 C# 的 HttpRequest 物件送過來。

Browser 送的是符合 HTTP 規則的資料。

Framework 收到這些資料之後,再把它解析、整理,最後轉成開發者比較容易使用的 Request 物件。

Request 與 Response 開始對稱了

做到 Day15 和 Day16 後,我發現整個架構開始慢慢變得更整齊。

一開始:

Request → 一堆 string
Response → 一堆 string

Day15 之後:

Request → 一堆 string
Response → HttpResponse

而 Day16 想完成的是:

Request → HttpRequest
Response → HttpResponse

整個流程就會變成:

Browser

Raw Request

HttpRequest

Framework

HttpResponse

Raw Response

Browser

這個結構比前幾天單純把所有事情塞在 Program.cs 裡清楚很多。


上一篇
為什麼 Framework 也需要自己的 Request 物件?
系列文
程式設計沒有告訴你的事:30 天破解每一個 Why17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言