Go 如何将上下文传递给r.WithContext,同时保留原始请求上下文的值? 问题陈述

Go 如何将上下文传递给r.WithContext,同时保留原始请求上下文的值? 问题陈述,go,Go,我想将HTTP请求的生存期与在web应用程序范围之外创建的上下文联系起来。因此,我编写了以下中间件(使用github.com/go-chi/chi): 中间件用于以下最小测试用例: package main import ( "context" "net/http" "github.com/SentimensRG/ctx" "github.com/SentimensRG/ctx/sigctx" "github.com/go-chi/chi" ) f

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

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

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
不雅解 问题似乎来自于试图从
r.Context()
恢复
*chi.Context
。似乎
r.WithContext
不会将存储在请求上下文中的值传输到新上下文

显而易见(尽管丑陋)的解决办法是:

这是可行的,但让我感到不安。我真的需要手动将每个相关值从
r.Context()
转移到传递到
r.WithContext()的上下文中吗

有几种故障情况,如下所示:

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

我的问题如下
是否有标准的“合并”上下文传递给
r.WithContext
r.context
中的现有上下文?

您不应将传入请求的上下文替换为不相关的上下文。首先:

包上下文定义上下文类型,它跨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))
                })
        }
}

似乎没有现成的解决方案,但是
github.com/SentimensRG/ctx
专门为此提供了
mergectx
子包


解决方法是使用。

我遇到了同样的问题,并且能够通过使用创建新上下文来解决这个问题

正在使用
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)

请参阅亚波尔科夫斯基的福林要点:

这并没有真正回答我的问题。这里的问题是,我希望传递给
BindContext
的上下文是用户可配置的。我无法提前知道将传递什么样的上下文。非正式地说,这里的要求是以某种方式“合并”两个上下文(我天真地认为这是
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()
                        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)
        })  
    }   
}
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)