中间件函数签名必须是func(http.Handler) http.Handler;需用http.HandlerFunc包裹闭包并返回http.Handler,鉴权失败须显式return防下游执行,跨中间件传值必须用r.WithContext(ctx)。

中间件函数签名必须是 func(http.Handler) http.Handler
Go 没有中间件语法糖,所谓“中间件”就是类型严格的函数:输入一个 http.Handler,输出另一个 http.Handler。写成 func(http.HandlerFunc) http.HandlerFunc 看似能跑,但会断掉与 chi.Mux、gorilla/mux.Router 等结构体实现的 handler 兼容性。
常见错误包括:
-
func auth(next http.Handler) { }—— 缺返回值,http.ListenAndServe直接 panic -
func auth(next http.Handler) func(http.ResponseWriter, *http.Request) { }—— 类型不匹配,无法嵌套进标准 handler 链
正确写法统一用 http.HandlerFunc 包裹闭包,再转为 http.Handler:
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)
})
}
提前终止请求时必须 return,且不能调用 next.ServeHTTP
鉴权失败、参数校验不通过、限流触发等场景下,你写了 http.Error(w, "Unauthorized", http.StatusUnauthorized) 就得立刻 return。否则 next.ServeHTTP(w, r) 仍会执行,可能造成重复响应、数据误写或敏感信息泄露。
立即学习“go语言免费学习笔记(深入)”;
更隐蔽的问题是:在 next.ServeHTTP() 之后再写 http.Error(),此时 response header 可能已 flush,触发 http: multiple response.WriteHeader calls panic。
建议封装一个工具函数:
func writeError(w http.ResponseWriter, status int, msg string) {
if w.Header().Get("Content-Type") == "" {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
}
http.Error(w, msg, status)
}
修改请求上下文必须用 r.WithContext(ctx),不能只改 ctx
很多人以为 r.Context() 天然贯穿整条链,其实不然:中间件里调用 next.ServeHTTP(w, r) 却没更新 r,后续中间件拿到的就是原始请求对象,WithValue 写入的内容就丢了。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
典型错误写法:
func BadAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "user_id", 123) // ❌ 错:没把新 ctx 绑回 *http.Request
next.ServeHTTP(w, r) // 这里 r.Context() 还是旧的
})
}
正确做法必须用 r.WithContext(ctx):
func GoodAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "user_id", 123)
next.ServeHTTP(w, r.WithContext(ctx)) // ✅
})
}
更安全的方式是封装一个工具函数,避免每次手写 WithContext。
带配置或状态的中间件必须用结构体,别滥用闭包和全局变量
需要配置参数(如超时时间、白名单 IP)或维护内部状态(如计数器、连接池)时,函数闭包容易失控。比如把 bucket map[string]int 放全局,结果并发写 panic 或数据错乱。
Go 的 HTTP server 默认多 goroutine 并发处理请求,任何共享状态都必须加锁或用 sync.Map。
推荐封装成结构体:
type rateLimiter struct {
limit int
bucket map[string]int
mu sync.RWMutex
}
func (r *rateLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 实现限流逻辑
}
注意:rateLimiter 方法接收指针,bucket 字段必须在结构体内初始化并加锁访问;若用 sync.Pool 复用结构体实例,还要重置内部 slice 长度,不能只清空内容。


















