# net/http 與 middleware 模式

> 每個 Go web 框架都長在 net/http 上，http.Handler 與 ServeHTTP 是唯一的抽象，middleware 就是包一層 Handler 的閉包，外層先跑。

原文：https://sheng.page/posts/go-net-http-and-middleware/ · 發布：2025-01-19 · 作者：Sheng

每個 Go web 框架，剝到最底層都是 `net/http`。搞懂 `http.Handler` 跟 `ServeHTTP`，才看得懂框架在它上面加了什麼、又藏了什麼。

## http.Handler 與 ServeHTTP

整個 net/http 的伺服器抽象只有一個介面：

```go
type Handler interface {
    ServeHTTP(w http.ResponseWriter, r *http.Request)
}
```

任何有 `ServeHTTP` 的型別都是 Handler。`http.ServeMux`、第三方框架、你自己的 handler，全部都是。常見的寫法是 `http.HandlerFunc`：一個帶 `ServeHTTP` 方法的函式型別，讓普通函式不用包 struct 就能當 Handler：

```go
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」的函式：

```go
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：

```go
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，伺服器才不會被單一請求帶崩：

```go
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](https://go.dev/doc/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，別讓一條請求帶垮伺服器。
- 接下來〈[struct tag 與 reflect](/posts/go-struct-tags-and-reflection/)〉打宣告式路由的地基。

延伸：本系列接著用這些觀念拆 gortex 框架，原始碼見 [yshengliao/gortex](https://github.com/yshengliao/gortex)。

---

> 想法與技術判斷出自 Sheng，和 Claude 一起起草 · 環境 Go 1.23（當時最新 release）· 本系列為事後回填整理 · 列入 20260613 blog 翻新計劃，新漆未乾。