2. Go HTTP中间件基础:net/http Handler接口、中间件模式、洋葱模型、链式调用

2.1 net/http Handler 接口:一切中间件的起点

Go 标准库 net/http 定义了 HTTP 处理的核心抽象——Handler 接口。任何实现了该接口的类型都可以处理 HTTP 请求,这也是构建中间件的基石。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

Handler 接口只包含一个方法 ServeHTTP,接收 ResponseWriter 用于写入响应,*Request 包含请求的所有信息。中间件本质上是一个接收 Handler 并返回新 Handler 的函数,从而实现对请求/响应流程的拦截与增强。

核心概念: 中间件 = func(next Handler) Handler。这种函数签名模式是 Go HTTP 中间件生态的通用契约。

2.2 中间件模式:函数式组合

中间件模式的核心思想是通过函数式组合,将多个处理层串联起来。每个中间件只关注自己的职责,例如日志记录、鉴权、超时控制等,然后调用 next.ServeHTTP 将控制权传递给下一个处理器。

典型的中间件函数签名如下:

type Middleware func(next http.Handler) http.Handler

func Logger(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr)
        next.ServeHTTP(w, r)
        log.Printf("[%s] %s completed in %v", r.Method, r.URL.Path, time.Since(start))
    })
}

func Auth(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        // 验证 token ...
        next.ServeHTTP(w, r)
    })
}
中间件 职责 执行时机
Logger 记录请求方法、路径、耗时 请求前 + 请求后
Auth 验证 Authorization 头 请求前(可中断)
Recovery 捕获 panic,返回 500 包裹整个调用链
Timeout 设置请求超时上下文 请求前

2.3 洋葱模型:请求与响应的双向穿透

洋葱模型(Onion Model)形象地描述了中间件链的执行顺序。请求从最外层中间件进入,逐层向内穿透,到达核心业务处理器后,响应再逐层向外返回。每一层中间件都可以在请求进入时执行前置逻辑,在响应返回时执行后置逻辑。

洋葱模型:中间件链请求/响应穿透 洋葱边界 Logger 中间件 Auth 中间件 Timeout 中间件 业务 Handler 请求 → ← 响应 请求从外到内,响应从内到外 每层可执行前置/后置逻辑
实践要点: 洋葱模型要求中间件必须调用 next.ServeHTTP,否则链会中断。前置逻辑在调用前执行,后置逻辑在调用后执行。利用 defer 可以优雅地实现后置逻辑,即使后续处理器发生 panic 也能执行。

2.4 链式调用:构建中间件管道

链式调用(Chaining)是将多个中间件组合成一个完整处理管道的编程模式。Go 中常见的实现方式有两种:嵌套调用和管道函数。

2.4.1 嵌套调用

handler := Logger(Auth(Timeout(http.HandlerFunc(finalHandler))))

这种写法直观但可读性差,中间件数量增多时难以维护。

2.4.2 管道函数

func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// 使用
handler := Chain(
    http.HandlerFunc(finalHandler),
    Logger,
    Auth,
    Timeout,
)

管道函数从最后一个中间件开始反向包装,确保执行顺序与传入顺序一致:先传入的中间件(Logger)在最外层,后传入的(Timeout)靠近核心。

方式 可读性 扩展性 推荐场景
嵌套调用 低 低 中间件 ≤ 3 个
管道函数 高 高 中间件 ≥ 3 个
第三方库(如 negroni、alice) 最高 高 团队协作项目

2.5 完整示例:构建一个迷你中间件链

package main

import (
    "log"
    "net/http"
    "time"
)

type Middleware func(next http.Handler) http.Handler

func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

func Logger(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("[REQ] %s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
        log.Printf("[RES] %s %s took %v", r.Method, r.URL.Path, time.Since(start))
    })
}

func Recovery(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("[PANIC] %v", err)
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello, Middleware!"))
}

func main() {
    finalHandler := http.HandlerFunc(helloHandler)
    handler := Chain(finalHandler, Logger, Recovery)
    http.Handle("/", handler)
    log.Println("Server starting on :8080")
    http.ListenAndServe(":8080", nil)
}
常见陷阱: 中间件中如果调用了 http.Error 或 w.WriteHeader 后仍然调用 next.ServeHTTP,会导致重复写入响应头(panic)。正确的做法是在中断请求时直接 return,不再调用 next。

2.6 中间件执行顺序可视化

下面用 SVG 展示一个包含 4 个中间件的链式调用执行流程,清晰呈现请求与响应的完整路径。

中间件链式调用执行流程 请求入口 Logger Auth Timeout Handler 响应出口 请求方向 → ← 响应方向 请求顺序:Logger → Auth → Timeout → Handler 响应顺序:Handler → Timeout → Auth → Logger

2.7 本章小结

  • Handler 接口是 Go HTTP 中间件的基石,中间件本质是 func(next Handler) Handler。
  • 洋葱模型描述了请求从外到内、响应从内到外的双向穿透过程,每层可执行前置和后置逻辑。
  • 链式调用通过管道函数将多个中间件组合成可维护的处理链,推荐使用反向遍历实现。
  • 中间件必须谨慎处理中断场景,避免重复写入响应头;利用 defer 实现可靠的后置清理逻辑。