如何将golang请求中的上下文传递给中间件

use*_*284 5 design-patterns timeout request go

我试图了解Golang 1.7中引入的上下文如何工作,以及将其传递给中间件和.NET的适当方法是什么HandlerFunc。上下文应该在主函数中初始化并传递给checkAuth函数吗?以及如何将其传递给HanlderServeHTTP函数?我读了Go并发模式如何使用上下文,但是我很难使这些模式适应我的代码。

func checkAuth(authToken string) util.Middleware {
    return func(h http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if r.Header.Get("Auth") != authToken {
                util.SendError(w, "...", http.StatusForbidden, false)
                return
            }
            h.ServeHTTP(w, r)
        })
    }
}

// Handler is a struct
type Handler struct {
    ...
    ...
}

// ServeHTTP is the handler response to an HTTP request
func (h *HandlerW) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    decoder := json.NewDecoder(r.Body)

    // decode request / context and get params
    var p params
    err := decoder.Decode(&p)
    if err != nil {
       ...
        return
    }

    // perform GET request and pass context
    ...


}


func main() {
    router := mux.NewRouter()

    // How to pass context to authCheck?
    authToken, ok := getAuthToken()
    if !ok {
        panic("...")
    }
    authCheck := checkAuth(authToken)

    // initialize middleware handlers
    h := Handler{
       ...
   } 

   // chain middleware handlers and pass context
   router.Handle("/hello", util.UseMiddleware(authCheck, Handler, ...))
}
Run Code Online (Sandbox Code Playgroud)

Zch*_*ary 12

如果有人尝试在处理函数中添加上下文值并在中间件中处理它。

net/http提供Request.CloneRequest.WithContext方法来修改请求上下文,但都返回一个新的请求指针,该指针不能更改原始请求指针*http.Request

您可以尝试这样做,而不是这样做:

func someHandler(w http.ResponseWriter, r *http.Request){
    ctx := r.Context()
    req := r.WithContext(context.WithValue(ctx, "key", "val"))
    *r = *req
}
Run Code Online (Sandbox Code Playgroud)


小智 6

如果您查看该Go Concurrency Patterns博客文章中的第一个示例,您会发现它们是从Background上下文“派生”其上下文的。结合对象上的ContextWithContext方法Request,可以满足您的需求。

我只是想通了(这不是我第一次阅读那些文档);当您“派生”一个上下文时,您正在做出一个更改而又另一个。我已经在包装http.Handler(实际使用了httprouter.Handle)。有趣的Request.Context是,它永远不会返回nil;如果未创建其他上下文,则返回背景上下文。

要指定超时,请在您的处理程序中(就在“ //执行GET请求”注释的上方),您可以执行以下操作:

ctx, cancel := context.WithTimeout(r.Context(), time.Duration(60*time.Second))
defer cancel()
r = r.WithContext(ctx)
Run Code Online (Sandbox Code Playgroud)

第一行创建了上下文并为您提供了取消钩子,您将其推迟;一旦处理了请求,则在执行此延迟调用(第2行)时,所有派生上下文(也就是添加变量的上下文)都将被取消。最后,第3行替换了请求,该请求现在包含更新的上下文。

在授权检查器中,一旦确定用户有效,就可以在调用之前将用户的信息添加到上下文中ServeHTTP。上下文的键不能使用内置类型,但是您可以创建一个新类型,该类型只是内置类型的别名。最好为键值定义常量。一个例子:

type ContextKey string

const ContextUserKey ContextKey = "user"

// Then, just above your call to ServeHTTP...

ctx := context.WithValue(r.Context(), ContextUserKey, "theuser")
h.ServeHTTP(w, r.WithContext(ctx))
Run Code Online (Sandbox Code Playgroud)

这会将现在两次派生的上下文(现在具有超时用户ID)传递给处理程序。

最后,难题的最后一部分-如何从处理程序中获取用户ID。这是最直接的部分;我们仅对Value请求的Context方法返回的值使用方法。类型是interface{},因此如果您想将其视为字符串(如本例所示),则需要类型断言。

user := r.Context().Value(ContextUserKey)
doSomethingForThisUser(user.(string))
Run Code Online (Sandbox Code Playgroud)

您不仅限于每种方法的一项更改;只要您继续派生相同的上下文,则在请求被服务后,当最初派生的上下文(在此示例中,我们指定了超时的上下文)在延迟cancel()调用触发时被取消时,所有这些都将被清除。。


cod*_*ara 6

我的一个项目刚刚遇到这个问题。我能够通过使用服务器结构内的 BaseContext 字段来解决这个问题。使用自定义上下文初始化 BaseContext 使服务器能够为所有传入请求提供此自定义上下文。下面是一些示例代码:

import "net/http"

type ServerHandler struct {
}

server := &http.Server{Addr: localhost:9001,
    Handler:     ServerHandler,
    BaseContext: func(_ net.Listener) context.Context { return <custom context> }}


server.ListenAndServe()
Run Code Online (Sandbox Code Playgroud)

通过使用自定义上下文初始化 BaseContext,您可以将自定义上下文传递给 ServeHTTP 方法。所有传入请求都将具有自定义上下文。

func (handler ServerHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
    //Access your context here as request.Context()
}
Run Code Online (Sandbox Code Playgroud)

在这里找到结构的定义:https://cs.opensource.google/go/go/+/refs/tags/go1.17 :src/net/http/server.go;l=2611


jab*_*cky 5

您可以通过添加一个函数以类型安全的方式从上下文中检索值来改进 Daniels 解决方案:

type ContextKey string

const ContextUserKey ContextKey = "user"

func UserFromContext(ctx context.Context) string {
    return ctx.Value(ContextUserKey).(string)
}

// Then, just above your call to ServeHTTP... 

ctx := context.WithValue(r.Context(), userKey, "theuser")
h.ServeHTTP(w, r.WithContext(ctx))
Run Code Online (Sandbox Code Playgroud)

处理程序不必强制转换类型,甚至不需要知道上下文键。