Go HTTP 中间件
Go HTTP 中间件通过 func(http.Handler) http.Handler 模式实现请求处理链,是实现日志、认证、CORS 等横切关注点的标准方式。
#type / concept
#status / growing
#tech / dev
#resource / go
[!info] related notes
- 所属 MOC: Go Web 后端
- 前置概念: Go 路由模式
- 并列概念: Go Context
Go HTTP 中间件
一句话定义
中间件是一个接受 http.Handler 并返回新的 http.Handler 的函数,在调用下游 Handler 前后插入横切逻辑(如日志、认证、panic 恢复)。
核心机制 / 工作原理
Go 的中间件模式基于 net/http 包的核心签名:
type Middleware func(http.Handler) http.Handler
一个典型的中间件结构如下:
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r) // 调用下一个 Handler
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
链式组合:
mux := http.NewServeMux()
mux.HandleFunc("/api/users", usersHandler)
// 包装顺序:Recovery → Auth → Logging → mux
// 请求进入时:Recovery → Auth → Logging → mux
// 响应返回时:mux → Logging → Auth → Recovery
handler := RecoveryMiddleware(AuthMiddleware(LoggingMiddleware(mux)))
或者用辅助函数简化链式调用:
func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
// 反向遍历,使第一个中间件最先执行
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}
handler := Chain(mux, LoggingMiddleware, AuthMiddleware, RecoveryMiddleware)
通过 Context 传递数据:
中间件经常需要向下游 Handler 传递解析后的信息(如当前用户):
type contextKey string
const userKey contextKey = "user"
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
user, err := validateToken(token)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return // 不调用 next,请求到此终止
}
ctx := context.WithValue(r.Context(), userKey, user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// 下游 Handler 取出用户信息
func usersHandler(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value(userKey).(*User)
fmt.Fprintf(w, "Hello, %s", user.Name)
}
最小例子 / 最小场景
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic recovered: %v\n%s", err, debug.Stack())
http.Error(w, "Internal Server Error", 500)
}
}()
next.ServeHTTP(w, r)
})
}
CORS 中间件在预检请求(OPTIONS)时直接返回,不调用 next,实现了请求拦截。Recovery 中间件用 defer + recover 兜底所有 panic。
为什么重要
- 关注点分离:业务 Handler 只处理业务逻辑,认证、日志、CORS 等由中间件独立处理。
- 可复用:同一个中间件可以应用到所有路由、部分路由或单个路由。
- 可组合:中间件像管道一样叠加,顺序可调,增删不影响其他层。
- 测试友好:中间件可以独立测试——构造一个 mock Handler 验证中间件行为。
边界与易混淆点
- 中间件的执行顺序很重要:包裹顺序决定了代码的执行顺序。最外层的中间件最先执行”前置逻辑”,最后执行”后置逻辑”。
- 如果中间件不调用
next.ServeHTTP(),请求处理链就中断了——这在认证失败、限流拒绝等场景是有意为之。 http.Handler和http.HandlerFunc的关系:HandlerFunc是一个适配器,让普通函数满足Handler接口。中间件返回的几乎总是http.HandlerFunc。context.WithValue的 key 应使用未导出的自定义类型,避免不同包之间的 key 冲突。- 第三方框架(chi、echo、gin)各自定义了自己的中间件签名,与标准库不通用。选择框架意味着选择其中间件生态。