在处理程序之后访问 HTTP 请求上下文

在我的日志记录中间件(链中的第一个)中,我需要访问一些上下文,这些上下文是在链下的某些 auth 中间件中编写的,并且仅在执行处理程序本身之后。


旁注:需要首先调用日志记录中间件,因为我需要记录请求的持续时间,包括在中间件中花费的时间。此外,当权限不足时,auth 中间件能够中止请求。在那种情况下,我还需要记录失败的请求。


我的问题是从指针读取上下文http.Request不会返回我期望它具有的身份验证数据。请参阅下面的示例:


package main


import (

    "context"

    "fmt"

    "net/http"

    "time"

)


const (

    contextKeyUsername = "username"

)


func authMiddleware(next http.Handler) http.Handler {

    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

        ctx := r.Context()

        ctx = context.WithValue(ctx, contextKeyUsername, "user123")

        next.ServeHTTP(w, r.WithContext(ctx))

    })

}


func logMiddleware(next http.Handler) http.Handler {

    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

        defer func(start time.Time) {

            ctx := r.Context()

            username := ctx.Value(contextKeyUsername)

            if username != nil {

                fmt.Printf("user %s has accessed %s, took %d\n", username,

                    r.URL.Path, time.Since(start).Milliseconds())

            } else {

                fmt.Printf("annonyous has accessed %s, took %d\n",

                    r.URL.Path, time.Since(start).Milliseconds())

            }

        }(time.Now())

        next.ServeHTTP(w, r)

    })

}


func welcome(w http.ResponseWriter, r *http.Request) {

    ctx := r.Context()

    username := ctx.Value(contextKeyUsername)

    if username != nil {

        fmt.Fprintf(w, fmt.Sprintf("hello %s", username.(string)))

    } else {

        fmt.Fprintf(w, "hello")

    }

}


func main() {

    mux := http.NewServeMux()

    mux.HandleFunc("/welcome", welcome)

    chain := logMiddleware(authMiddleware(mux))

    http.ListenAndServe(":5050", chain)

}

虽然 get 请求127.0.0.1:5050/welcome确实返回了预期的字符串hello user123,但日志的输出是:


annonyous has accessed /welcome, took 0

由于请求是作为指针传递的,我预计在执行延迟时,上下文将包含预期username值。


我在这里错过了什么?


临摹微笑
浏览 108回答 1
1回答

慕尼黑8549860

WithContext返回请求的浅表副本,即创建的请求与从中读取上下文的authMiddleware请求不同。logMiddleware您可以让根中间件(在本例中为logMiddleware)创建带值的上下文和浅请求副本,但不是普通字符串在上下文中存储非零指针authMiddleware,然后使用指针间接分配指针指向的值,然后logMiddleware,在next退出后,可以取消引用该指针以访问该值。并且为了避免不愉快的取消引用,您可以使用指向带有字符串字段的结构的指针,而不是指向字符串的指针。type ctxKey uint8const userKey ctxKey = 0type user struct{ name string }func logMiddleware(next http.Handler) http.Handler {    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {        u := new(user)        r = r.WithContext(context.WithValue(r.Context(), userKey, u))        defer func(start time.Time) {            if u.name != "" {                fmt.Printf("user %s has accessed %s, took %s\n", u.name, r.URL.Path, time.Since(start))            } else {                fmt.Printf("annonyous has accessed %s, took %s\n", r.URL.Path, time.Since(start))            }        }(time.Now())        next.ServeHTTP(w, r)    })}func authMiddleware(next http.Handler) http.Handler {    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {        if u, ok := r.Context().Value(userKey).(*user); ok {            u.name = "user123"        }        next.ServeHTTP(w, r)    })}func welcome(w http.ResponseWriter, r *http.Request) {    if u, ok := r.Context().Value(userKey).(*user); ok && u.name != "" {        fmt.Fprintf(w, "hello %s", u.name)    } else {        fmt.Fprintf(w, "hello")    }}https://go.dev/play/p/N7vmjQ7iLM1
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go