go http中间件必须用闭包包装handler,因其需在不改变http.handler接口前提下注入配置(如logprefix)并实现链式调用,否则会导致变量共享、panic或上下文丢失。

Go HTTP 中间件为什么必须用闭包包装 handler
因为 Go 的 http.Handler 接口只接受一个 http.ResponseWriter 和 *http.Request,中间件要“加逻辑”又不能改接口,只能靠返回新 handler 的方式链式组装。不包闭包,你就没法把中间件自己的配置(比如日志前缀、超时时间)带进后续处理里。
常见错误是写成直接调用:loggerMiddleware(http.HandlerFunc(home)) 却忘了它内部没做闭包捕获,导致所有请求共享同一份变量,或者 panic:「cannot assign to returned value」。
- 正确写法必须返回函数:
func(h http.Handler) http.Handler { return http.HandlerFunc(...) } - 配置参数(如
logPrefix)得在闭包外定义、闭包内引用,否则每次中间件初始化都丢失上下文 - 如果中间件里用了
defer或 goroutine,注意*http.Request的 body 可能已被读取过,再读就是空
gorilla/mux 和 net/http 原生路由对中间件的支持差异
gorilla/mux 本身不提供中间件机制,它的 Router.Use() 只是把 handler 包一层再传给 http.ServeMux,本质还是靠你手动 wrap;而原生 net/http 连 Use() 都没有,全靠自己串函数。
容易踩的坑是以为 router.Use(middleware) 能自动作用于子路由——其实它只影响调用 Use() 之后注册的 route,且不会递归进 Subrouter。如果你在 subrouter 上漏了 Use(),那块路由就裸奔。
立即学习“go语言免费学习笔记(深入)”;
-
net/http下推荐封装一个通用链式工具函数:func chain(h http.Handler, mids ...func(http.Handler) http.Handler) http.Handler -
gorilla/mux的Subrouter()返回新 router,必须显式调用.Use(),否则中间件不生效 - 别在中间件里修改
req.URL.Path后不调用http.Redirect或重写req.RequestURI,否则 mux 匹配失败
中间件顺序错乱导致 context.Value 丢失或 panic
Go 的 context.Context 是靠中间件一层层往下传的,req.Context() 默认是空 context,中间件 A 用 context.WithValue 加字段,中间件 B 才去读——但如果 B 在 A 前执行,ctx.Value(key) 就是 nil,接着类型断言就 panic。
典型错误现象:「interface conversion: interface {} is nil, not string」,尤其在 auth 中间件读 userID 时最常见。
- 鉴权类中间件(如 JWT 解析)必须放在日志、指标类中间件之前,否则日志里打不出用户 ID
- 不要在中间件里用全局变量存 context 数据,
context.WithValue是唯一安全传递方式 - 自定义 key 类型别用字符串,用私有 struct 类型变量,避免 key 冲突:
type userIDKey struct{}
panic 恢复中间件为什么不能只 defer recover()
单纯 defer func() { recover() }() 只能捕获当前 goroutine 的 panic,但 HTTP server 启动后每个请求都在独立 goroutine 里跑,recover 必须放在 handler 函数最外层才有效。漏掉这层包裹,panic 会直接崩掉整个服务进程。
更隐蔽的问题是 recover 后没写 response,客户端卡住等超时,或者写了但 status code 错误(比如 200 带 error body),前端无法识别异常。
- 必须在中间件返回的 handler 函数第一行就
deferrecover,且要检查err := recover()是否非 nil - 恢复后务必显式写 response:
w.WriteHeader(http.StatusInternalServerError),再写 body - 别在 recover 里启动新 goroutine 处理错误日志——可能访问已销毁的
*http.Request字段
中间件真正的复杂点不在写法,而在调用链中各环节对 request/response 生命周期的理解是否一致。body 读没读、header 改没改、context 续没续上,任何一个环节脱节,问题就藏得深、复现难。











