go http中间件链需显式构造,必须满足func(http.handler) http.handler签名,顺序决定洋葱模型流向,context传值、每层recover、边界清晰是关键。

Go 的 HTTP 中间件链不是靠框架自动注册的,而是靠函数式组合显式构造出来的。只要签名对、调用对、顺序对,链就稳;任一环节错,请求就卡住、panic 就崩服务、日志就漏记、响应就写乱。
func(http.Handler) http.Handler 是硬性接口要求,不是风格建议
标准库的 http.ListenAndServe 和 http.Handle 只接受实现了 http.Handler 接口的值(即带 ServeHTTP(http.ResponseWriter, *http.Request) 方法的类型)。中间件必须返回 http.Handler,否则编译报错:cannot use myMiddleware(myHandler) (type http.HandlerFunc) as type http.Handler。
- 错误写法:
func(next http.HandlerFunc) http.HandlerFunc—— 返回值类型不满足接口,无法直接传给http.Handle - 正确写法:
func(next http.Handler) http.Handler,内部用http.HandlerFunc(func(w, r) { ... })包一层再返回 - 别省略
return:漏掉返回值会导致链断裂,后续中间件和 handler 完全不执行 - 自定义 struct 实现
http.Handler也完全兼容,只要签名一致
中间件顺序决定洋葱模型的实际流向,错一位逻辑就翻车
链式调用是嵌套闭包:recovery(logging(auth(handler))) 表示 recovery 最外层,handler 最内层。请求从外向内穿透,响应从内向外返回。顺序错了,panic 捕不到、日志记不全、认证就失效。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 必须把
recovery放最外层,否则内层 panic 会穿透到http.Server导致整个进程退出 -
auth要比logging更靠近 handler,否则未认证请求也会被记录(且可能没 path/method) -
body 解析类中间件(如读取并重放r.Body)必须在所有依赖原始 body 的中间件之前,否则下游读到空 body - 调试时可在每个中间件首尾加
log.Printf("→ %s", "name")和log.Printf("← %s", "name"),看进出是否成对
context.WithValue 传数据是唯一安全方式,别碰 *http.Request 或全局变量
*http.Request 是不可变的,不能往里面塞字段;全局变量或闭包捕获外部状态在并发下必然出错。所有中间件间共享信息(如用户 ID、trace ID、解析后的 token)必须走 context。
- 上游中间件调用
r = r.WithContext(context.WithValue(r.Context(), key, value))创建新请求 - 下游通过
r.Context().Value(key)读取,注意类型断言 - key 建议用私有 struct 类型(如
type userIDKey struct{}),避免字符串 key 冲突 - 别在中间件里修改
ResponseWriter后还继续调用next.ServeHTTP,否则易触发http: superfluous response.WriteHeader
recover 必须每层独立兜底,别指望“统一 catch”
http.Server 默认不 recover panic。一个中间件里 JSON 解析失败、空指针或第三方 API 超时 panic,若没被本层 recover,就会直接终止当前 goroutine 并向上抛——如果外层没有 recover,整个 HTTP 服务就挂了。
- 每个中间件最外层都应包一层
defer func() { if r := recover(); r != nil { /* log & return 500 */ } }() - 尤其在
json.Unmarshal、jwt.Parse、io.ReadAll(r.Body)等易错点前加 recover - 别只在最外层 recover 就完事:万一 auth 中间件自己 panic 了但没 recover,logging 还没执行完就中断,日志就断层
- recover 后记得清空 panic 状态,避免影响后续请求(Go runtime 不自动重置)
中间件链的扩展性不来自堆砌功能,而来自每个环节的边界清晰:签名统一、调用明确、数据隔离、容错自治。最容易忽略的是——每个中间件都该是个“可单独上线、可单独下线、可单独测试”的独立单元,而不是靠顺序巧合才能跑通的一串闭包。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










