fiber 默认不捕获错误,需手动添加 fiber.recover() 和兜底中间件处理 panic 与 404;响应一旦写入即 committed,不可再调 ctx.next();结构体 json 输出须加 json tag;recover 中不可重复写响应。

fiber.New() 里没配错误处理中间件,404 和 panic 就直接暴露给用户
Fiber 默认不带任何错误捕获逻辑,fiber.New() 启动的 app 是“裸奔”状态。访问一个没注册的路由,返回的是空响应体 + 404 状态码,没有统一格式;handler 里 panic 了,服务直接 crash,连日志都不留。
必须手动加 fiber.Recover() 拦住 panic,再配一个 app.Use(func(c *fiber.Ctx) error { ... }) 兜底未匹配路由。别指望框架自动给你补上。
-
fiber.Recover()要在所有其他中间件之前注册,否则 panic 会提前终止链路 - 兜底 404 中间件得放在
app.Use()最末尾,确保它只在所有路由都失配后才触发 - 别用
fiber.Default()替代——它自带的 logger 会把 panic 堆栈打满屏,线上环境不可控
ctx.Status(500).JSON(...) 后还调 ctx.Next(),后续 handler 照样被跳过
这是最常被误解的操作:ctx.Status()、ctx.SendString()、ctx.JSON() 这些方法一旦调用,就代表响应已开始写入,Fiber 内部标记为 “committed”。此时再调 ctx.Next(),调度器直接忽略,后续所有 handler 都不会执行。
典型翻车场景:权限中间件里判断失败,写了 c.Status(401).JSON(...),接着又写 c.Next(),以为能继续走日志或监控中间件——实际全丢弃。
- 拒绝请求就
return,别碰ctx.Next() - 放行才调
ctx.Next(),且必须确保前面没写过响应 - 调试时在每个中间件开头加
log.Printf("in %s", "xxx"),看哪一环之后没了输出,就能定位是否提前 committed
自定义错误响应结构体字段没加 json tag,前端收到空对象
你写了 return c.Status(400).JSON(fiber.Map{"error": "invalid id"}) 没问题,但若换成结构体:type ErrorResponse struct { Msg string },然后 c.JSON(ErrorResponse{Msg: "invalid id"}),前端大概率收到 {}。
因为 Go 默认导出字段首字母大写,但 JSON 序列化需要显式 json:"msg" tag,否则字段被忽略。Fiber 不做 magic,它只是调 json.Marshal。
- 所有用于 JSON 输出的结构体,每个字段都要加
jsontag,哪怕值是空字符串也要写json:"msg,omitempty" - 别依赖
fiber.Map临时应付——它适合原型,不适合生产 API 的错误契约 - 如果用 Viper 统一配置错误码和文案,记得结构体字段名和 Viper key 保持一致,避免拼错导致零值
panic 恢复后没重置状态,同一个 ctx 多次写响应会 panic
fiber.Recover() 捕获 panic 后,ctx 对象仍处于“已写头”或“已写体”的中间态。如果你在 recover 中又调了一次 c.Status(500).JSON(...),底层 fasthttp 会报 header already written 并再次 panic。
这不是 Fiber bug,是 fasthttp 的底层约束:一个 Ctx 只能写一次响应。
- recover 中不要尝试重写响应,而是立即
return c.Status(500).SendString("server error")—— 单次写完就 return - 想记录 panic 日志?用
defer func() { if r := recover(); r != nil { log.Printf("panic: %v", r) } }()在 handler 内部自己包一层,比依赖全局 recover 更可控 - 真正要复用错误处理逻辑,抽成函数传
*fiber.Ctx和 error,别在 recover 里再造业务分支











