必须用r.withcontext()更新请求对象、自定义key类型写入、安全断言读取;三步任一出错则context失效:中间件需r = r.withcontext(ctx)传递,handler中用v, ok := ctx.value(key).(t)判断,超时取消须用withtimeout/withcancel而非withvalue。

直接用 context.WithValue 传值、用 req.WithContext() 更新请求对象、用 ctx.Done() 监听取消——这三步做错任意一个,context 就等于没传。
HTTP 中间件里怎么把值塞进 context 并传下去
中间件不能直接改 r.Context(),因为它是只读副本;必须用 r.WithContext() 生成新请求对象再往下传。
- 写入值要用自定义 key 类型(如
type userIDKey string),避免字符串 key 冲突 - 必须调用
r = r.WithContext(ctx),否则下游 handler 拿到的还是原始 context - 不要在中间件里用
context.Background()或context.TODO()替换根 context,会切断取消链 - 如果中间件嵌套多层,每层都应基于上一层的
r.Context()派生,而不是反复从context.Background()开始
Handler 里怎么安全读取 context 里的值
读取时 key 类型和写入时必须完全一致,否则 ctx.Value(key) 返回 nil,强制类型断言会 panic。
- 推荐封装成导出函数,比如
GetUserID(ctx),内部用v, ok := ctx.Value(UserIDKey).(int64)判断 - 永远检查
ok,不直接用ctx.Value(key).(int64) - 结构体值建议只读,若需修改,应传指针并自行保证并发安全,但通常不建议在 context 里存可变大对象
- 不要用
map[string]interface{}或全局变量替代 context —— 请求隔离性就没了
什么时候该用 WithCancel / WithTimeout 而不是 WithValue
WithValue 只负责传数据,不控制生命周期;超时和取消必须靠 WithCancel 或 WithTimeout 创建新 context,并监听 ctx.Done()。
- 发起 HTTP 请求前,必须用
http.NewRequestWithContext(ctx, ...),否则底层 transport 不响应取消 - 数据库查询、RPC 调用等阻塞操作,都要把 context 传进去,且函数内部要定期检查
ctx.Err() != nil -
WithTimeout内部已含 cancel 逻辑,但cancel()仍需 defer 调用,防止 goroutine 泄漏 - 不要用
WithValue存截止时间或取消函数 —— 这些是控制信号,不是元数据
最易被忽略的是:context 的取消信号不会自动传播到第三方库的 I/O 操作里,除非那个库显式支持 context(比如 net/http、database/sql)。自己写的阻塞循环或 channel 等待,必须手动 select ctx.Done()。否则 cancel 了也没用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











