iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
Software Development

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

每個 Request 都要做 Logging 和驗證嗎?Middleware 到底是什麼?

  • 分享至 

  • xImage
  •  

前幾天,我們一直在把整個 Request 到 Response 的流程補完整。

目前已經可以想像一個 Request 進來後:

Browser

HttpRequest

Routing

Parameter Binding

Handler

Result Conversion

HttpResponse

Browser

看起來整條流程已經很完整了。

但這時又出現一個很實際的問題。

假設每一次 Request 進來,我都想先記錄:

誰來了?

打了哪個 Path?

用了什麼 Method?

花了多少時間?

或者某些功能需要先確認:

使用者有沒有登入?

Token 合不合法?

發生 Exception 時要不要統一處理?

難道每個 Handler 都要自己重複寫一次嗎?

例如:

Hello Handler

先 Logging
再驗證
再執行真正邏輯

Products Handler

先 Logging
再驗證
再執行真正邏輯

Users Handler

又再寫一次 Logging
又再寫一次驗證

這樣 Framework 很快又會出現大量重複程式碼。

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

如果很多 Request 都要經過同樣的處理,Framework 到底怎麼把這些共用邏輯放在一起?

這就是 Middleware 開始出現的地方。

💭 我原本以為……

以前使用 ASP.NET Core 時,我常常看到:

app.Use(...)

或者一些:

UseAuthentication

UseAuthorization

UseExceptionHandler

以前可能知道:

這些東西要加在 Program.cs 裡。

但沒有真的理解:

為什麼要叫 Middleware?

也不知道它們到底插在 Request 的哪個位置。

只會覺得:

Framework 好像有很多「功能開關」。

但現在自己把 Request Pipeline 一步一步拆完後,突然比較能理解。

Middleware 並不是單純一個設定。

它其實是在:

Request 到達 Handler 之前或之後,

插入一段共用的處理流程。

Middleware 到底是什麼?

目前可以先把 Middleware 理解成:

Request Pipeline 中的一個處理節點。

例如:

Request

Middleware A

Middleware B

Routing

Handler

每一個 Middleware 都可以:

先做一些事情

決定要不要繼續往下走

也可以在下游處理完成後,再做一些事情

所以 Middleware 並不只是:

Request 前的程式碼。

它甚至可以包住後面的整個流程。

最簡單的 Logging Middleware 怎麼想?

假設我想記錄每個 Request。

如果沒有 Middleware,可能每個 Handler 都自己寫:

收到 Request

開始計時

執行 Handler

結束計時

印出時間

這會很重複。

如果有 Middleware,就可以變成:

Request

Logging Middleware

記錄開始時間

交給下一個處理流程

Handler 執行

回到 Logging Middleware

記錄結束時間

Response

所以 Logging 不需要寫進每個 Handler。

只要所有 Request 都會經過同一個 Middleware,就能統一處理。

為什麼 Middleware 可以「回來」?

這是 Middleware 一開始比較容易搞混的地方。

它不是只有:

由上往下跑完。

更像是:

先往下呼叫下一層

等下一層完成

再回到自己

例如:

Middleware A 開始

Middleware B 開始

Handler

Middleware B 結束

Middleware A 結束

所以整個 Pipeline 很像一層一層包起來。

可以想成:

A(
B(
Handler
)
)

Request 進去時:

A → B → Handler

Response 回來時:

Handler → B → A

這也是為什麼 Middleware 可以同時處理:

Request 前

Response 後

的工作。

Logging 就是一個很典型的例子。

Exception Handling 為什麼很適合 Middleware?

假設 Handler 裡發生錯誤。

如果沒有統一處理,每個 Handler 都可能要自己:

try
catch

再建立:

500 Internal Server Error

但如果把 Exception Handling 放在 Middleware:

Exception Middleware

try

next()

後面的所有處理

如果裡面任何地方丟 Exception:

Handler

throw

就會一路回到外層的 Exception Middleware。

它就可以統一:

catch Exception

記錄錯誤

建立 500 Response

所以 Middleware 很適合處理:

Cross-Cutting Concerns

也就是「很多功能都會需要,但又不屬於任何一個特定 Handler 的共用關注點」。

Authentication 也是同樣概念

假設某些 Request 需要登入。

如果每一個 Handler 都自己:

讀 Authorization Header

解析 Token

確認是否合法

那會非常麻煩。

比較好的方式是:

Request

Authentication Middleware

讀取 Header

驗證身分

建立 User Context

next()

Routing / Handler

後面的 Handler 就不需要再重新做一次完整驗證。

它只需要知道:

這個 Request 的使用者是誰。

所以 Middleware 可以把底層驗證邏輯放在 Handler 之前統一完成。

Middleware 可以不呼叫下一層嗎?

可以。

這點非常重要。

假設 Authentication Middleware 發現:

Token 無效。

那它根本沒有必要繼續:

Routing

Handler

可以直接回:

401 Unauthorized

也就是:

Request

Authentication Middleware

驗證失敗

401 Response

結束

這種情況通常稱為:

Short-Circuit

也就是 Middleware 直接終止 Pipeline。

所以 Middleware 不只是:

「一定做完再交給下一層」。

它其實有權決定:

這個 Request 還要不要繼續往下走。

這讓 Pipeline 變得非常有彈性。

如果 Authentication 通過呢?

就可以:

Authentication Middleware

成功

next()

後面的 Pipeline

所以 Middleware 很像:

每一個關卡都可以決定:

通過

直接結束。

Pipeline 又是什麼?

前面一直提到:

Request Pipeline

現在可以更具體理解。

Pipeline 就是一連串按照順序執行的處理步驟。

例如:

Request

Exception Middleware

Logging Middleware

Authentication Middleware

Routing

Handler

Result Conversion

Response

每一層都有不同責任。

而 Framework 的工作之一,就是:

把這些處理步驟按照順序串起來。

所以 ASP.NET Core 裡常看到:

UseA

UseB

UseC

順序會重要。

因為它其實就是在建立:

A

B

C

Endpoint

順序真的會影響結果嗎?

會。

假設:

Authentication Middleware

放在 Routing 前或後,

可能會影響它能不能取得某些 Endpoint 資訊。

又或者 Exception Middleware 如果放得太裡面,

就可能抓不到外層發生的 Exception。

所以 Middleware Pipeline 並不是:

「有加就好」。

而是:

執行順序本身就是設計的一部分。

這也是為什麼 Framework 文件常常會特別告訴開發者:

某些 Middleware 要放在哪一個 Middleware 前面或後面。

next 到底是什麼?

在 Middleware 的概念裡,常常會看到一個:

next

目前可以先把它理解成:

下一個 Middleware,或 Pipeline 後面的剩餘部分。

例如:

Middleware A

裡面:

做 A 的前置工作

next()

做 A 的後置工作

當呼叫:

next()

其實就是:

把控制權交給 Pipeline 的下一層。

所以:

A → next

可能進到:

B

而 B 再呼叫:

next

才會進到:

C

最後可能到:

Handler。

這就是 Middleware Chain。

它和 Delegate 很有關係

做到這裡,其實又會看到之前學過的 Delegate。

因為:

next

本質上就是:

一段等等可以被呼叫的程式。

這和 Day13 用:

Action

Func

的概念很接近。

例如概念上:

Middleware(next)

會回傳一段新的處理程式。

它裡面先做自己的事情,

再決定要不要呼叫:

next()

所以 Middleware Pipeline 可以透過 Delegate 一層一層組合起來。

這也讓之前學 Delegate 的內容再次接回 Framework 設計。

Framework 為什麼不能全部寫在 Program.cs?

理論上當然可以。

例如:

收到 Request

先 Logging

再 Authentication

再 Routing

再 Handler

再 Error Handling

全部寫在 while(true) 裡。

功能也能做。

但很快會變成:

Program.cs

幾百行

甚至上千行。

而且各種責任混在一起。

Middleware 的價值就是把:

Logging

Authentication

Exception Handling

CORS

Timing

其他共用功能

拆成獨立元件。

然後 Framework 再負責把它們組成 Pipeline。

所以:

Program.cs

不需要知道每個 Middleware 裡面所有細節。

它只需要決定:

Pipeline 的順序。


官方怎麼說?

ASP.NET Core 會使用一系列 Request Delegates 組成 Request Pipeline。

每個 Middleware 都可以:

在下一個 Middleware 執行之前做處理

呼叫下一個 Middleware

在下一個 Middleware 完成後再做處理

也可以選擇不呼叫下一層,直接終止 Request Pipeline。

這就對應到:

Request

Middleware

next

Middleware

Endpoint

Response

因此 Middleware 不只是外掛功能。

它本身就是 Framework Request Processing Architecture 的核心部分之一。


以前的我:

Request

Controller

Response

現在的我:

Request

Middleware Pipeline

Routing

Parameter Binding

Handler

Result Processing

Middleware Pipeline

Response

也就是說:

Handler 並不是 Request Pipeline 的全部。

它只是中間真正處理商業邏輯的一部分。

在它之前和之後,都可以存在很多 Framework 級別的共用處理。


今天最大的發現是:

Middleware 其實是在控制「流程」。

前幾天做的元件比較像:

HttpRequest
→ 描述 Request

HttpResponse
→ 描述 Response

Router
→ 找 Handler

Parameter Binder
→ 準備參數

而 Middleware 處理的是:

這些東西到底要按照什麼順序經過哪些步驟。

也就是:

Framework 不只是需要很多元件。

還需要一個方法把所有元件串起來。

Pipeline 就是在做這件事。


Framework 可以把 Logging、Authentication、Exception Handling 等共用邏輯拆成 Middleware。

每一個 Middleware 接收 Request 後,可以做自己的處理,再把控制權交給下一個 Middleware。

最後形成:

Request

Middleware

Middleware

Handler

Middleware

Middleware

Response

這樣共用邏輯就不用重複寫進每一個 Handler。

目前 Mini Web Framework 已經有很多零件:

HttpRequest

Router

Parameter Binding

Handler

Result Conversion

HttpResponse

現在還需要一件事情:

把它們真正串成一條 Pipeline。

例如未來可能是:

Browser

HttpRequest

Exception Middleware

Logging Middleware

Router

Parameter Binder

Handler

Result Converter

HttpResponse

Browser

如果能做到這裡,

Mini Web Framework 就不再只是:

幾個獨立功能拼在一起。

而是真的開始有一個完整的:

Request Processing Pipeline。

今天暫時不實作

今天依然先理解 Middleware 與 Pipeline 的概念。

因為距離最後五天的成品整合已經很近了。

真正開始做成品時,可以先實作一個最簡單的 Middleware:

Logging Middleware。

例如:

收到 Request

印出 Method 和 Path

呼叫 next

Response 完成

印出處理完成

只要這個最小 Pipeline 成功,就代表:

Framework 已經能把共用處理插進 Request Flow。

之後如果還有時間,再加入:

Exception Handling

或其他簡單 Middleware。

結尾

今天讓我第一次重新看待:

Request → Handler → Response

這條流程。

以前會覺得 Handler 就是整個 Web Application 的中心。

但現在開始發現:

真正的 Framework 在 Handler 外面,其實還有一層很重要的 Pipeline。

很多不屬於特定 Handler 的功能:

Logging

Authentication

Exception Handling

Timing

都可以放在 Middleware 中。

每個 Middleware 不需要知道整個 Framework 的所有細節。

它只需要:

處理現在的 Request

決定要不要呼叫 next

必要時再處理回來的 Response

最後一層一層組合,就形成完整的 Request Pipeline。

這也再次讓我看到 Framework 設計一直重複出現的一個想法:

把不同責任拆開。

再用清楚的方式把它們組回來。

現在我們已經有:

Request

Routing

Parameter Binding

Handler

Response

甚至開始有:

Middleware Pipeline

整個 Framework 的核心零件幾乎都出現了。

但目前很多東西都還只是個別概念。

真正到了最後成品時,Framework 到底應該長什麼樣子?

Program.cs 又應該剩下多少東西?

所以在正式進入最後五天整合前,最後一個很重要的問題是:

一個 Framework 到底該怎麼把這些元件組織起來?


上一篇
Handler 回傳的東西都不一樣,Framework 怎麼把它們變成 HTTP Response?
系列文
程式設計沒有告訴你的事:30 天破解每一個 Why24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言