Go HTTP 中间件

Go HTTP 中间件通过 func(http.Handler) http.Handler 模式实现请求处理链,是实现日志、认证、CORS 等横切关注点的标准方式。

#type / concept #status / growing #tech / dev #resource / go

[!info] related notes

Go HTTP 中间件

一句话定义

中间件是一个接受 http.Handler 并返回新的 http.Handler 的函数,在调用下游 Handler 前后插入横切逻辑(如日志、认证、panic 恢复)。

核心机制 / 工作原理

Go 的中间件模式基于 net/http 包的核心签名:

type Middleware func(http.Handler) http.Handler

一个典型的中间件结构如下:

func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r) // 调用下一个 Handler
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

链式组合:

mux := http.NewServeMux()
mux.HandleFunc("/api/users", usersHandler)

// 包装顺序:Recovery → Auth → Logging → mux
// 请求进入时:Recovery → Auth → Logging → mux
// 响应返回时:mux → Logging → Auth → Recovery
handler := RecoveryMiddleware(AuthMiddleware(LoggingMiddleware(mux)))

或者用辅助函数简化链式调用:

func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
    // 反向遍历,使第一个中间件最先执行
    for i := len(middlewares) - 1; i >= 0; i-- {
        h = middlewares[i](h)
    }
    return h
}

handler := Chain(mux, LoggingMiddleware, AuthMiddleware, RecoveryMiddleware)

通过 Context 传递数据:

中间件经常需要向下游 Handler 传递解析后的信息(如当前用户):

type contextKey string
const userKey contextKey = "user"

func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        user, err := validateToken(token)
        if err != nil {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return // 不调用 next,请求到此终止
        }
        ctx := context.WithValue(r.Context(), userKey, user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// 下游 Handler 取出用户信息
func usersHandler(w http.ResponseWriter, r *http.Request) {
    user := r.Context().Value(userKey).(*User)
    fmt.Fprintf(w, "Hello, %s", user.Name)
}

最小例子 / 最小场景

func CORSMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusOK)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func RecoveryMiddleware(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 recovered: %v\n%s", err, debug.Stack())
                http.Error(w, "Internal Server Error", 500)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

CORS 中间件在预检请求(OPTIONS)时直接返回,不调用 next,实现了请求拦截。Recovery 中间件用 defer + recover 兜底所有 panic。

为什么重要

  • 关注点分离:业务 Handler 只处理业务逻辑,认证、日志、CORS 等由中间件独立处理。
  • 可复用:同一个中间件可以应用到所有路由、部分路由或单个路由。
  • 可组合:中间件像管道一样叠加,顺序可调,增删不影响其他层。
  • 测试友好:中间件可以独立测试——构造一个 mock Handler 验证中间件行为。

边界与易混淆点

  • 中间件的执行顺序很重要:包裹顺序决定了代码的执行顺序。最外层的中间件最先执行”前置逻辑”,最后执行”后置逻辑”。
  • 如果中间件不调用 next.ServeHTTP(),请求处理链就中断了——这在认证失败、限流拒绝等场景是有意为之。
  • http.Handlerhttp.HandlerFunc 的关系:HandlerFunc 是一个适配器,让普通函数满足 Handler 接口。中间件返回的几乎总是 http.HandlerFunc
  • context.WithValue 的 key 应使用未导出的自定义类型,避免不同包之间的 key 冲突。
  • 第三方框架(chi、echo、gin)各自定义了自己的中间件签名,与标准库不通用。选择框架意味着选择其中间件生态。
创建于 2026/6/25 更新于 2026/6/25