chain(m1, m2, m3).then(h)等价于m1(m2(m3(h))),因正确链式实现需倒序遍历切片,确保m1最外层、最先执行;若正序遍历则顺序颠倒,导致日志漏记、鉴权失效。

Go标准库不提供 Chain 函数,但中间件链的执行顺序和组合方式直接决定请求是否被正确拦截、日志是否漏记、鉴权是否失效——写反了就等于没加中间件。
为什么 Chain(m1, m2, m3).Then(h) 不等于 m1(m2(m3(h)))
关键在参数展开方向:多数手写 Chain 实现(比如从切片尾部开始遍历)会把 m1 放在最外层,即等价于 m1(m2(m3(h)))。这符合“先日志、再鉴权、最后业务”的预期顺序。
但如果你用 for i := 0; i 正向遍历,就会变成 <code>m3(m2(m1(h))),导致鉴权在日志之前执行,甚至业务 handler 先跑完才进日志中间件。
- 调试时在每个中间件开头加
log.Println("→ in", "m1"),看输出顺序是否与你声明的顺序一致 - 社区轻量库如
alice明确约定alice.New(m1, m2, m3).Then(h)是正序组装 - 自己实现时别依赖“看起来像链表”,重点看
next = ms[i](next)中i是递增还是递减
ResponseWriter 被多次 WriteHeader 的真实原因
不是中间件写了两次 WriteHeader,而是下游中间件不知道上游已经发过状态码。Go 的 http.ResponseWriter 接口没有 Written() 方法,也没暴露当前状态,所以容易误判。
- 所有需要改写响应体或 header 的中间件(如压缩、CORS、错误统一包装),必须用包装型
ResponseWriter,且重写WriteHeader时记录已写状态 - 典型错误模式:
authMW调用http.Error(w, ...)后返回,loggingMW还试图在next.ServeHTTP后写 header,触发http: multiple response.WriteHeader calls - 解决方案:用
gorilla/handlers.CompressHandler这类成熟封装,或自己实现时加字段written bool+ 互斥锁(高并发下不可省)
Context 传值必须用 WithValue,不能靠闭包或全局变量
多个中间件之间共享数据(如用户 ID、请求 traceID)看似简单,但用错方式会导致数据污染或丢失。
- 闭包捕获的变量是共享的,一个请求还没结束,下一个请求就可能覆盖它;goroutine 间也不安全
- 正确做法是调用
r = r.WithContext(context.WithValue(r.Context(), key, value)),然后在下游用r.Context().Value(key)取 - key 必须是自定义类型(如
type userIDKey struct{}),避免字符串 key 冲突 - 不要把敏感信息(如 token 原文)塞进 context,只放必要标识符
真正难的不是写出 Chain,而是每层中间件都得想清楚:我该在什么时候中断?中断后要不要清理 context?响应头冲突了谁负责兜底?这些边界问题不会报编译错误,但上线后查起来要命。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











