c.Locals 不是线程安全的全局状态,仅限单请求生命周期内使用,因 Ctx 来自 sync.Pool 而可能被复用,残留数据会导致越权等竞态问题;应每次初始化、显式传参、避免跨 goroutine 使用,并优先采用依赖注入替代全局变量。

不能管理全局状态,也不该共享上下文——Fiber 的 Ctx 是复用对象,不是线程安全的“上下文容器”,直接存全局或跨 handler 传引用必出竞态。
为什么 c.Locals 不是安全的“全局状态”
c.Locals 只是当前请求生命周期内、单次 Ctx 实例上的 map,它不跨请求、不跨协程、不跨 goroutine。但问题在于:Ctx 来自 sync.Pool,用完归还后可能被下一个请求复用。如果你在中间件里写 c.Locals["user"] = user,又没在 handler 结束前清空或重置,下个请求拿到这个 Ctx 时可能读到残留的旧值。
- 典型翻车:鉴权中间件设
c.Locals["role"] = "admin",但后续 handler panic 导致没走完流程,Ctx归还池中;下一请求恰好拿到它,c.Locals["role"]还是"admin",越权了 - 解决办法不是加锁,而是每次 handler 入口显式初始化关键字段:
c.Locals["user_id"] = 0,或统一用c.Set("key", value)+ 文档约定“只读不继承” - 别把
c.Locals当成 PHP 的$_SESSION或 Node.js 的res.locals——它没有自动清理机制,也没有作用域隔离
跨中间件传递数据只能靠 c.Locals,但必须配约束
这是唯一被框架支持的“请求级共享”方式,但必须配合明确的使用契约:
- 所有写入必须发生在
ctx.Next()之前(即本 middleware 主体逻辑中),禁止在 defer 或 goroutine 里写c.Locals - 所有读取必须发生在
ctx.Next()之后(即下游 middleware 或 handler 中),且要先做类型断言或默认值兜底:if role, ok := c.Locals["role"].(string); ok { ... } - 禁止在异步 goroutine 中捕获并使用
c或其字段——Ctx不是线程安全对象,一旦handler返回,c就可能被归还池中甚至重置
想共享数据库连接或配置?别碰 global,用依赖注入容器
Fiber 本身不提供 DI 容器,但你可以用 github.com/uber-go/dig 或 go.uber.org/fx 等标准方案,把连接池、缓存客户端等作为 singleton 注入到 handler 函数参数中:
func handler(db *sql.DB, cache *redis.Client) fiber.Handler {
return func(c *fiber.Ctx) error {
// db 和 cache 是线程安全的,由容器保证单例+生命周期管理
return c.JSON(db.QueryRow(...))
}
}
- 不要在 handler 里调
sql.Open()或redis.NewClient()——连接池本身是并发安全的,但反复新建实例会耗尽 fd - 避免用
var DB *sql.DB全局变量:它可能被多个请求并发修改(比如调DB.SetMaxOpenConns()),且测试时无法 mock - 如果非要用全局,至少加
sync.Once初始化,但依然不如 DI 显式、可测、可替换
真正需要跨协程共享时,必须用 context.Context + 显式传参
比如你在 handler 里启了一个 goroutine 做异步日志上报,它需要访问当前请求的 trace_id:
- 错误做法:
go func() { log.Printf("trace: %s", c.Locals["trace_id"]) }()——c可能已失效 - 正确做法:
traceID := c.Locals["trace_id"].(string); go func(ctx context.Context, id string) { ... }(c.Context(), traceID) - 注意:
c.Context()返回的是请求级context.Context,它自带取消信号和 deadline,比裸传字符串更健壮 - 别试图用
context.WithValue往里面塞业务数据——那只是临时 workaround,长期看应重构为参数传递或服务对象
最易被忽略的一点:Ctx 的复用机制意味着它的内存地址可能重复,而 Go 的 unsafe 操作(Fiber 内部大量使用)会让这种复用变得“看不见但危险”。你以为在改自己的副本,其实是在改别人刚还回来的那个。


















