net/http中间件必须为func(http.Handler)http.Handler签名,顺序决定洋葱模型流向,Recovery须置最外层防panic崩溃,闭包捕获需避免变量复用,状态类中间件应封装为结构体实现ServeHTTP。

net/http 本身不提供中间件栈,所谓“接入”其实是手动构造函数链——不是装个包就能用,而是靠你写对签名、嵌对顺序、防住陷阱。
中间件函数签名必须是 func(http.Handler) http.Handler
这是硬性要求,错一个字符就编译失败。常见错误包括:
- 写成
func(http.ResponseWriter, *http.Request)—— 这只是 handler 函数,不是中间件 - 漏掉返回类型,比如只写
func(next http.Handler)—— Go 不允许无返回值的中间件 - 试图传额外参数,如
func(next http.Handler, timeout time.Duration)—— 破坏链式调用兼容性
正确写法示例:
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("→ %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("← %s %s", r.Method, r.URL.Path)
})
}
拼链顺序决定执行时机,不是“先注册先执行”
中间件越靠外,越早拿到请求、越晚拿到响应。比如:Recovery(Logging(Auth(handler))) 的实际执行流是:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- Recovery 拦截 panic(最外层)
- Logging 打日志开始(第二层)
- Auth 校验 token(第三层)
- handler 处理业务(最内层)
- 返回时:handler → Auth → Logging → Recovery
如果把 Auth 放最外,401 请求连日志都不会打;把 Recovery 放内层,panic 直接崩进程。
闭包捕获配置参数时,别在循环里直接引用变量
批量注册中间件时,容易踩这个坑:
for _, m := range middlewares {
mux.Handle("/"+m.path, m.middleware(handler))
}如果
m.middleware 是闭包且内部用了 m,所有中间件最终共享最后一次迭代的 m 值。
修复方式:显式拷贝局部变量
for _, m := range middlewares {
cur := m // 关键:立刻拷贝
mux.Handle("/"+cur.path, cur.middleware(handler))
}
带状态或需并发安全的中间件,别用全局变量
比如限流器、计数器、连接池,必须封装成结构体并实现 ServeHTTP 方法:
type RateLimiter struct {
limit int
bucket map[string]int
mu sync.RWMutex
}
func (r *RateLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 加锁读写 bucket
}直接用全局
map 或 int 变量,在高并发下会 panic 或数据错乱。
真正难的不是写中间件,是写完之后能稳定跑在生产环境里——顺序错、闭包漏、状态乱、panic 没 recover,任何一个都可能让服务静默失败。

















