ctx.Value() 存的值在每次 HTTP 请求结束、c.Next() 返回后被清空,Gin 调用 context.Reset() 重置 c.Keys 等字段并将 Context 归还 sync.Pool 复用;存入的值(如 c.Set)不会跨请求保留,且不可存栈上分配的临时指针(如 &User{}),应存值类型或长生命周期对象指针。

ctx.Value() 存的值什么时候会被清空
每次 HTTP 请求结束、c.Next() 返回后,Gin 会调用 context.Reset() 清空 c.Keys(即 ctx.Value() 底层存储)、c.Errors、c.Params 等字段,并将整个 *gin.Context 对象归还给 sync.Pool 复用。这意味着:你存进 c.Set("key", value) 的东西,下一次请求复用这个 Context 实例时已不存在。
为什么不能往 ctx.Value() 里塞 &User{...} 这类临时指针
因为 &User{ID: 123} 是在 handler 函数栈上分配的,handler 执行完就可能被回收;而 *gin.Context 又会被池化复用——下次请求拿到的可能是同一块内存,但里面指针指向的原始 User 结构体早已失效,读取时可能 panic 或返回垃圾数据。
- 安全做法:存值类型,比如
c.Set("user", User{ID: 123}) - 必须存指针时:只允许指向全局变量、长生命周期对象(如
config.GlobalDB、engine.Logger),绝不可是new(User)或&localVar - 特别注意:不要用
c.Value()传解析后的 request body,应提前解析好再存;否则子 goroutine 里读c.Request.Body会是nil或已关闭状态
跨中间件传值,用 c.Set 还是 c.SetSameSite
c.Set() 是标准方式,写入 c.Keys map;c.SetSameSite() 是 Gin v1.9+ 新增的内部方法,仅用于设置 SameSite cookie 属性,和传值无关——别混淆。所有业务数据都走 c.Set(key, value),但务必配合上面的生命周期规则使用。
-
c.Get("key")返回value, exists bool,记得判exists - 键名建议用自定义类型(如
type userKey struct{})避免字符串冲突,而不是直接用"user" - 不推荐用
context.WithValue()包一层再传,Gin 的*gin.Context本身已封装了context.Context,额外包绕反而增加 GC 压力且无实际收益
goroutine 里用 c.Value() 很危险
如果在 handler 中启动 goroutine 并直接传入 c 或调用 c.Value(),一旦请求响应完成,c 被回收,goroutine 里访问 c.Value() 可能读到脏数据,或触发 context canceled 错误(尤其当它依赖底层 context.Context 的 Done channel 时)。
- 正确做法:在启动 goroutine 前,把需要的数据显式拷贝出来,比如
u := c.MustGet("user").(User),再传u进去 - 若需传递取消信号,应从
c.Request.Context()派生新 context,如ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second),并确保cancel()在 goroutine 结束时调用 - 永远不要在 goroutine 里调用
c.Abort()或c.JSON()—— 此时 response writer 很可能已写出并关闭
sync.Pool 归还的 *gin.Context 实例,其字段内存未被 memset,只是重置了关键字段(如 Keys、Errors)。所以如果你忘了清空自定义字段,或误用指针,下个请求就可能踩到上个请求留下的残影。


















