中间件函数签名必须是func(http.handler) http.handler,因其是go标准库链式组合的唯一契约,确保类型兼容、显式调用next.servehttp、支持闭包传参及context安全传值。

中间件函数签名必须是 func(http.Handler) http.Handler
Go 的 HTTP 中间件本质是装饰器,接收一个 http.Handler,返回一个新的 http.Handler。不是 func(http.ResponseWriter, *http.Request) —— 那是 handler 本身,不是中间件。
常见错误是直接返回 http.HandlerFunc 而没包装原 handler,导致链路中断:
// ❌ 错误:没调用 next.ServeHTTP,请求到这就停了
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Request: %s %s", r.Method, r.URL.Path)
// 忘了这一行 → 请求不会继续往下走
})
}
正确写法必须显式调用 next.ServeHTTP(w, r):
- 所有中间件都需在逻辑末尾或条件分支中确保
next.ServeHTTP(w, r)被执行(除非有意终止) - 若中间件要修改响应,需用
ResponseWriter包装器(如自定义responseWriter结构体) - 注意:Go 1.22+ 支持
http.HandlerFunc直接转为http.Handler,但签名契约不能破
如何让中间件支持配置参数(比如日志前缀、超时秒数)
中间件工厂函数比裸中间件更实用——它返回中间件,而不是中间件本身。这样能传参、复用、避免闭包变量污染。
例如带前缀的日志中间件:
func WithPrefix(prefix string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("[%s] %s %s", prefix, r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
}
// 使用:
http.ListenAndServe(":8080", WithPrefix("API")(r))
- 参数应在工厂函数里捕获,而非在中间件闭包内从外部变量读取(易引发竞态或配置错乱)
- 配置结构体比多个字符串/整型参数更可维护,尤其当参数 >3 个时
- 不要在工厂函数里做耗时初始化(如连接 DB),应提前做好并传入;中间件函数体应轻量
多个中间件串联时顺序很关键
Go 没有内置中间件栈,靠函数嵌套实现顺序。最外层中间件最先执行「进入」逻辑,最后执行「退出」逻辑(类似洋葱模型)。
比如 recovery → logging → auth → handler,实际调用链是:
recovery(logging(auth(handler)))
这意味着:
-
auth在logging之后执行,所以未通过鉴权的请求不会被记日志(符合预期) - 但
recovery包裹最外层,才能捕获logging或auth中 panic - 如果把
logging放最外层,它会记录所有请求(包括 401/403),可能不符合审计要求 - 用辅助函数(如
chain.Middleware)可以扁平化写法,但底层仍是嵌套,顺序不能错
中间件里读取 request body 会破坏后续读取
r.Body 是单次读取流,中间件里 io.ReadAll(r.Body) 后,下游 handler 再读就是空的。
解决方法只有两个:
- 用
r.Body包装器(如http.MaxBytesReader或自定义readCloser)并重置r.Body,但需复制 body 到内存或临时文件(注意大文件和内存安全) - 约定上游中间件不消费 body,只解析 header / URL / query,真正读 body 留给最终 handler
- 若必须解析 JSON body 做鉴权(如签名验证),建议用
io.NopCloser(bytes.NewReader(buf))替换r.Body,且 buf 必须是完整副本
这个坑几乎每个写过 body 解析中间件的人都踩过,而且错误表现是下游 handler 返回空数据或 EOF,不容易定位。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











