go中无装饰器语法,中间件链本质是func(http.handler) http.handler函数的嵌套组合,执行呈洋葱模型:外层先处理请求、后处理响应,且必须显式调用next.servehttp(w, r)才能延续链路。

Go 里没有装饰器语法,别被 Python 思维带偏
Go 语言本身不支持类似 Python 的 @decorator 语法,所谓“装饰器模式”在 Go 中实际是函数式中间件链(middleware chain)的惯用写法——本质是高阶函数套用。强行套用“装饰器”概念,容易误以为能像 Python 那样在方法上加标签,结果卡在语法报错或运行时 panic 上。
常见错误现象:cannot use xxx as type http.Handler、missing return at end of function、中间件顺序错乱导致 next.ServeHTTP 没被调用。
- 所有中间件必须接收
http.Handler并返回http.Handler(或等价的函数签名) - 包装逻辑必须显式调用
next.ServeHTTP(w, r),漏掉就等于中断请求流 - 中间件执行顺序和注册顺序相反:先注册的后执行(类似洋葱模型外层→内层)
标准中间件函数签名怎么写才不出错
最稳妥的写法是统一用 func(http.Handler) http.Handler 类型,避免混用 func(http.ResponseWriter, *http.Request) 匿名函数导致类型不匹配。
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)
})
}
- 用
http.HandlerFunc显式转换,确保返回值可被http.ServeMux或chi.Router接受 - 不要在中间件里直接写
w.WriteHeader()后还调用next.ServeHTTP,否则可能触发http: multiple response.WriteHeader calls - 若需修改响应头或状态码,应在
next.ServeHTTP之后读取/重写(需用 ResponseWriter 包装器,如ResponseWriter装饰)
chi 和 Gin 的中间件注册方式差异很大
chi 使用 r.Use() 注册全局中间件,Gin 用 r.Use() 或 group.Use(),但底层行为不同:chi 的 Use() 是追加到路由树节点,Gin 的 Use() 是累积到引擎或组的 handlers 切片。混淆会导致中间件只对部分路由生效。
- chi 中,
r.Use(A, B)等价于每个路由都套A(B(handler));但若在子路由上调sub.Use(C),则只有该子路由走 C - Gin 中,
router.Use(A)影响全部路由;group.Use(B)只影响该 group 下路由,且 B 会在 A 之后执行(即 A → B → handler) - 切勿把 chi 的中间件直接塞进 Gin 的
Use()——类型不兼容:func(http.Handler) http.Handler不能当func(*gin.Context)用
中间件里想透传业务数据,别用全局变量
常见错误是用包级变量存用户 ID 或请求上下文,结果并发请求互相覆盖。正确做法是通过 context.Context 传递键值对,且 key 类型必须是自定义类型(防止和其他中间件 key 冲突)。
type ctxKey string
const UserIDKey ctxKey = "user_id"
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := extractUserID(r.Header.Get("Authorization"))
ctx := context.WithValue(r.Context(), UserIDKey, userID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
- key 不能用字符串字面量,必须是未导出的自定义类型(如
ctxKey),否则不同中间件可能意外覆盖同一 key - 下游 handler 里用
userID, ok := r.Context().Value(UserIDKey).(string)安全断言,不能直接强转 - 不要在中间件里修改原始
*http.Request字段(如r.URL.Path),应通过 context 或新建 request 透传
真正麻烦的不是写中间件,而是调试它——比如某个中间件没生效,往往是因为注册位置不对、类型不匹配、或者忘了调用 next.ServeHTTP。建议每次加新中间件后,用 curl + -v 看响应头变化,再逐层加 log 打印执行路径。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











