Golang 中的 Context 为什么只有上文没有下文?一般如何传递下文? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
SSang
V2EX    Go 编程语言

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

  •  
  •   SSang 2024-01-29 15:03:33 +08:00 3702 次点击
    这是一个创建于 624 天前的主题,其中的信息可能已经有所发展或是发生改变。

    概述

    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:因为这些日志是需要以特定格式输出用于审计的,所以各个中间件自行输出可能会比较难受,主要是想各司其职,不要把心智负担下放到下游中间件)。

    15 条回复    2024-01-30 19:54:42 +08:00
    monsterxx03
        1
    monsterxx03  
       2024-01-29 15:23:08 +08:00   1
    *req = *req.WithContext(...)
    singer
        2
    singer  
       2024-01-29 15:28:01 +08:00   1
    不怪,这么处理合理。参考 gin 框架,https://github.com/gin-gonic/gin/blob/master/context.go#L69 。上下文中传递轻量数据,一个 map 足够了,你认为会有并发,那就 sync.map 。
    kuanat
        3
    kuanat  
       2024-01-29 17:27:55 +08:00   1
    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
        4
    lvlongxiang199  
       2024-01-29 17:54:09 +08:00
    建议还是把鉴权放到 log 之前. 向 ctx 里头塞指针, 万一有地方把指针里的值改了, 很难 debug, 不如让它不可变
    mainjzb
        5
    mainjzb  
       2024-01-29 18:11:22 +08:00
    gin 的 ctx 有 set 方法, 内部维护了一个 map
    mainjzb
        6
    mainjzb  
       2024-01-29 18:12:35 +08:00
    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
        7
    SSang  
    OP
       2024-01-29 18:25:34 +08:00
    @lvlongxiang199 链式调用,把 log 放后面无法保证一定被调用,否则要单独抽一个逻辑,但其实不只是 log 中间件需要获取响应,所以会变得很不通用
    SSang
        8
    SSang  
    OP
       2024-01-29 18:27:37 +08:00
    @kuanat 感谢,你这个写法比我的好。我也是不太喜欢 gin 的方式,我也是希望尽可能兼容官方接口。
    DefoliationM
        9
    DefoliationM  
       2024-01-29 18:51:46 +08:00 via Android
    最开始塞个 map 进去,之后直接往 map 里存
    rrfeng
        10
    rrfeng  
       2024-01-29 19:11:53 +08:00
    这个不是 context 包要解决的问题
    你需要的是 http 的 RequestContext ,比如楼上说的 gin 的,可以直接 Set/Get 任意值。
    wqtacc
        11
    wqtacc  
       2024-01-29 22:13:08 +08:00
    像下面这样使用

    ```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
        12
    iceheart  
       2024-01-30 00:02:10 +08:00 via Android
    Context 就是一棵树,想咋玩就咋玩喽
    lvlongxiang199
        13
    lvlongxiang199  
       2024-01-30 11:38:24 +08:00
    @SSang 似乎可以这样,

    middlewareA:|________________________将 user_id 等信息放入 ctx______________________|
    middlewareB: |____________________________log__________________________|
    middlewareC: |_________________如果没有 user_id 报错_____|
    lvlongxiang199
        14
    lvlongxiang199  
       2024-01-30 11:39:27 +08:00
    @SSang 似乎可以这样,
    ```
    middlewareA:|________________________将 user_id 等信息放入 ctx______________________|
    middlewareB: |____________________________log__________________________|
    middlewareC: |_________________如果没有 user_id 报错_____|
    ```
    flighter
        15
    flighter  
       2024-01-30 19:54:42 +08:00
    去实现 自己的 MyContext 去做这个事情
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3534 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 04:40 PVG 12:40 LAX 21:40 JFK 00:40
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86