Go HTTP中间件是接收并返回http.Handler的函数,通过闭包在原handler前后插入逻辑;需调用next.ServeHTTP以确保链式执行,顺序影响执行流程,配置应作为参数传入避免共享,panic恢复中间件须置于最外层。
Go 的中间件本质是函数套函数:接收一个 http.Handler,返回一个新的 http.Handler,在调用原 handler 前后插入逻辑。它不是框架内置概念,而是基于 http.Handler 接口和闭包的自然延伸。
核心是实现 func(http.Handler) http.Handler 签名。注意别漏掉 next.ServeHTTP(w, r),否则请求就断了。
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Started %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r) // 必须调用,否则下游 handler 不会执行
log.Printf("Completed %s %s", r.Method, r.URL.Path)
})
}
使用时链式包装:
handler := http.HandlerFunc(yourHandler)handler = loggingMiddleware(handler) handler = authMiddleware(handler) // 可叠加多个 http.ListenAndServe(":8080", handler)
中间件本身可以接受配置参数,返回“中间件工厂函数”。常见错误是把配置写死在闭包外,导致所有实例共享同一份配置。
allowedOrigins 应作为参数传入,而非全局变量CORS() 调用返回独立的中间件实例w.Header().Set() 必须在 next.ServeHTTP 之前设置响应头,否则可能被覆盖func CORS(allowedOrigins []string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
if len(allowedOrigins) == 0 || contains(allowedOrigins, origin) {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
}
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
}
func contains(slice []string, s string) bool {
for _, v := range slice {
if v == s {
return true
}
}
return false
}
中间件从外到内执行:第一个包装的最先运行,最后一个包装的最后运行(但它的前置逻辑在 next.ServeHTTP 前,后置逻辑在之后)。比如:
loggingMiddleware(authMiddleware(handler)):先打日志 → 再鉴权 → 再执行 handler → 再打完成日志next.ServeHTTP 不会被调用,后续中间件和 handler 都不会执行容易忽略的是:中间件内部的 http.Error 或直接写 w.WriteHeader 后再调用 next.ServeHTTP,可能导致重复写 header 报错 http: multiple response.WriteHeader calls。