Go HTTP中间件本质是func(http.Handler)http.Handler函数链,通过闭包组合处理器,需显式调用如loggingMiddleware(authMiddleware(handler)),顺序决定执行时机,拦截响应需return避免重复写header,数据传递依赖context.Context。
Go 标准库的 http.Handler 接口只要求实现 ServeHTTP(http.ResponseWriter, *http.Request) 方法,中间件就是满足该签名、且内部调用下一个处理器的函数。它不是框架概念,而是基于闭包和高阶函数的自然表达。
常见错误是试图“注册中间件”后自动生效——Go 没有全局中间件注册表,必须显式拼接调用链。比如 loggingMiddleware(authMiddleware(handler)) 才是正确写法,而不是期待某个 Use() 方法自动注入。
next.ServeHTTP(w, r)
next.ServeHTTP,会导致 http: multiple response.WriteHeader calls 错误func(http.Handler) http.Handler 构建可复用中间件这是最主流、最符合 Go 风格的中间件签名。它接收一个 http.Handler,返回一个新的 http.Handler,便于组合与测试。
例如日志中间件:
立即学习“go语言免费学习笔记(深入)”;
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("START %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("END %s %s", r.Method, r.URL.Path)
})
}
使用时链式包裹:
handler := http.HandlerFunc(yourHandler)
handler = loggingMiddleware(handler)
handler = authMiddleware(handler)
http.ListenAndServe(":8080", handler)
http.Handler 而非 http.HandlerFunc,否则无法兼容自定义 struct 实现的 Handlerfunc(jwtKey string) func(http.Handler) http.Handler { ... }
http.HandlerFunc 是类型别名,不是接口;它只是让函数能转成 http.Handler 的快捷方式中间件拦截请求的核心动作是:不调用 next.ServeHTTP(w, r),而是直接向 w 写入响应(status code + body)。
典型场景包括鉴权失败、限流超限、路径非法等:
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return // ← 必须 return,否则继续执行 next
}
// 验证 token...
if !isValid(token) {
http.Error(w, "Invalid token", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
http.Error 或 w.WriteHeader+w.Write 后,必须 return,否则后续逻辑可能 panic 或写出重复 header*http.Request 字段(如 r.URL.Path)后直接传给 next——它不可变;如需改写路径,应构造新 *http.Request(用 r.Clone(r.Context()))r.Body 并 defer r.Body.Close(),否则下游 handler 会读到空内容context.Context
Go 不支持类似 Express 的 res.locals 或 Django 的 request attributes,跨中间件共享数据唯一正统方式是通过 r.Context()。
例如把用户 ID 注入 context 供下游 handler 使用:
func userContextMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := extractUserID(r) // 从 token 或 cookie 解析
ctx := context.WithValue(r.Context(), "user_id", userID)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
// 在最终 handler 中获取:
func yourHandler(w http.ResponseWriter, r *http.Request) {
userID := r.Context().Value("user_id").(string) // 注意类型断言
// ...
}
type ctxKey string; const userIDKey ctxKey = "user_id"