iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
Software Development

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

把 Middleware 接進來:我的 Framework 終於有 Request Pipeline 了

  • 分享至 

  • xImage
  •  

昨天,我們已經把 Framework 最核心的流程接起來:

HttpRequest

Router

Handler

HttpResponse

也就是說,Request 進來後,Framework 已經知道怎麼找到 Handler,執行它,最後把結果變成 Response。

但前面 Day24 研究 Middleware 時,我們已經知道:

真正的 Web Framework 不只是在 Request 到達 Handler 時做 Routing。

很多 Request 還會需要經過一些共用處理。

例如:

Logging

Exception Handling

Authentication

Timing

CORS

如果這些功能全部寫在每一個 Handler 裡,程式會變得非常重複。

所以今天要做的事情,就是把之前只停留在概念上的 Middleware,真正放進我們的 Framework Flow 裡。

今天想理解的問題就是:

Framework 到底怎麼讓每一個 Request 在到達 Handler 前後,自動經過同一套 Middleware?

💭 我原本以為……

以前看到 ASP.NET Core:

app.Use(...)

會覺得這可能只是:

幫 Framework 開啟某個功能。

例如:

UseAuthentication

就是「開啟驗證」。

UseExceptionHandler

就是「開啟錯誤處理」。

但前面理解 Middleware 後才知道:

這些東西不是單純設定值。

它們其實是在建立:

Request Pipeline。

Request 不會直接跳到 Handler。

而是會依序經過不同 Middleware。

例如:

Request

Logging

Authentication

Routing

Handler

Handler 完成後,流程又會往回走:

Handler

Authentication

Logging

Response

所以 Middleware 真正改變的,不是某個單一功能。

而是:

Request 的整體執行流程。

目前 Framework 的流程少了什麼?

Day27 的核心大概是:

Browser

MiniWebServer

HttpRequest

Router

Handler

HttpResponse

Browser

這已經可以工作。

但如果我想在每個 Request 開始時印:

收到 GET /hello

然後完成後再印:

GET /hello 完成

現在最簡單的方法可能就是直接寫在 MiniWebServer。

但如果未來還要:

計時

驗證

錯誤處理

全部放進 MiniWebServer,就會再次回到:

一個 Class 什麼都負責。

這和 Day26 想拆掉巨大 Program.cs 的目的完全相反。

所以這些共用工作應該有自己的位置。

這個位置就是:

Middleware Pipeline。

第一個 Middleware 先做什麼?

最後成品不用一開始就做 Authentication。

最適合的第一個 Middleware 還是:

Logging Middleware。

因為它很簡單,又很容易看出 Pipeline 有沒有真的運作。

想像 Request:

GET /hello

進入 Pipeline 後:

Logging Middleware

印出:
Request 開始:GET /hello

next

Router

Handler

HttpResponse

回到 Logging Middleware

印出:
Request 完成:GET /hello

如果能看到這個順序,就代表 Middleware 已經真正「包住」Handler。

next 再次出現

Day24 已經提過 next。

今天正式把它放進 Framework 時,可以再重新理解。

next 不是一個特殊的 HTTP 關鍵字。

它本質上只是:

下一段可以被執行的程式。

例如 Middleware 可以理解成:

先做自己的事

呼叫 next()

等 next 完成

再做自己的事

所以:

Middleware A

next

如果 next 指向 Middleware B:

A Before

B Before

Handler

B After

A After

也就是:

A(
B(
Handler
)
)

所以 Middleware Pipeline 真正做的事情,其實是:

一層一層把 Delegate 包起來。

Delegate 又接回來了

Day13 做 Routing 時,我們第一次接觸:

Action

Func

當時只是把 Handler 當成:

可以稍後執行的一段程式。

到了 Middleware,又會看到同一個概念。

例如:

next

就是:

可以稍後呼叫的下一段流程。

所以整個 Framework Pipeline 可以透過 Delegate 來組合。

這也讓我發現:

Delegate 不只是 C# 裡一個語法功能。

對 Framework 來說,它其實非常重要。

因為 Framework 常常需要:

先「保存一段程式」

等到某個 Request 發生時

再按照流程呼叫。

Middleware 的基本結構可以怎麼想?

目前不一定要先寫實際程式,但概念可以是:

Middleware

接收 HttpRequest

接收 next

做 Before

呼叫 next

得到 HttpResponse

做 After

回傳 HttpResponse

也就是:

HttpRequest

Middleware

HttpResponse

但是 Middleware 中間還可以呼叫:

next

讓 Request 繼續往下一層。

最終最底層的 next 就可能是:

Router

Handler

HttpResponse

這樣 Middleware 就不需要自己知道 Route Table 裡有哪些 Handler。

它只需要:

處理 Request

然後決定要不要繼續。

Request Pipeline 怎麼被組起來?

假設我們有:

Logging Middleware

Exception Middleware

最後的 Handler Pipeline

Framework 要把它們組成:

Exception

Logging

Handler

可以先想成:

最底層:

Handler

再包一層:

Logging(Handler)

最後再包:

Exception(
Logging(
Handler
)
)

Request 進去後:

Exception Before

Logging Before

Handler

Logging After

Exception After

所以 Pipeline Builder 的工作其實就是:

把一堆 Middleware 按照順序組合成一個最終 Delegate。

最後 Server 不需要知道裡面有幾層。

它只需要:

pipeline(request)

就可以了。

這是今天我覺得非常重要的轉變。

Server 原本:

收到 Request

自己 Routing

自己執行 Handler

現在可以變成:

收到 Request

交給 Pipeline

至於 Pipeline 裡:

Logging

Routing

Handler

Exception Handling

怎麼執行,

由 Framework 自己管理。

Middleware 為什麼可以 Short-Circuit?

假設 Middleware 不呼叫 next。

那後面的流程自然就不會發生。

例如:

Authentication Middleware

發現沒有 Token

直接建立 401 Response

return

這次就不會:

Router

Handler

所以 Middleware 本身有能力決定:

Request 還能不能繼續。

這就是 Short-Circuit。

對我們最後的 Mini Web Framework 來說,不一定要做完整 Authentication。

但如果 Middleware 架構設計正確,理論上就已經支援這種能力。

這就是 Framework Design 很有趣的地方。

今天只做 Logging。

但好的設計可以讓未來:

Authentication

Exception Handling

Timing

也使用同一套 Pipeline 機制。

Logging Middleware 需要知道 Router 嗎?

不需要。

它只需要知道 Request。

例如:

Method = GET

Path = /hello

然後:

開始時間

next()

結束時間

它根本不用知道:

最後是哪個 Handler。

也不用知道:

Route Table 長什麼樣。

這就是責任分離又再次出現。

Router 不需要知道 Logging。

Logging 也不需要知道 Router。

它們只是透過 Pipeline 串起來。

這比把:

Console.WriteLine()

直接塞進 Router 或 Server 乾淨很多。

Exception Middleware 又可以怎麼接?

雖然最後成品時間有限,但可以先理解它的位置。

例如:

Exception Middleware

try

next()

正常 HttpResponse

如果:

next()

Handler

throw Exception

就會回到:

catch

然後產生:

500 Internal Server Error

所以:

Exception Middleware

包住整個後續 Pipeline

這個設計可以讓:

Router

Handler

Result Converter

後面發生的錯誤,

統一在外層處理。

這也是 Middleware Pipeline 很強的地方。

順序為什麼又重要?

假設:

Exception Middleware

放在 Logging Middleware 外面:

Exception

Logging

Handler

如果 Logging 本身也發生 Exception,

外層 Exception Middleware 有機會捕捉到。

但如果順序反過來:

Logging

Exception

Handler

那 Logging 自己前置處理產生的錯誤,內層 Exception Middleware 就抓不到。

所以 Middleware 的註冊順序,不只是看起來好不好看。

它會真正改變:

誰包住誰

以及:

誰能處理誰的結果。

這也就是 ASP.NET Core Middleware 順序非常重要的原因。

Program.cs 又更像 Framework 使用端了

如果 Middleware 真的做進來,

Program.cs 未來可以開始像:

建立 Application

加入 Logging Middleware

註冊 Route

Run

而不用:

手動呼叫 Logging

手動呼叫 Router

手動 Send

所以理想的使用方式開始變成:

Application Setup

Framework 執行

這又再次接回:

Inversion of Control。

開發者不是每次 Request 來都自己控制整個流程。

而是先告訴 Framework:

我要哪些 Middleware

我要哪些 Route

接著 Framework 自己:

等 Request

跑 Pipeline

執行 Handler

產生 Response

這就越來越像真正 Framework。

官方怎麼說?

ASP.NET Core 的 Request Pipeline 是由一連串 Request Delegates 組成。

每個 Middleware 可以:

在下一個元件之前執行工作

呼叫下一個 Middleware

在下一個 Middleware 完成後再執行工作

或者不呼叫下一個元件,直接 Short-Circuit Pipeline。

所以:

Request

Middleware A

Middleware B

Endpoint

Response

不是單純由上往下的一串函式。

更準確地說,是一層一層組合起來的 Request Delegates。

這和我們今天想讓 Mini Web Framework 做的最小 Middleware Pipeline 是同一個核心概念。


以前的我:

Middleware 就是 Framework 裡的一個額外功能。

現在的我:

Middleware

其實是 Request Pipeline 的組成單位

Framework

把 Middleware 一層一層組合

形成最終 Pipeline

Server 把每一個 HttpRequest 丟進 Pipeline

也就是:

Middleware 不只是「做 Logging」。

真正重要的是:

它提供一種可以插入共用處理的 Framework 機制。


今天最大的發現是:

Pipeline 本身也可以被看成「一段程式」。

Server 不需要知道:

Logging

Authentication

Router

Handler

分別怎麼跑。

只要 Framework 事先把它們組成:

pipeline

Request 進來後:

pipeline(request)

所有流程就會按照順序發生。

這讓 Framework 又多了一層抽象:

以前:

Server

直接控制每一步

現在:

Server

Pipeline

Pipeline 自己控制每一步

這開始真正有 Framework 的味道了。

Framework 可以把每個 Middleware 看成一個會包住「下一段流程」的 Delegate。

從最底層 Handler 開始,一層一層把 Middleware 包起來,最後形成一個完整 Pipeline。

Server 收到 Request 後,只需要把 HttpRequest 交給 Pipeline。

剩下的 Logging、Routing、Handler、Response 等流程,就由 Framework 自己執行。

🛠 與 Mini Web Framework 的關聯

Day27 的 Framework Core:

MiniWebServer

HttpRequest

Router

Handler

HttpResponse

Day28 加入 Middleware 後:

MiniWebServer

HttpRequest

Middleware Pipeline

├── Logging

├── 其他 Middleware

└── Router

Handler

HttpResponse

Browser

這代表 Mini Web Framework 開始真正具有:

Request Pipeline。

而不是只有:

Route Table + TCP Server。

最後成品至少可以做到:

啟動 Server

註冊 Route

使用 Logging Middleware

Request 自動經過 Middleware

找到 Handler

產生 HttpResponse

回到 Browser

這樣已經足以展示一個 Mini Web Framework 的核心架構。

今天的實作目標

Day28 真正補實作時,可以先只做最小 Logging Middleware。

例如每次 Request:

Request 開始:GET /hello

執行後面的 Pipeline

Request 完成:GET /hello

只要 Console 順序可以證明:

Middleware Before

Handler

Middleware After

就代表 Middleware Pipeline 已經成功。

不需要今天就做完整 Authentication 或 CORS。

因為最後成品最重要的是:

核心架構完整。

而不是功能數量最多。

結尾

前面幾天,我們一直在做不同的 Framework 元件。

HttpRequest

Router

Handler

HttpResponse

但今天第一次開始處理:

這些元件「怎麼按照順序執行」。

Middleware 讓我發現:

Framework 的設計不只是「有哪些 Class」。

還包括:

控制流程本身也可以被抽象化。

Request 進來後,不需要由 MiniWebServer 手動寫:

先 Logging

再 Routing

再 Handler

而是 Framework 可以事先把它們組成:

Pipeline

最後 Server 只需要:

把 Request 交給 Pipeline。

這讓整個架構又往前一步。

從一個可以處理 HTTP 的 Server,

逐漸變成:

一個可以組合 Request Processing 行為的 Framework。


現在 Mini Web Framework 已經有:

Server

HttpRequest

Router

Handler

HttpResponse

Middleware Pipeline

核心架構幾乎都到位了。

但最後還不能只看:

/hello 有沒有顯示 Hello。

真正的成品還需要確認:

/products 正不正常?

不存在的 Route 有沒有 404?

連續送很多 Request 會不會出問題?

Middleware 每次都有沒有執行?

如果 Handler 出錯又會怎樣?

所以最後兩天,就不能再只增加新東西了。


上一篇
把 Router、Handler、Response 接起來:Framework 的核心流程終於成形
下一篇
Framework 組好了,但真的能用嗎?開始做完整整合測試
系列文
程式設計沒有告訴你的事:30 天破解每一個 Why30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言