在我的日志(log)中间件(链中的第一个)中,我需要访问在链的更下游的某个身份验证中间件中编写的一些上下文,并且只有在执行处理程序本身之后才能访问.

Side note: The logging middleware needs to be called first since I need to log the duration of the request including the time spend in middleware. Also the auth middleware is able to abort a request when permissions are not sufficient. in that case I need to log the failed request as well.

我的问题是,从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)
}

尽管对127.0.0.1:5050/welcome的GET请求确实返回预期的字符串hello user123,但日志(log)的输出是:

annonyous has accessed /welcome, took 0

因为请求是作为指针传递的,所以我本以为在执行延迟时,上下文将包含预期的username值.

我错过了什么吗?

推荐答案

WithContext返回该请求的浅拷贝,即由authMiddleware创建的请求与logMiddleware从中读取上下文的请求不同.

您可以让根中间件(在本例中为logMiddleware)创建带值上下文和浅层请求副本,但不是使用普通字符串在上下文中存储non-nil pointer,然后让authMiddleware使用指针间接赋值,然后在next退出后,logMiddleware可以取消对该指针的引用以访问该值.

为了避免令人不快的取消引用,您可以使用指向带有字符串字段的 struct 的指针,而不是指向字符串的指针.

type ctxKey uint8

const userKey ctxKey = 0

type 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

Go相关问答推荐

"k8s.io/apimachinery/pkg/runtime.对象(缺少方法DeepCopyBody)

如何从google.golang.org/grpc/stats包中将golang中不同事件的输出进行组合,以获取func HandlePRC

如何修复Go中调用GetRawInputDeviceInfA Windows API函数时的错误?

GORM Find方法中缺少字段

如何使用GO GIN从Auth0 JWT内标识检索权限

如何解析Go-Gin多部分请求中的 struct 切片

使用GOTK3和librsvg在Go中如何加载内联SVG?

GoLang: gocui 边框 colored颜色

Golang crypto/rand 线程安全吗?

golang yaml 马歇尔网址

Wire google Inject with multi return from provider 函数

如何仅提取时间作为持续时间

使用 GO 在侧 tar 文件中提取 tar 文件的最快方法

没有堆栈跟踪的 go 程序崩溃是什么意思?

将 Golang Gin 与 AWS Lambda 和无服务器与代理路径一起使用

为什么 x/net/html Token().Attr 上的 len 在此处为空切片返回非零值?

如何在 Windows 中使用 github.com/AllenDang/giu 和 github.com/gordonklaus/portaudio 构建 GO 程序

go mod tidy 错误消息:但是 go 1.16 会 Select

如何断言类型是指向golang中接口的指针

在 Go 泛型中,如何对联合约束中的类型使用通用方法?