中间件执行顺序由注册调用顺序和嵌套结构决定,而非优先级字段:标准库需手动嵌套,框架如Gin/Fiber/Echo的Use()顺序即执行顺序,外层中间件先执行请求、后执行响应,Abort()会中断后续中间件执行。

中间件执行顺序不是靠“优先级字段”控制的,而是由注册调用顺序和嵌套包装结构决定的——写在前面的,就先执行;写在后面的,在请求阶段后进、在响应阶段先出。
Go标准库中间件必须手动嵌套,顺序极易搞反
标准 http.Handler 中间件本质是函数包装:每个中间件接收一个 next http.Handler,返回一个新的 http.Handler。执行流是外层→内层→业务 handler,但响应阶段是倒序触发(后置逻辑先执行)。
常见错误现象:logMW(authMW(handler)) 表示日志在外层、鉴权在内层,所以请求进来先打日志,再鉴权;但鉴权失败 Abort() 后,日志中间件的“结束”逻辑根本不会运行——因为 next.ServeHTTP() 没被调用。
- 想让日志覆盖整个生命周期,必须确保它是最外层包装
- 多个中间件嵌套时,顺序是「从右往左」读:最右边的最先执行前置逻辑,最左边的最后执行前置逻辑
- 别依赖 IDE 自动格式化重排,手写嵌套链要一行一中间件,避免视觉混淆
Gin/Fiber/Echo 的 Use() 调用顺序即执行顺序
这些框架底层仍是基于标准库,但封装了中间件注册机制。router.Use() 或 app.Use() 的调用顺序直接决定请求阶段的执行顺序——没有隐式排序,没有自动去重,也没有“高优先级插队”能力。
立即学习“go语言免费学习笔记(深入)”;
常见错误现象:在 router.Group("/api") 里调用 v1.Use(authMW),误以为它会“覆盖”或“替换”全局中间件;其实它只是追加到该组路由的中间件列表末尾,且一定在全局中间件之后、具体 handler 之前执行。
-
router.Use(mwA, mwB)等价于先后调用两次Use(),mwA先执行 - 路由级中间件如
GET("/x", handler, mwC)(Fiber 支持)只对当前路由生效,且在所有Use()之后、handler之前 - 如果某个中间件调用了
c.Abort()或 panic,后续所有中间件(包括同组内后注册的)都不会执行
混合使用标准库中间件和框架中间件时,外层永远先于内层
当你把 Gin 引擎作为 http.Handler 包进标准库中间件时,比如 http.ListenAndServe(":8080", stdMW(router)),整个 Gin 实例就成了 stdMW 的 next。这意味着:
请求流向是:stdMW → Gin engine → Gin.Use() 注册的中间件 → 路由 handler
响应流向是反的:handler → Gin 中间件 → Gin engine → stdMW
- 标准库中间件看不到
*gin.Context,只能操作原始*http.Request和http.ResponseWriter - Gin 中间件无法拦截
stdMW的 panic,除非你在stdMW内部做 recover - 日志时间戳若跨两层中间件,要注意系统时钟漂移或并发打印错乱,建议统一用
time.Now().UnixMicro()打点
需要动态调整顺序?得自己维护中间件切片并显式排序
框架原生不支持按数值优先级插入,但你可以绕过 Use(),改用结构体切片管理:
type Middleware struct {
Handler http.Handler
Priority int
Name string
}
var middlewares []Middleware
<p>func AddMiddleware(h http.Handler, priority int, name string) {
middlewares = append(middlewares, Middleware{Handler: h, Priority: priority, Name: name})
}</p><p>func BuildChain(final http.Handler) http.Handler {
sort.Slice(middlewares, func(i, j int) bool {
return middlewares[i].Priority < middlewares[j].Priority // 数值越小越靠外
})
for i := len(middlewares) - 1; i >= 0; i-- {
final = middlewares[i].Handler(final)
}
return final
}
这个模式下,Priority=1 的中间件会被包在最外层,Priority=100 的则最靠近 handler。但注意:一旦启用此方案,就不要再混用框架原生的 Use(),否则执行顺序不可控。
真正容易被忽略的是:中间件之间若共享状态(比如从 request context 取值),必须确保前置中间件已写入、后续中间件才读取——而这个“前后”完全取决于你排序后的切片索引,不是代码书写顺序。


















