go中间件必须是func(http.handler) http.handler类型,需显式调用next.servehttp(w, r)实现洋葱模型,数据传递依赖context.context,组合顺序为m1→m2→m3→handler,panic需在外层中间件recover。

Go 框架里的中间件不是插件,也不是自动注册的钩子;它必须是 func(http.Handler) http.Handler 类型的函数,否则根本进不了 HTTP 处理链。
中间件签名必须严格匹配 func(http.Handler) http.Handler
很多初学者写成 func(w http.ResponseWriter, r *http.Request) 或 func(http.HandlerFunc),这类函数无法被 http.Handle、http.ListenAndServe 或 Gin/Echo 等框架识别。真正能串起来的,只有一种签名:
-
func(next http.Handler) http.Handler—— 这是中间件的“类型契约”,框架靠它判断是否可嵌套 - 外层闭包(如
loggingMiddleware(logger *log.Logger))用于接收配置,但不参与链式调用 - 内层必须用
http.HandlerFunc包装逻辑,并显式调用next.ServeHTTP(w, r) - 漏掉
next.ServeHTTP就等于中断请求,下游 handler 永远不会执行
调用 next.ServeHTTP 的位置决定中间件行为
洋葱模型不是框架自动调度的,而是靠你手动控制 next.ServeHTTP 的时机来实现。位置不同,语义完全不同:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 放在最前面:前置拦截(如鉴权检查失败时直接
http.Error,不调用next.ServeHTTP) - 放在中间:改写请求后放行(如替换
r.Body后再传给下游) - 放在最后:后置处理(如设置响应头、记录耗时),但要注意:此时响应可能已写出,
w.Header().Set()会 panic - 完全不调用:终止流程(如限流拒绝、CORS 预检通过后直接返回)
跨中间件传递数据只能用 context.Context
别用闭包捕获变量,更别用包级全局变量存 request-scoped 数据——Go HTTP server 是并发的,必然引发数据竞争。
- 写值:
ctx := r.Context().WithValue(key, value),其中key必须是自定义类型(避免字符串 key 冲突) - 读值:
value := ctx.Value(key),务必做非空判断和类型断言 - 传下去:
r = r.WithContext(ctx),再调用next.ServeHTTP(w, r) - 别用
context.Background()替代 request context——它没有 cancel 和 timeout 信号
组合多个中间件时顺序极易出错
手写嵌套 m1(m2(m3(handler))) 容易把顺序写反;用 pipeline 组合时,也要注意 fold 方向:
-
Chain(m1, m2, m3).Then(h)表示执行顺序为m1 → m2 → m3 → h - 等价于手动嵌套的
m1(m2(m3(h))),不是m3(m2(m1(h))) - 调试时可在每个中间件开头加
log.Println("in m1"),看输出顺序验证是否按预期流转 - 典型错误:把
CORS中间件放在内层,结果下游中间件提前 return,响应头没发出去
最常被忽略的是:中间件里发生的 panic 不会被标准 http.ServeMux 捕获,必须自己加 recover;而 recover 放的位置也很关键——如果放在最外层中间件,它能兜住所有 panic;但如果放在某个内层中间件里,外层的 panic 它就收不到。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










