net/http 與 middleware 模式
net/http 與 middleware 模式
每個 Go web 框架,剝到最底層都是 net/http。搞懂 http.Handler 跟 ServeHTTP,才看得懂框架在它上面加了什麼、又藏了什麼。
http.Handler 與 ServeHTTP
整個 net/http 的伺服器抽象只有一個介面:
type Handler interface {
ServeHTTP(w http.ResponseWriter, r *http.Request)
}
任何有 ServeHTTP 的型別都是 Handler。http.ServeMux、第三方框架、你自己的 handler,全部都是。常見的寫法是 http.HandlerFunc:一個帶 ServeHTTP 方法的函式型別,讓普通函式不用包 struct 就能當 Handler:
type HandlerFunc func(http.ResponseWriter, *http.Request)
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
f(w, r) // 方法直接呼叫自己
}
「函式型別掛方法」這招是 Go 的慣用法,後面 middleware 全靠它。
middleware 就是 Handler 包 Handler
middleware 不是什麼魔法,就是一個「吃 Handler、回 Handler」的函式:
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
簽章 func(http.Handler) http.Handler 就是 middleware 的全部。它在呼叫 next 前後插行為,包一層加一個責任。
用閉包串中介層
要串多個,就一層包一層。用閉包寫個 helper:
func Chain(h http.Handler, mws ...func(http.Handler) http.Handler) http.Handler {
for i := len(mws) - 1; i >= 0; i-- {
h = mws[i](h) // 反向包,最後一個最先被包、最外層最先執行
}
return h
}
handler := Chain(mux, Logging, Auth) // 進來先 Logging 再 Auth
順序就是洋蔥:最外層先跑。框架(包含 gortex)做的也就是把這套泛用化,再加上路由與 context。
defer 與 recover
handler 裡 panic 預設會炸掉那條請求的 goroutine。用 defer + recover 攔住回 500,伺服器才不會被單一請求帶崩:
func Recover(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
recover 只有在 defer 的函式裡呼叫才有效。defer 在函式返回(含 panic 展開)時才執行,正好接得住。
Effective Go 精選:Functions、defer
middleware 這套吃的是 Go 把函式當一級值的能力:函式可以當參數、回傳值、塞進 slice。Effective Go 的 Functions 還講了多回傳值與 named result。
defer 則是 Effective Go 點名的清理慣用法:把「關檔、解鎖、收尾」排在函式出口,靠近開啟處寫、不怕中途 return 漏掉。要記兩件事:多個 defer 是 LIFO(後進先出),而參數在 defer 那行就求值、不是等到真正執行才算。
重點整理
- net/http 的核心就一個 interface:
http.Handler/ServeHTTP,所有框架都長在它上面。 - middleware 就是
func(http.Handler) http.Handler,包一層加一個行為。 - 用閉包串 chain,順序等於洋蔥,外層先跑。
defer+recover把 panic 攔住回 500,別讓一條請求帶垮伺服器。- 接下來 A2〈struct tag 與 reflect〉打宣告式路由的地基。
延伸:本系列接著用這些觀念拆 gortex 框架,原始碼見 yshengliao/gortex。
大綱 Sheng,內文 Claude 協助 · 環境 Go 1.23(當時最新 release)· 本系列為事後回填整理 · 列入 20260613 blog 翻新計劃,新漆未乾。