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

时间:2016-10-09 17:42:36

标签: design-patterns go request timeout

我试图了解Golang 1.7中引入的上下文如何工作以及将其传递给中间件和HandlerFunc的适当方式。上下文是否应该在main函数中初始化并传递给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, ...))
}

2 个答案:

答案 0 :(得分:5)

如果查看first example at that Go Concurrency Patterns blog post,您会注意到他们正在从Background上下文“派生”他们的上下文。结合Context对象上的WithContextRequest方法,可以为您提供所需的内容。

我只是想出来了(这不是我第一次阅读这些文档);当你“衍生”一个背景时,你正在做另一个有一个改变。我已经包装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行)时,将取消任何派生的上下文(AKA,即添加变量的内容)。最后,第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。这是最直接的部分;我们只是对请求的Value方法返回的值使用Context方法。类型为interface{},因此如果要将其视为字符串,则需要类型断言(如本示例所示)。

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

您不限于每种方法一次更改;只要你继续得到相同的上下文,一旦请求被提供,它们都将被清理,当最初派生的上下文(在这个例子中,我们指定超时的那个)被延迟时{{ 1}}召唤火灾。

答案 1 :(得分:0)

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

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