Go中链式处理请求最直接方式是用http.Handler接口组合中间件,签名应为func(http.Handler) http.Handler,需显式调用next.ServeHTTP(w, r),并通过context.Context安全传递数据。

Go 中用 http.Handler 实现请求链式处理最直接
标准库的 http.Handler 接口天然支持链式组合,不需要自己造轮子。核心是让每个中间件返回一个新的 http.Handler,把下一个处理器作为闭包变量捕获进去。
常见错误是直接在中间件里调用 next.ServeHTTP() 却忘了传入原始的 http.ResponseWriter 和 *http.Request —— 这会导致后续 handler 拿不到请求上下文或写响应失败。
- 中间件函数签名应为
func(http.Handler) http.Handler,不是func(http.ResponseWriter, *http.Request) - 必须显式调用
next.ServeHTTP(w, r),否则链就断了 - 若需修改
*http.Request(如加 header、解析 body),要用r.WithContext()或r.Clone(),原生*http.Request是不可变的
用 net/http 自带的 http.HandlerFunc 避免类型转换麻烦
手写 struct 实现 http.Handler 接口容易漏掉 ServeHTTP 方法签名细节,而 http.HandlerFunc 是函数类型别名,自带转换能力,写起来更轻量。
典型场景:日志中间件、鉴权检查、CORS 设置——这些都不需要持久状态,纯函数就够了。
立即学习“go语言免费学习笔记(深入)”;
- 定义中间件时直接返回
http.HandlerFunc匿名函数,内部调用next.ServeHTTP(w, r) - 不要试图在中间件里 return 一个新
http.ResponseWriter,Go 的http.ResponseWriter不可替换,只能包装 - 如果真要拦截响应体(比如压缩、重写 HTML),得用
httptest.ResponseRecorder或自定义ResponseWriter实现,但这是少数情况
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("REQ: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r) // 必须这句,否则链中断
})
}
多个中间件顺序错位导致逻辑失效
Go 的中间件执行顺序和注册顺序严格一致,但「包裹方向」是反的:最后注册的中间件最先执行。这和 Express/Koa 的 use() 直觉相反,容易踩坑。
比如你写了 auth → logging → handler,实际执行流是:auth 先拦截请求,再进 logging,最后到业务 handler;但 response 是反过来:handler 写完响应后,logging 才能记录耗时,auth 才能清理 context。
- 鉴权类中间件(如 JWT 校验)必须放在最外层,否则后面中间件可能拿到未认证的
r.Context() - 依赖请求 body 的中间件(如 JSON 解析)要放在读取 body 的 handler 之前,且注意 body 只能读一次
- 调试时可在每个中间件开头加
log.Printf("in %s", "name")和结尾加log.Printf("out %s", "name")看进出顺序
用 context.Context 在链中传递数据,别用全局变量或闭包捕获
中间件之间共享数据(如用户 ID、请求 ID)必须通过 r.Context(),而不是靠闭包变量或包级变量。后者在并发请求下会互相污染。
标准做法是用 context.WithValue() 注入键值对,下游用 ctx.Value(key) 取。键建议用私有类型避免冲突。
- 定义 key 类型:
type ctxKey string; const userCtxKey ctxKey = "user" - 注入:
r = r.WithContext(context.WithValue(r.Context(), userCtxKey, userID)) - 取出:
userID := r.Context().Value(userCtxKey).(string)(注意类型断言安全) - 别用
string当 key,不同包可能撞 key 名;也别用interface{}做 key,Go 1.21+ 会报 warning
context 传递和 Request 复制的成本越明显;高频服务里,非必要不层层 WithCancel 或 WithValue。真正关键的字段才放 context,其余走局部变量。










