Go中间件函数签名必须是func(http.Handler) http.Handler,正确写法用http.HandlerFunc包裹闭包并转为http.Handler;提前终止需return防重复响应;读取r.Body前须缓存;跨中间件传值唯一安全方式是context.WithValue。

中间件函数签名必须是 func(http.Handler) http.Handler
Go 没有中间件语法糖,所谓“中间件”就是接收一个 http.Handler、返回另一个 http.Handler 的函数。写成 func(http.HandlerFunc) http.HandlerFunc 也能用,但限制了下游类型——比如对方是自定义 struct 实现的 http.Handler(如 chi.Mux 或 gorilla/mux.Router),你就接不上去。
常见错误包括:
-
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, ...) 就得立刻 return。否则 next.ServeHTTP(w, r) 仍会执行,可能造成重复响应、数据误写或敏感信息泄露。
立即学习“go语言免费学习笔记(深入)”;
典型错误写法:
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
// 缺少 return → 下面这行还会执行!
}
next.ServeHTTP(w, r)
更隐蔽的问题是:在 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.Body 前必须缓存,否则下游收不到数据
r.Body 是 io.ReadCloser,只能读一次。日志中间件里用 io.ReadAll(r.Body) 打印 body 后,下游 handler 再读就是空字节——这不是 bug,是设计使然。
要复用 body,得手动缓存并重置:
- 先
bodyBytes, _ := io.ReadAll(r.Body) - 再
r.Body = io.NopCloser(bytes.NewReader(bodyBytes))
注意两点:
- 生产环境慎用全量缓存,大文件或高并发易 OOM;建议只记录长度(
r.ContentLength)或采样(如前 1KB) - 别在中间件里调用
r.ParseForm()后再去读r.Body,它内部已经消费过一遍了
跨中间件传数据必须用 context.Context,别碰全局变量
想把用户 ID、请求 ID、租户信息从认证中间件传给日志或业务 handler,唯一安全方式是用 context.WithValue(r.Context(), key, val),再通过 r.WithContext() 透传下去。
错误做法:
- 用包级变量存 request-scoped 数据 → 并发下值污染
- 直接修改
*http.Request字段 → 不符合接口契约,部分框架(如 Gin)会替换原始*http.Request
示例:
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
uid := parseUIDFromToken(r.Header.Get("Authorization"))
ctx := context.WithValue(r.Context(), "user_id", uid)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// 下游可安全获取:
// uid := r.Context().Value("user_id").(string)
key 推荐用私有类型(如 type userIDKey struct{})避免字符串冲突,这是容易被跳过的细节。


















