iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
Software Development

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

把 Router、Handler、Response 接起來:Framework 的核心流程終於成形

  • 分享至 

  • xImage
  •  

昨天開始正式把巨大 Program.cs 拆開。

原本很多事情都混在一起:

TCP Server

Request Parsing

Routing

Handler

Response

現在則開始把它們分成不同責任。

例如:

MiniWebServer

負責 Server

HttpRequest

負責 Request

Router

負責找 Handler

HttpResponse

負責 Response

這樣看起來比較乾淨了。

但拆開之後又出現一個新的問題。

這些 Class 現在只是分開放著。

如果它們沒有真的接起來,也只是:

很多獨立零件。

所以今天真正想解決的是:

Framework 收到一個 Request 之後,到底要怎麼把 Router、Handler、Response 串成一條真正可以執行的流程?


以前會覺得:

把程式拆成幾個 Class,就算是重構完成。

但現在開始發現:

拆開只是第一步。

Framework 更重要的是:

這些元件之間怎麼合作。

例如:

MiniWebServer 收到 Request。

那接下來是誰負責找 Route?

Router 找到 Handler 後,又是誰執行?

Handler 回傳結果後,怎麼變成 HttpResponse?

HttpResponse 又怎麼回到原本那條 NetworkStream?

所以真正的 Framework 不是:

有很多 Class。

而是:

這些 Class 之間形成一條清楚的 Request Processing Flow。

今天要接起來的核心流程

今天先不要一次加入所有進階功能。

像:

Reflection

Parameter Binding

Middleware

JSON Model Binding

都可以再慢慢補。

Day27 最重要的是先把最核心的流程跑通:

HttpRequest

Router

Handler

HttpResponse

Browser

只要這條主幹可以順利運作,後面的功能就有地方可以插進去。

第一步:Server 不應該自己知道所有 Route

假設 MiniWebServer 收到:

GET /hello HTTP/1.1

Server 可以透過 HttpRequest 取得:

Method = GET

Path = /hello

但 Server 不應該自己寫:

如果是 /hello 就執行這個。

如果是 /products 就執行那個。

因為這樣 Routing 又回到 Server 裡面了。

比較合理的是:

MiniWebServer

把 request.Path 交給 Router

Router 找 Handler

也就是:

Server 負責收到 Request。

Router 負責回答:

這個 Request 應該交給誰。

這樣兩者的責任才真的分開。

Router 裡面到底存什麼?

之前我們已經用過:

Dictionary

來建立最簡單的 Route Table。

例如概念上:

/hello

Hello Handler

/products

Products Handler

所以 Router 可以理解成:

Route Table
+
Route Matching

Application 啟動時:

註冊 Route

Request 進來時:

查 Route

也就是:

Register

之後 Match

這兩件事都是 Router 很自然的責任。

Route Registration 和 Route Matching 不一樣

這裡開始有一個很重要的區分。

Framework 啟動時,我們會做:

註冊 Route。

例如:

/hello → Hello Handler

這時還沒有任何 Browser Request。

只是先告訴 Framework:

未來如果有人要求 /hello,要交給這個 Handler。

而真正 Request 進來後:

GET /hello HTTP/1.1

才開始:

Route Matching

也就是:

Path = /hello

Route Table

找到 Hello Handler

所以:

Route Registration
→ 建立規則

Route Matching
→ 使用規則

Framework 啟動階段和 Request 處理階段,其實是不一樣的。

找到 Handler 後怎麼辦?

假設 Router 找到:

Hello Handler

接下來就是:

執行 Handler。

目前最簡單的 Handler 可以先維持:

沒有參數

執行後回傳 HttpResponse

所以可以想成:

Router

找到 Func

handler()

HttpResponse

這裡就重新接回 Day13 學過的 Delegate。

Handler 不需要自己知道:

Browser 是誰

TCP 怎麼連

NetworkStream 在哪裡

它只要做:

自己的處理邏輯

最後產生結果。

例如:

Hello Handler

Body = Hello!

Products Handler

Body = Products Page

這樣 Handler 才會比較像 Application Logic。

Handler 為什麼不直接 Send?

假設 Handler 自己:

建立 HttpResponse

然後直接:

Send(stream)

看起來好像也可以。

但這樣 Handler 就必須知道:

NetworkStream

也就是又開始碰底層 Server 細節。

更乾淨的設計是:

Handler

只回傳 HttpResponse

Framework

負責真正 Send

這樣 Handler 的責任就是:

決定「要回什麼」。

而 Framework 的責任是:

決定「怎麼送出去」。

這就是 Day15 HttpResponse 出現後,很重要的一個分工。

所以完整流程可以是:

Handler

return HttpResponse

Framework

response.Send(stream)

Handler 和 Browser 之間還隔著 Framework。

找不到 Route 怎麼辦?

如果 Router 收到:

/abc

但 Route Table 裡沒有:

/abc

那 Router 就找不到 Handler。

這時也不能讓 Server 什麼都不做。

Framework 還是要產生:

404 Not Found

所以可以想成:

Router.Match(request)

找到?
├── 有
│ ↓
│ Handler
│ ↓
│ HttpResponse

└── 沒有

404 HttpResponse

最後不管哪一種,都統一:

HttpResponse

Send

Browser

這就是一個很漂亮的統一出口。

成功和失敗最後都是 Response

以前:

Route 找得到

走一套邏輯

Route 找不到

另外一套邏輯

但現在開始可以統一成:

Request

最後一定產生 HttpResponse

可能是:

200 OK

也可能是:

404 Not Found

甚至未來可能:

400 Bad Request

500 Internal Server Error

對 Server 而言,最後只需要:

拿到 Response

Send

這樣整條流程會乾淨很多。

MiniWebServer 的角色開始變清楚

做到這裡後,可以重新思考 MiniWebServer。

它不需要理解每個 Handler 在做什麼。

也不需要知道:

Hello 是什麼。

Products 是什麼。

它真正負責的是:

啟動 Listener

等待 Client

讀取 Request

建立 HttpRequest

交給 Framework 處理

取得 HttpResponse

送回 Client

可以整理成:

TCP

HttpRequest

Application Pipeline

HttpResponse

TCP

所以 MiniWebServer 比較像:

HTTP 世界和 Framework 世界之間的入口。

Program.cs 又可以再變簡單一點

如果 Router 和 Server 都拆出去了,Program.cs 理論上只需要:

建立 Server

建立 Router

註冊 Route

啟動

也就是從:

我要告訴程式「每個 Request 怎麼底層處理」

慢慢變成:

我要告訴 Framework「我的 Application 有哪些功能」

例如概念上:

註冊 /hello

註冊 /products

Run

這已經開始接近 Framework 使用體驗。

使用 Framework 和實作 Framework 的差別開始出現

Framework 內部可能知道:

TcpListener

NetworkStream

HTTP Parsing

Dictionary

HttpResponse.Send

但使用 Framework 的 Program.cs 不應該全部知道。

它只需要:

Route

Handler

Run

這時候整個專案開始明顯分成兩個世界。

Framework Internals:

Server

Request

Router

Response

Application:

/hello

/products

其他 Handler

這個分界非常重要。

因為如果未來把 Framework 做成 Library,

其他專案理論上也可以使用同一套 Server、Router、Request、Response。

只需要換掉:

Application Routes

這才開始真正有「Framework 可重用性」的感覺。

Request Processing Flow 到底是什麼?

前面一直講 Request Pipeline。

今天先不加入完整 Middleware,但其實 Router、Handler、Response 本身就已經形成最基本的 Processing Flow。

例如:

Browser

MiniWebServer

HttpRequest.Parse

Router.Match

Handler

HttpResponse

Send

Browser

這就是最小 Request Processing Flow。

後面如果加入 Middleware,只是在這條主線外再包更多步驟。

例如:

Browser

MiniWebServer

HttpRequest

Logging Middleware

Router

Handler

HttpResponse

Logging Middleware

Browser

所以今天先把核心流程接好,比立刻做完整 Middleware 更重要。

Framework 的核心其實開始很小

做到這裡,我突然發現:

一個最小 Web Framework 的核心,其實可以先縮成幾個問題。

第一:

Request 是什麼?

第二:

Request 要交給誰?

第三:

誰來處理?

第四:

最後要回什麼?

也就是:

HttpRequest

Router

Handler

HttpResponse

其他功能其實都是在這條主線上慢慢增加。

例如:

Headers

Query

Body

Parameter Binding

Middleware

Exception Handling

都沒有改變最核心的:

Request → Handler → Response

這條流程。

官方怎麼說?

真正的 ASP.NET Core 中,Routing 會將 HTTP Request 與 Endpoint 進行配對。

Endpoint 則代表應用程式中可以被執行的 Request Processing Code。

Request 經過 Pipeline 後,Endpoint 執行,再產生 HTTP Response。

雖然真正 ASP.NET Core 的架構比我們的 Mini Web Framework 複雜非常多,但核心方向仍然可以理解為:

Request

Routing

Endpoint

Response

所以我們現在先把:

Router

Handler

HttpResponse

接起來,正是在建立最小版本的這條核心路徑。


以前的我:

Server 收到網址

if / else

執行某段程式

現在的我:

MiniWebServer

HttpRequest

Router

Handler

HttpResponse

MiniWebServer

Browser

也就是說:

Server 不負責所有事情。

它只是讓 Request 進入 Framework,再讓 Response 回到 Client。

真正 Application 的邏輯則存在 Handler 裡。

這讓 Framework、Server、Application 三者的角色開始真正分開。


今天最大的發現是:

拆 Class 並不是結束。

真正重要的是建立「元件之間的資料流」。

例如:

HttpRequest 的輸出

成為 Router 的輸入

Router 的輸出

是 Handler

Handler 的輸出

是 HttpResponse

HttpResponse

再交回 Server

每一個元件不需要知道所有事情。

只需要知道:

我要接收什麼。

我要產生什麼。

這種介面之間的合作,才讓很多小元件變成真正完整的 Framework。

Framework 的核心流程可以先建立成:

HttpRequest

Router

Handler

HttpResponse

MiniWebServer 只負責接收 Client、建立 Request,以及最後送出 Response。

這樣 Application Logic 和 HTTP / TCP 底層細節就開始真正分開。

🛠 與 Mini Web Framework 的關聯

Day26 做的是:

把零件拆開。

Day27 則是:

把零件重新接起來。

目前最終成品的核心已經可以想像成:

Program.cs

Register Routes

Run

Framework 內部:

MiniWebServer

HttpRequest

Router

Handler

HttpResponse

當 Browser 打:

/hello

Framework:

收到 Request

解析 /hello

Router 找到 Hello Handler

執行

得到 Hello Response

Send

Browser 顯示 Hello

這就是最基本但完整的 Web Framework Core Flow。

今天的實作目標

Day27 真正實作時,可以集中做幾件事。

第一:

完成 Router 類別。

第二:

讓 Router 可以 Register Route。

第三:

讓 Router 可以根據 Path 找到 Handler。

第四:

讓 MiniWebServer 收到 HttpRequest 後呼叫 Router。

第五:

Handler 回傳 HttpResponse。

第六:

找不到 Route 時建立 404 Response。

第七:

重新測試:

/hello

/products

/abc

確認拆開後整個流程還是正常。

完成之後,應該可以得到:

/hello

Hello!

/products

Products Page

/abc

404 Not Found

外部功能可能跟 Day14 看起來差不多。

但內部架構已經完全不一樣。

這就是最後幾天最重要的事情:

不是一直增加新功能。

而是把前面理解過的東西真正組成 Framework。

結尾

Day26,我們開始把巨大 Program.cs 拆開。

但今天才真正發現:

拆開只是為了讓元件可以用更清楚的方式重新合作。

HttpRequest 告訴 Framework:

這次 Request 是什麼。

Router 告訴 Framework:

誰應該處理它。

Handler 負責:

真正的 Application Logic。

HttpResponse 則描述:

最後要回什麼。

MiniWebServer 把最前面的 TCP 和最後的 TCP 接起來。

整個流程:

Browser

Request

Framework

Handler

Framework

Response

Browser

終於開始真正成形。

從最開始只有:

Console.WriteLine("Hello World");

到現在已經可以畫出一條自己的 Request Processing Flow。

最後三天的工作,就是繼續讓這條流程:

更完整

更乾淨

更像一個真正可以使用的 Mini Web Framework。


目前最核心的:

Request

Router

Handler

Response

已經接起來了。

但前面我們還學過一個非常重要的 Framework 功能:

Middleware。

現在如果想讓每個 Request 都自動經過 Logging,

又不想把 Logging 寫進每一個 Handler,

到底要怎麼把 Middleware 真正插進我們自己的 Framework Pipeline?


上一篇
開始組裝 Mini Web Framework:第一步,先把巨大 Program.cs 拆掉
下一篇
把 Middleware 接進來:我的 Framework 終於有 Request Pipeline 了
系列文
程式設計沒有告訴你的事:30 天破解每一個 Why30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言