中间件中必须用r.WithContext()替换请求对象才能使context.WithValue()生效;key须为自定义类型,取值需ok判断;异步goroutine应使用context.WithoutCancel避免被请求取消信号误杀。

中间件里怎么把值塞进 context 并确保下游能拿到
必须用 r.WithContext() 替换请求对象,否则 context.WithValue() 的结果根本不会传下去。常见错误是只调用 context.WithValue(r.Context(), key, val),却忘了把新 ctx 绑回 *http.Request。
- key 必须是自定义类型(比如
type userIDKey string),不能用字符串字面量,否则不同包之间容易冲突 - 写入后要立刻用
r = r.WithContext(newCtx),再调next.ServeHTTP(w, r) - 取值时必须用完全相同的 key 类型,且始终配合
ok判断:v, ok := ctx.Value(myKey).(int64),别直接强制转换 - 中间件链越长,
ctx嵌套越深,但 Go runtime 对此无性能损耗,不用刻意“扁平化”
异步 goroutine 里该不该直接传 r.Context()
不该。直接传 r.Context() 会让后台任务被请求生命周期绑架——请求一结束,ctx.Done() 就关闭,哪怕日志上报还没发完。
- 需要保留元数据(如 trace ID、user ID)但丢弃取消信号?用
context.WithoutCancel(r.Context()) - 需要独立超时?再套一层
context.WithTimeout(context.WithoutCancel(r.Context()), 30*time.Second) - 绝对不要在 goroutine 外部调用
cancel(),否则可能误杀其他共享该 ctx 的协程 - 自己启的 long-running loop,必须在
select里监听,否则超时机制形同虚设
业务数据到底该不该塞进 context.Value()
只存少量、不可变、生命周期与请求一致的元数据(如 traceID、userID)。业务逻辑需要的数据,该建结构体就建结构体,别往 context.Context 里塞 map 或 struct。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 塞了业务数据会导致调试困难:不知道谁写的、谁读的、有没有被覆盖
- 类型断言失败会 panic:
panic: interface conversion: interface {} is nil, not *User - 用开关类值(如
"skipAuth")更危险——下游行为变成隐式依赖,测试和重构都踩坑 - 日志字段、监控标签这类可读性需求,应在 middleware 提前提取并注入 logger,别靠 runtime 查 context
handler 函数里怎么正确获取和使用 ctx
唯一合法入口是 r.Context()。千万别用 context.Background() 或 context.TODO() 替代,否则超时和取消信号彻底丢失。
立即学习“go语言免费学习笔记(深入)”;
- 所有阻塞操作必须用
xxxContext版本函数:比如http.Client.DoContext、sql.DB.QueryContext - 自己写的循环或等待逻辑,必须主动检查
select { case - 派生子 ctx(如加超时)后,记得
defer cancel(),否则 timer 不释放,可能内存泄漏 - ctx 是只读的,
WithValue返回新实例,原 ctx 不变,不存在并发写冲突问题

















