go中间件必须显式调用next.servehttp(w, r),因其不自动流转请求,漏调会导致请求静默挂起、无日志无报错;唯一推荐签名是func(http.handler) http.handler,确保与标准库及生态兼容。

Go 里没有“标准责任链接口”,硬套 Java 风格的 SetNext() 或 type Chain interface 反而让代码难测、难组合、难调试。最可靠、最常用的方式,就是用 func(http.Handler) http.Handler 写中间件,显式调用 next.ServeHTTP(w, r) 控制流转——漏掉这句,请求就静默挂起,监控不报错,日志没输出,排查起来像在黑盒里摸开关。
为什么中间件必须显式调用 next.ServeHTTP(w, r)
Go 的 HTTP 处理器不自动传递请求,next 只是一个闭包捕获的变量,不调它,控制流就停在当前函数末尾,后续 handler 完全不会执行。这不是“默认继续”,而是“彻底中断”。
- 漏写
next.ServeHTTP(w, r):请求卡住,连接保持打开,客户端超时,服务端无错误日志 - 写了但传错参数(比如传了
w和r的副本或 nil):下游拿不到原始请求上下文,路由匹配失败或 panic - 在
http.Error()后仍调用next.ServeHTTP():可能触发http: multiple response.WriteHeader calls
func(http.Handler) http.Handler 是唯一推荐的签名
别定义自己的 type Middleware func(*http.Request) *http.Request 或 type Handler interface { Serve(ctx context.Context) error }——这些签名要么丢失响应控制权,要么和标准库生态割裂,导致无法复用 chi、gorilla/mux 等中间件。
- 所有中间件统一该签名,才能自由嵌套:
WithRecovery(WithAuth(WithLogging(h))) - 用
http.HandlerFunc包一层,避免手动实现ServeHTTP方法出错 - 返回值是
http.Handler,可直接传给http.ListenAndServe或http.ServeMux.Handle
中间件顺序错了,等于白写
Go 中间件是“包裹式”执行:最后注册的最先运行。比如 logging(auth(h)) 表示 logging 在最外层,auth 居中,h 在最内。这个方向和 Express/Koa 直觉相反,极易踩坑。
-
Recovery必须最外层:否则 panic 会直接炸出 server,根本轮不到它捕获 -
Auth要在Logging之后、业务 handler 之前:否则拒绝请求的日志漏记,或未鉴权就进了业务逻辑 -
RateLimit建议放在Auth后:避免未认证流量耗尽配额,也方便按用户维度限流 - 调试时在每个中间件开头加
log.Printf("→ %s", "auth"),看输出顺序验证是否符合预期
想改请求或响应?别动原始对象,用标准方式包装
*http.Request 不可变,http.ResponseWriter 不可替换,强行赋值或类型断言只会埋雷。
- 改请求:用
r = r.WithContext(context.WithValue(r.Context(), key, val)),下游通过r.Context().Value(key)获取 - 改响应:必须实现
ResponseWriter装饰器,内嵌原ResponseWriter,重写WriteHeader()和Write();注意检查written状态,避免重复写 header - key 类型要用私有 struct(如
type userIDKey struct{}),防止 context key 冲突
真正难的不是写出链,而是确保每层都正确移交控制权、正确传递 context、正确包装 response writer。线上最常崩的点,永远是某层中间件忘了调 next.ServeHTTP(w, r),或者 recovery 没包在最外层——这两处一旦出问题,现象极隐蔽,恢复时间远大于修复时间。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











