【问题标题】:How to pass context in golang request to middleware如何将golang请求中的上下文传递给中间件
【发布时间】:2017-02-18 04:26:21
【问题描述】:

我试图了解 Golang 1.7 中引入的上下文是如何工作的,以及将它传递给中间件和 HandlerFunc 的适当方法是什么。那么上下文是否应该在主函数中初始化并传递给checkAuth 函数?以及如何将其传递给HanlderServeHTTP 函数? 我读过Go concurrency patternsHow to use Context,但我很难让这些模式适应我的代码。

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, ...))
}

【问题讨论】:

  • 你想使用 go 1.7 中引入的上下文来实现中间件,还是你想在 1.7 之前实现具有自己上下文的中间件?使用Request.ContextRequest.WithContextcontext.WithValue等新引入的方法,前者会容易得多
  • 我正在使用 go 1.7 并且喜欢使用 Request.Context。

标签: design-patterns go request timeout


【解决方案1】:

如果您查看first example at that Go Concurrency Patterns blog post,您会注意到它们是从Background 上下文“派生”出它们的上下文。结合 Request 对象上的 ContextWithContext 方法,可以满足您的需求。

我只是想通了(这不是我第一次阅读这些文档);当您“导出”一个上下文时,您正在通过一次更改制作另一个上下文。我已经包装了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)

第一行创建上下文并为您提供取消挂钩,您可以推迟该挂钩;一旦请求得到处理,当这个延迟调用(第 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))

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

最后,难题的最后一部分 - 如何从处理程序中获取用户 ID。这是最直接的部分;我们只需对请求的Context 方法返回的值使用Value 方法。类型是interface{},所以如果你想把它当作一个字符串(就像这个例子一样),你需要一个类型断言。

user := r.Context().Value(ContextUserKey)
doSomethingForThisUser(user.(string))

您不限于每种方法进行一次更改;只要你继续派生相同的上下文,一旦请求得到服务,当最初派生的上下文(在本例中,我们指定超时的那个)在延迟 @987654337 @调用触发。

【讨论】:

    【解决方案2】:

    您可以通过添加一个函数来以类型安全的方式从上下文中检索值来改进 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))
    

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

    【讨论】:

    • 是你的“return ctx.Value(userKey) , (string)”结尾的错误逗号吗?
    • 是的,它应该是一个 '.' - 然后类型转换实际上有效;)
    • 稍微改进一下,type contextKey string 是私有的,这确保它不会与其他包中的任何其他上下文键发生冲突。
    【解决方案3】:

    刚刚在我的一个项目中遇到了这个问题。我能够通过使用服务器结构中的 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()
    

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

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

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

    【讨论】:

    • 这应该是公认的答案。请记住,Server 结构中需要正确设置许多选项,例如 ReadTimeout。否则默认是永远等待
    猜你喜欢
    • 2018-03-25
    • 2016-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多