用http.Handler实现中间件链最安全,应避免全局注册;中间件函数接收并返回http.Handler,按洋葱模型从右往左组合;需包装ResponseWriter捕获状态码和body;必须用context.Context透传请求数据;推荐模块化管理中间件。

用 http.Handler 实现中间件链,别碰 net/http 的全局注册
Go 没有“中间件”原生概念,但 http.Handler 接口天然支持链式封装。业务里直接改 http.ListenAndServe 的 handler 参数最安全,避免用 http.HandleFunc 或全局 http.DefaultServeMux——后者会让拦截逻辑和路由耦合,测试难、替换难、debug 时找不到谁注册了啥。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有中间件写成函数,接收
http.Handler返回http.Handler,比如func(loggingMiddleware http.Handler) http.Handler - 按执行顺序从右往左套:
loggingMiddleware(authMiddleware(routeHandler)),越靠右的越先执行(类似洋葱模型) - 不要在中间件里调用
http.ServeHTTP后还继续写逻辑,除非你明确要短路(比如鉴权失败直接w.WriteHeader(401)并 return)
拦截请求前/后:用 ResponseWriter 包装器捕获状态码和 body
想记录响应耗时、状态码或修改 header,必须包装 http.ResponseWriter。原生接口不暴露状态码,直接写 w.WriteHeader(200) 后拿不到值。常见错误是只包装 WriteHeader 忽略 Write,导致流式响应(如 JSON streaming)的状态码取错。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 实现一个结构体嵌入
http.ResponseWriter,重写WriteHeader记录状态码,重写Write记录字节数(如果需要) - 注意
WriteHeader可能被多次调用,只取第一次有效值;Go 1.22+ 允许WriteHeader被覆盖,但业务中间件应以首次为准 - 别在包装器里做耗时操作(如写日志到磁盘),会拖慢整个请求链;优先打到内存 buffer 或异步 channel
用 context.Context 透传请求级数据,别用全局变量或闭包捕获
中间件之间传用户 ID、trace ID、租户信息,必须走 context.WithValue。闭包捕获变量看似简单,但会导致 handler 复用时数据污染(比如两个并发请求共享同一中间件实例的局部变量);全局 map 加锁又违背 Go 的并发哲学。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 定义带类型的 key,比如
type ctxKey string; const userIDKey ctxKey = "user_id",避免字符串 key 冲突 - 在中间件中用
r = r.WithContext(context.WithValue(r.Context(), userIDKey, userID))注入,下游用userID := r.Context().Value(userIDKey)取 - 不要把大结构体塞进 context,只放轻量标识符;context 是传递元数据的,不是对象容器
模块化中间件:每个功能单独 go.mod,用 replace 本地调试
业务增长后,日志、熔断、限流、审计等中间件会分散在不同团队维护。硬编码在主项目里会导致升级困难、版本冲突、无法灰度。Golang 模块机制正好用来解耦。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 每个中间件建独立仓库,
go mod init github.com/yourorg/mw-logging,导出干净函数如Logging(next http.Handler) http.Handler - 主项目
go.mod中用require github.com/yourorg/mw-logging v0.1.0,本地调试时加replace github.com/yourorg/mw-logging => ./internal/mw-logging - 注意
replace只影响当前 module,子 module 不自动继承;多层依赖时,用go list -m all | grep mw-确认实际加载版本
中间件真正的复杂点不在写法,而在生命周期管理:超时控制、panic 恢复、context 取消传播、以及和第三方库(比如 gorilla/mux)的兼容性。这些细节一旦漏掉,问题往往出现在压测或上线后,而不是本地跑通那一刻。

















