go http中间件必须返回http.handler或接收http.handlerfunc,标准签名为func(http.handler) http.handler;执行顺序呈洋葱模型,最外层最先处理请求、最后处理响应,panic恢复中间件须置于最外层。

中间件函数签名必须返回 http.Handler 或接受 http.HandlerFunc
Go 的 HTTP 中间件本质是“包装”处理器的函数,不是独立的拦截器。如果你写了一个不返回 http.Handler 或不接收 http.HandlerFunc 的函数,它就不是合法中间件,无法参与链式调用。
常见错误现象:cannot use middleware (type func(http.Handler) http.Handler) as type http.Handler——说明你直接把中间件当 handler 用了,没套在最终 handler 上。
- 标准中间件签名是
func(http.Handler) http.Handler,比如日志中间件:func logging(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.URL.Path) next.ServeHTTP(w, r) }) } - 也可以用
func(http.HandlerFunc) http.HandlerFunc,更轻量,但要注意:它不能直接传给http.ListenAndServe,必须先转成http.Handler(比如用http.HandlerFunc(...)包一层) - 别在中间件里漏掉
next.ServeHTTP(w, r),否则请求就断在那了,下游中间件和最终 handler 完全不会执行
用 http.Handler 链式组合时顺序决定执行流向
中间件调用顺序和实际执行顺序是「外层先入、内层先出」,类似洋葱模型。最外层中间件最先拿到请求,也最后拿到响应;最内层(即最终 handler)在中间件层层包裹后才执行。
使用场景:比如你想先校验 token,再限流,再打日志,最后进业务逻辑,那组合顺序必须是 auth(middleware(limit(log(handler)))),而不是反过来。
-
auth最外层:请求进来第一关,没 token 直接返回 401,根本不会进里面 -
limit第二层:通过 auth 后才检查是否超频 -
log倒数第二层:记录完整处理过程(含可能的 panic 捕获) -
handler最内层:真正处理业务,它的ServeHTTP是整个链的终点 - 错误容易踩:把
log(auth(handler))写成auth(log(handler)),会导致 token 错误时日志里没有 method/path 信息(因为 log 还没执行到)
用 gorilla/mux 或 chi 时中间件注册方式不同
标准库 net/http 没有内置中间件注册机制,得手动嵌套;而 gorilla/mux 和 chi 提供了 .Use() 或 .With() 方法,但底层仍是函数嵌套,只是语法糖。
参数差异:比如 chi 的 router.Use(mw1, mw2, mw3) 等价于 mw1(mw2(mw3(handler))),顺序和手写一致;但 gorilla/mux 的 r.Use() 是按注册顺序从左到右叠加,效果一样。
-
chi支持路由级中间件:r.With(auth).Get("/admin", adminHandler),只对这个路由生效 -
gorilla/mux不支持单路由中间件,只能靠子路由器:adminRouter := r.PathPrefix("/admin").Subrouter(); adminRouter.Use(auth) - 性能影响:第三方路由库的
.Use()会多一次函数调用开销,但可忽略;真正影响性能的是中间件内部逻辑(比如 DB 查询、加解密),不是包装本身
panic 恢复中间件必须放在最外层
Go HTTP 处理器中一旦 panic,整个 goroutine 会崩溃,连接直接断开。想统一捕获并返回 500,恢复中间件必须包在整个链最外侧,否则内层 panic 会跳过它。
常见错误现象:把 recover 中间件写在 log 里面,结果 auth 或 limit 里 panic 了,log 根本没机会运行,自然也进不到 recover 分支。
- 正确写法:
func recoverer(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) log.Printf("PANIC: %+v", err) } }() next.ServeHTTP(w, r) }) } - 务必确保它是第一个被调用的中间件:
recoverer(auth(limit(log(handler)))) - 注意:
http.Error发送后不能再写 body,所以后续中间件如果尝试写 response,会 panic(http: superfluous response.WriteHeader)——这也是为什么恢复中间件要最外层:它得兜住所有可能的写操作异常
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











