代码之家  ›  专栏  ›  技术社区  ›  Louis Thibault

在保留原始请求上下文的值的同时,如何将上下文传递给r.withContext?

go
  •  0
  • Louis Thibault  · 技术社区  · 6 年前

    问题陈述

    我想将HTTP请求的生存期与在Web应用程序范围之外创建的上下文联系起来。因此,我编写了以下中间件(使用 github.com/go-chi/chi ):

    func BindContext(c context.Context) func(http.Handler) http.Handler {
        return func(h http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                h.ServeHTTP(w, r.WithContext(c))
            })
        }
    }
    

    中间件用于以下最小测试用例:

    package main
    
    import (
        "context"
        "net/http"
    
        "github.com/SentimensRG/ctx"
        "github.com/SentimensRG/ctx/sigctx"
    
        "github.com/go-chi/chi"
    )
    
    func greet(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
    }
    
    func BindContext(c context.Context) func(http.Handler) http.Handler {
        return func(h http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                h.ServeHTTP(w, r.WithContext(c))
            })
        }
    }
    
    func main() {
        r := chi.NewMux()
        r.Use(BindContext(ctx.AsContext(sigctx.New())))
        r.Get("/", greet)
        http.ListenAndServe(":8080", r)
    }
    

    处理程序因以下错误而崩溃:

    2018/07/25 14:58:57 http: panic serving [::1]:56527: interface conversion: interface {} is nil, not *chi.Context
    goroutine 35 [running]:
    net/http.(*conn).serve.func1(0xc42014a0a0)
            /usr/local/go/src/net/http/server.go:1726 +0xd0
    panic(0x12749c0, 0xc42014c200)
            /usr/local/go/src/runtime/panic.go:502 +0x229
    github.com/go-chi/chi.(*Mux).routeHTTP(0xc4201180c0, 0x12fcf00, 0xc420166000, 0xc420160200)
            /Users/lthibault/go/src/github.com/go-chi/chi/mux.go:400 +0x2f3
    github.com/go-chi/chi.(*Mux).(github.com/go-chi/chi.routeHTTP)-fm(0x12fcf00, 0xc420166000, 0xc420160200)
            /Users/lthibault/go/src/github.com/go-chi/chi/mux.go:368 +0x48
    net/http.HandlerFunc.ServeHTTP(0xc420142010, 0x12fcf00, 0xc420166000, 0xc420160200)
            /usr/local/go/src/net/http/server.go:1947 +0x44
    main.fail.func1.1(0x12fcf00, 0xc420166000, 0xc420160100)
            /Users/lthibault/go/src/github.com/lthibault/mesh/cmd/scratch/main.go:22 +0x77
    net/http.HandlerFunc.ServeHTTP(0xc420148000, 0x12fcf00, 0xc420166000, 0xc420160100)
            /usr/local/go/src/net/http/server.go:1947 +0x44
    github.com/go-chi/chi.(*Mux).ServeHTTP(0xc4201180c0, 0x12fcf00, 0xc420166000, 0xc420160000)
            /Users/lthibault/go/src/github.com/go-chi/chi/mux.go:81 +0x221
    net/http.serverHandler.ServeHTTP(0xc420150000, 0x12fcf00, 0xc420166000, 0xc420160000)
            /usr/local/go/src/net/http/server.go:2694 +0xbc
    net/http.(*conn).serve(0xc42014a0a0, 0x12fd1c0, 0xc42014c080)
            /usr/local/go/src/net/http/server.go:1830 +0x651
    created by net/http.(*Server).Serve
            /usr/local/go/src/net/http/server.go:2795 +0x27b
    

    不雅解

    问题似乎来自 Mux.routeHTTP ,尝试恢复 *chi.Context r.Context() . 似乎 r.WithContext 不将存储在请求上下文中的值传输到新上下文。

    最明显(尽管很难看)的解决办法是:

    func BindContext(c context.Context) func(http.Handler) http.Handler {
        return func(h http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                rctx := r.Context().Value(chi.RouteCtxKey).(*chi.Context)
                c = context.WithValue(c, chi.RouteCtxKey, rctx)
                h.ServeHTTP(w, r.WithContext(c))
            })
        }
    }
    

    这行得通,但让我感到不安。我真的需要手动将每个相关值从 R.() 传递到上下文中 r.WithContext() ?

    这里有几个失败案例:

    • 当有许多不同的值要传输时会发生什么?
    • 如果不导出上下文键(如Go中建议的那样),会发生什么?
    • 如果原始上下文在我传入的上下文之前终止,会发生什么?

    (几句话:没什么好东西!)

    我的问题是

    是否有标准的“合并”上下文传递给 小精灵 中的现有上下文 r.Context ?

    3 回复  |  直到 5 年前
        1
  •  1
  •   Peter saif iqbal    6 年前

    不应将传入请求的上下文替换为不相关的上下文。初学者:

    包上下文定义了上下文类型,它跨API边界和进程之间承载截止日期、取消信号和其他请求范围的值。

    sigctx.New() 在任何请求发生之前调用,因此根据定义不是请求范围。几乎所有的代码都期望在a)请求完成时取消请求上下文,或者b)客户端中止请求(通常是因为它不再对响应感兴趣)。你正在通过替换上下文来打破这个假设。您还将删除其他中间商先前可能添加到上下文中的任何值。

    似乎您希望中止对sigint或sigterm的请求。你应该 添加 请求上下文的取消条件,而不是完全替换它。也许是这样:

    func BindContext(c context.Context) func(http.Handler) http.Handler {
            return func(h http.Handler) http.Handler {
                    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                            rCtx := r.Context()
                            ctx, cancel := context.WithCancel(rCtx)
    
                            go func() {
                                    select {
                                    case <-c.Done(): // SIGINT/SIGTERM
                                    case <-rCtx.Done(): // Request finished or client aborted
                                    }
                                    cancel()
                            }()
    
                            h.ServeHTTP(w, r.WithContext(ctx))
                    })
            }
    }
    

    更新:

    为了让用户配置上下文,接受一个从请求上下文派生新上下文的函数(尽管用户也可以直接提供这样做的中间件):

    func WithContext(new func(context.Context) context.Context) func(http.Handler) http.Handler {
        return func(h http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                r = r.WithContext(new(r.Context()))
    
                h.ServeHTTP(w, r)
            })  
        }   
    }
    
        2
  •  0
  •   Louis Thibault    6 年前

    似乎没有现成的解决方案,但是 github.com/SentimensRG/ctx 提供一个 mergectx 专门为此目的分装。

    解决方案是使用 mergectx.Merge .

        3
  •  0
  •   ndequeker Idan Wender    5 年前

    我也面临同样的问题,通过创建新的上下文 chi.NewRouteContext .

    请求正在使用 httptest . 您可以使用更新请求的上下文 r.WithContext .

    例子

    w := httptest.NewRecorder()
    r := httptest.NewRequest("GET", "/", nil)
    
    rctx := chi.NewRouteContext()
    rctx.URLParams.Add("key", "value")
    
    r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
    
    handler := func(w http.ResponseWriter, r *http.Request) {
        key := chi.URLParam(r, "key") // "value"
    }
    
    handler(w, r)
    

    见Aapolkovsky的以下要点: https://gist.github.com/aapolkovsky/1375348cab941e36c62da24a32fbebe7