我试图了解Golang 1.7中引入的上下文如何工作以及将其传递给中间件和HandlerFunc
的适当方式。上下文是否应该在main函数中初始化并传递给checkAuth
函数呢?以及如何将其传递给Hanlder
和ServeHTTP
函数?
我读过Go concurrency patterns和How 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, ...))
}
答案 0 :(得分:5)
如果查看first example at that Go Concurrency Patterns blog post,您会注意到他们正在从Background
上下文“派生”他们的上下文。结合Context
对象上的WithContext
和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)
第一行创建上下文并为您提供取消挂钩,您推迟;一旦请求被提供,在执行此延迟调用(第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))
处理程序不必强制转换类型,甚至不需要知道上下文键。