中间件注册顺序即执行顺序,Fiber不自动重排:请求时按app.Use()顺序执行,响应时逆序;全局中间件优先于路由组和路由级中间件;路由级中间件在全局之后、handler之前;前缀匹配为精确前缀;运行时需手动构造函数链实现动态顺序。

中间件注册顺序就是执行顺序
Fiber 不会自动重排中间件,app.Use() 调用的先后顺序,直接决定请求进入时的执行顺序。比如你写了:
app.Use(logger.New())
app.Use(authMiddleware)
app.Get("/api/user", handler)
那每次请求进来,一定是先跑 logger.New(),再进 authMiddleware,最后才到 handler;响应返回时则逆序:handler → authMiddleware(返回阶段)→ logger(返回阶段)。
常见错误现象:panic: middleware A tried to access context value set by B, but B hasn't run yet——本质是把依赖 B 设置 ctx 值的 A 放在了 B 前面注册。
- 想让日志在鉴权之后打?不行,必须把
app.Use(authMiddleware)写在app.Use(logger.New())之前 - 想让 CORS 在静态资源中间件之前生效?那就得把
cors.New()挂在更靠前的位置,而不是塞在static.New()后面 - 全局中间件永远比路由组中间件先执行,哪怕你把
v1 := app.Group("/v1")写在app.Use()前面也没用
路由级中间件在全局之后、Handler之前运行
用 app.Get("/path", mw, handler) 这种方式传入的中间件,只对当前路由生效,且执行时机固定:在所有全局中间件之后、该路由的 handler 之前。
例如:
app.Use(recover.New())
app.Use(logger.New())
app.Get("/admin", authMiddleware, func(c *fiber.Ctx) error {
return c.SendString("secret")
})
执行链是:recover → logger → authMiddleware → handler。
注意:authMiddleware 的签名必须是 func(*fiber.Ctx) error,否则编译不报错但运行时 panic;它不能返回 nil 就完事,得显式 return nil 或 return c.Next()(虽然 c.Next() 在路由级中间件里其实等价于放行)。
- 别在路由级中间件里调
c.Next()后还写逻辑——Fiber 已把控制权交出去,后续 handler 执行完才会回来 - 如果鉴权失败,直接
c.Status(401).SendString("no")+return,千万别c.Next(),否则 handler 还会执行 - 多个路由级中间件按参数顺序执行:
app.Get("/x", a, b, c, h)→ a → b → c → h
避免用 app.Use() 前缀挂载导致作用域误判
app.Use("/api", cors.New()) 看似是“给 /api 下所有路由加 CORS”,但它实际含义是:“只对路径以 /api 开头的请求触发此中间件”,且它**不会自动向下透传到子中间件或嵌套路由**。
典型陷阱:
-
app.Use("/api", cors.New())对/api/v1/users生效,但对/health或/完全不生效——这没问题;但如果你又写了app.Use(logger.New())(无前缀),它就会覆盖所有路径,包括/favicon.ico,日志刷屏 -
app.Use(static.New("./public"))(没带前缀)会让所有请求(比如/login)先被 static 尝试匹配,找不到才进路由,性能明显下降 -
app.Use("/assets", static.New("./public"))是正确写法,但要注意:若前端请求/assets/logo.png,Fiber 会去读./public/logo.png;而/assets/默认不返回目录列表,除非你显式设static.Config.Browse = true
前缀匹配不是“模糊包含”,而是“精确前缀或斜杠边界”:/user 能匹配 /user 和 /user/profile,但不匹配 /users(后面不是 / 或结尾)。
动态调整顺序只能绕过 app.Use() 自己构造链
框架原生不支持运行时重排中间件。如果你需要根据配置开关某中间件,或者按环境切换顺序,别碰 app.Use() 链式调用——它在启动时就固化了。
正确做法是手动拼函数链,再一次性注入:
var chain []fiber.Handler
if cfg.EnableAuth {
chain = append(chain, authMiddleware)
}
if cfg.EnableRateLimit {
chain = append(chain, limiter.New())
}
chain = append(chain, handler)
app.Get("/api/data", chain...)
或者更彻底地封装成工厂函数:
func buildHandler(cfg Config) fiber.Handler {
h := handler
if cfg.EnableAuth {
h = authMiddleware(h)
}
if cfg.EnableCompress {
h = compress.New()(h)
}
return h
}
app.Get("/api/data", buildHandler(cfg))
这种写法完全脱离 app.Use(),规避了注册顺序不可变的问题,也更容易做单元测试。
最易被忽略的一点:Fiber 的中间件链是**同步执行、不可中断的**。一旦某个中间件 panic 或没 return,后续中间件和 handler 都可能跳过,而 recover 中间件未必能兜住——尤其在 defer 里操作数据库事务时,tx 若没通过 ctx.Locals 绑定,panic 发生后 rollback 就失效了。











