Golang 中的 Context 为什么只有上文没有下文?一般如何传递下文?

101 天前
 SSang

概述

golang 似乎为了保证线程安全,context 不允许修改,只能继承,但这样带来的问题就是上文环境无法获取在下文中更新的 context 。

func left(ctx context.Context) {
    right(ctx)
    value := GetContextValue(ctx, "key")
    fmt.Println(value)
}

func right(ctx context.Context) {
    ctx := context.WithValue(ctx, "key", "value")
}

因为 right 中 context 并没有改变旧的 ctx ,因此 left 中无法获取到 key 的值。

初步想法

我的想法是 ctx 里面塞一个指针,不知道这样是否合理。

// 类似这样,可能不是很准确

func right(ctx context.Context) {
    sctx := ctx.Value("context").(*SyncContext)
    sctx.Set(...)
}

func left(ctx context.Context) {
    sctx := ctx.Value("context").(*SyncContext)
    right(ctx)
    fmt.Println(sctx.Get(...))
}

type SyncContext struct {
    values sync.Map
}
func NewSyncContext() *SyncContext { ... }
func (c *SyncContext) Get(key string) any { ... }
func (c *SyncContext) Set(key string, value any) { ... }

func main() {
    ctx := context.WithValue(context.Background(), "context", NewSyncContext())
    left(ctx)
}

但感觉这种姿势怪怪的。有没有其他的想法?

场景

大概描述一下我的具体场景,http middleware 使用链式调用,第一个中间件是日志中间件,会在所有 next 调用结束后输出日志,请求、响应这些目前都有办法获取了,就是 next 中间件往 req.Context() 写的数据读不到(因为 req.WithContext 也会创建新的 request ,而不是修改 request 的 ctx ,目前看到的代码也没有提供修改 request context 的途径)。

主要是 next 中间件会进行一些身份认证,会把用户信息写进 context ,需要日志最后打出这些用户信息 ( PS:因为这些日志是需要以特定格式输出用于审计的,所以各个中间件自行输出可能会比较难受,主要是想各司其职,不要把心智负担下放到下游中间件)。

2753 次点击
所在节点    Go 编程语言
15 条回复
monsterxx03
101 天前
*req = *req.WithContext(...)
singer
101 天前
不怪,这么处理合理。参考 gin 框架,https://github.com/gin-gonic/gin/blob/master/context.go#L69 。上下文中传递轻量数据,一个 map 足够了,你认为会有并发,那就 sync.map 。
kuanat
101 天前
Go 的 context 是 1.7 版本引入给 net/http 服务的,用来解决信号和取消问题,传 value 只是顺带的,同时特别强调了线程安全的问题。名字用了 context 但是语义上确实只有上文。所以当你真正需要上下文的时候 context 包是不够的。

一般中间件解决这个问题的思路是自定义 context ,其实我不太喜欢 gin 的方式,我个人的偏好是类似

```
type MyContext struct {
ctx context.Context
// custom field
key string
}
```
这样的方式。然后实现 Context 的接口方法,写几个 wrapper 就可以完成对 context.Context 的兼容,不影响原本 net/http 的信号取消机制。

剩下的就是语法层面的封装了,需要实现一组方法,比如从 context.Context 衍生出子 MyContext:
```
func DeriveMyContext(ctx context.Context) *MyContext {
myCtx, _ := ctx.Value(MyCtxKey).(*MyContext)
return myCtx
}
```
此处用接口断言是根据 context 的设计,value 通过自定义类型模拟命名空间,防止 key 冲突。

结合起来就是 `context.WithValue(context.Background(), key, value)` 中的 kv 对,实际上就是通过 context.Value 传递了一个特定的 key ,这个 key 等价于指向 MyContext 的指针,和你的思路是一致的。

这样中间件所有涉及的 context 都通过一个 MyContext 的结构共享上下文,如果涉及到多线程可以加 Mutex 锁。

反正 Go 在传递 context 这件事上已经一条道走到黑了,比如 1.21 标准化的 slog 日志库也可以接受 context ,稍微封装下也可以直接用。
lvlongxiang199
101 天前
建议还是把鉴权放到 log 之前. 向 ctx 里头塞指针, 万一有地方把指针里的值改了, 很难 debug, 不如让它不可变
mainjzb
101 天前
gin 的 ctx 有 set 方法, 内部维护了一个 map
mainjzb
101 天前
gin 是这么用的。内部维护一个 map

// 中间件
c.Set("user_id", s.UserID)
c.Set("session_id", s.ID)
c.Set("token", s.AccessToken)

后面的 handler 直接 c.Get("user_id") 获取即可。
SSang
101 天前
@lvlongxiang199 链式调用,把 log 放后面无法保证一定被调用,否则要单独抽一个逻辑,但其实不只是 log 中间件需要获取响应,所以会变得很不通用
SSang
101 天前
@kuanat 感谢,你这个写法比我的好。我也是不太喜欢 gin 的方式,我也是希望尽可能兼容官方接口。
DefoliationM
101 天前
最开始塞个 map 进去,之后直接往 map 里存
rrfeng
101 天前
这个不是 context 包要解决的问题
你需要的是 http 的 RequestContext ,比如楼上说的 gin 的,可以直接 Set/Get 任意值。
wqtacc
101 天前
像下面这样使用

```go
func left(ctx context.Context) {
ctx = right(ctx)
value := ctx.Value("key")
fmt.Println(value)
}

func right(ctx context.Context) context.Context {
return context.WithValue(ctx, "key", "value")
}
```
iceheart
101 天前
Context 就是一棵树,想咋玩就咋玩喽
lvlongxiang199
100 天前
@SSang 似乎可以这样,

middlewareA:|________________________将 user_id 等信息放入 ctx______________________|
middlewareB: |____________________________log__________________________|
middlewareC: |_________________如果没有 user_id 报错_____|
lvlongxiang199
100 天前
@SSang 似乎可以这样,
```
middlewareA:|________________________将 user_id 等信息放入 ctx______________________|
middlewareB: |____________________________log__________________________|
middlewareC: |_________________如果没有 user_id 报错_____|
```
flighter
100 天前
去实现 自己的 MyContext 去做这个事情

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1012453

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX