handler中必须调用r.context()获取上下文,再通过r.withcontext()更新请求对象向下传递;中间件需用context.withvalue添加数据并确保key为自定义类型,异步goroutine应使用context.withoutcancel避免取消信号误杀。

handler 里怎么拿到并传 context.Context
Go 的 http.Handler 接口不接收 context.Context 参数,所以不能“直接传入”。必须从 *http.Request 中显式提取:调用 r.Context() 获取当前请求上下文。这是唯一合法入口。
常见错误是手动生成 context.Background() 或硬塞 context.TODO() —— 这会让超时、取消信号彻底丢失,上游中断后下游 goroutine 还在跑。
- 所有中间件和 handler 都应以
r.Context()为起点向下传递 - 不要覆盖
r.Context()返回的 ctx,除非你明确要派生新上下文(如加超时、加值) - 若需注入 trace ID、用户信息等业务数据,用
context.WithValue(),但 key 必须是自定义类型(例如type userIDKey string),避免字符串 key 冲突
中间件中如何安全增强 context 并更新 request
中间件本质是包装 http.Handler,它必须把增强后的 context 注入到 *http.Request 中,下游才能拿到。Go 不允许直接修改 r.Context() 返回的 ctx,但提供了 req.WithContext(newCtx) 来生成新 request 实例。
典型陷阱是:只改了局部变量 ctx,却没调用 req = req.WithContext(newCtx),导致下游仍拿到原始上下文。
- 每次增强 context 后,必须用
req = req.WithContext(newCtx)替换 request 对象 - 中间件链中每层都应调用
next.ServeHTTP(w, req),传的是增强后的req,不是原始req -
context.WithValue()存的值仅在当前请求生命周期内有效,无需担心并发写冲突(因为 ctx 是只读的)
异步 goroutine 中该不该用 r.Context() 直接传入
不该。直接把 r.Context() 传给异步 goroutine,会导致该 goroutine 生命周期被请求绑定——请求结束即 cancel,但很多后台任务(如日志上报、消息投递、缓存刷新)本就不该随请求终止。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
正确做法是用 context.WithoutCancel(r.Context()) 创建一个不继承取消信号的新 ctx,只保留 Value(如 trace ID、user ID),同时丢弃 Done 通道。
- 如果异步任务需要独立超时控制,用
context.WithTimeout(context.WithoutCancel(r.Context()), 30*time.Second) - 绝对不要在 goroutine 外部调用
cancel(),否则可能误杀其他共享同一 ctx 的 goroutine - 若任务需主动终止(如等待多个 RPC,任一失败就退出),才用
context.WithCancel(),且 cancel 函数应在 goroutine 内部可控调用
trace 上下文为什么总断在 HTTP outbound 调用
因为 context.Context 本身不带 trace 字段,OpenTelemetry 的传播完全依赖手动注入/提取。漏掉一次 otel.GetTextMapPropagator().Inject(),链路就断一跳。
常见断点:用 http.Client.Do() 调第三方 API 时,没在 req.Header 里塞 traceparent;或者 header key 写成 Traceparent(必须全小写)。
- 入口处必须从
req.Header.Get("traceparent")提取,失败时 fallback 创建 root span,别传 nil context - 每次 outbound 请求前,必须调用
otel.GetTextMapPropagator().Inject(ctx, req.Header) - key 类型推荐用 unexported 类型(如
spanKey struct{}),避免 value 冲突
最易被忽略的是:context.WithoutCancel() 和 otel.GetTextMapPropagator().Inject() 这两步必须同时做——前者保值不断链,后者透传不丢 trace。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










