Fiber中间件必须是返回error的func(*fiber.Ctx)函数,需用c.Next()显式调用后续处理,错误传播和执行时机依赖该签名与调用方式。

中间件函数必须返回 func(*fiber.Ctx) error
Go Fiber 的中间件本质就是一个接收 *fiber.Ctx 并返回 error 的函数。如果函数签名写成 func(fiber.Ctx) 或漏掉 error 返回值,编译会直接报错:cannot use … (type func(*fiber.Ctx)) as type fiber.Handler。Fiber 要求中间件能参与错误传播链,所以 error 不可省略。
常见写法:
func AuthMiddleware() fiber.Handler {
return func(c *fiber.Ctx) error {
token := c.Get("Authorization")
if token == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "missing token"})
}
// 验证逻辑...
return c.Next() // 继续后续 handler
}
}
- 必须用
c.Next()显式调用下一个中间件或路由 handler,不写就中断流程 - 不能在
return c.Next()后再写逻辑(除非用 defer),因为该行已返回 - 若想终止请求并返回响应,直接
return c.JSON(...)或return c.SendStatus(...),不要加c.Next()
全局中间件和路由级中间件的注册方式不同
全局中间件对所有路由生效,用 app.Use(...);路由级中间件只作用于某组路径,用 app.Get("/path", middleware, handler) 或 app.Group(...).Use(...)。
-
app.Use(AuthMiddleware()):所有请求(包括静态文件、OPTIONS 预检)都会过这个中间件 -
app.Post("/api/login", LoginHandler):不经过AuthMiddleware,适合免鉴权接口 -
api := app.Group("/api"); api.Use(AuthMiddleware()); api.Get("/user", UserHandler):仅/api/*下的路由受控
注意:app.Use() 注册的中间件也匹配静态路由(如 /public/*),若不想校验图片/CSS,得用 app.Static() 单独挂载,并确保它在 app.Use() 之前注册——顺序很重要。
中间件里读取请求体要小心 c.Body() 的一次性消耗
Fiber 的 c.Body() 是底层 io.ReadCloser 的一次读取,调用后 body 流就被消耗掉了。如果多个中间件(比如日志中间件 + 解析中间件)都调 c.Body(),第二个会读到空字节。
- 正确做法:统一由一个中间件调用
c.Body()并缓存到c.Locals,例如:c.Locals("rawBody", c.Body()) - 或者改用
c.Request().Body()的拷贝方式(需手动req.Body.Copy(),但 Fiber 不直接暴露该方法) - 更稳妥的是:用
c.BodyParser(&v)直接解析进结构体,它内部已处理流重放逻辑 - 调试时打印
string(c.Body())后,后续c.FormValue()或c.Query()可能异常,这是典型征兆
自定义中间件中调用 c.Next() 前后逻辑执行时机易混淆
c.Next() 不是“异步等待”,而是同步调用后续 handler,并等其返回后才继续执行 c.Next() 后面的代码。这决定了“前置逻辑”和“后置逻辑”的写法。
func LoggerMiddleware() fiber.Handler {
return func(c *fiber.Ctx) error {
// ← 这里是“进入时”执行(前置)
start := time.Now()
err := c.Next() // ← 阻塞等待路由 handler 执行完毕
// ← 这里是“退出时”执行(后置),此时 response 已写入但未发送
latency := time.Since(start)
log.Printf("%s %s %v", c.Method(), c.Path(), latency)
return err // 必须返回 err,否则上层无法感知 handler 是否出错
}
}
- 若在
c.Next()前修改了c.Response().Set(...),下游 handler 仍可覆盖 - 若在
c.Next()后读c.Response().StatusCode(),拿到的是最终状态码;但此时响应体可能已被写入,不能再调c.JSON() - 想做响应体劫持(如压缩、脱敏),得用
c.Context().SetBodyStreamWriter,不是简单包裹c.Next()
中间件嵌套越深,c.Next() 的控制流越容易误判——建议每个中间件只专注一件事,避免在单个中间件里混写前后逻辑。


















