在我的日志中間件(鏈中的第一個)中,我需要訪問一些背景關系,這些背景關系寫在一些 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確實回傳了預期的 string hello user123,但日志的輸出是:
annonyous has accessed /welcome, took 0
由于請求作為指標傳遞,我預計在執行延遲時,背景關系將包含預期username值。
我在這里想念什么?
uj5u.com熱心網友回復:
WithContext回傳請求的淺表副本,即由創建的authMiddleware請求與從中logMiddleware讀取背景關系的請求不同。
您可以讓根中間件(在這種情況下是logMiddleware)創建帶有值的背景關系和淺請求副本,但不是純字串,而是在背景關系中存盤一個非零指標,然后authMiddleware使用使用指標間接分配指標指向的值,然后退出logMiddleware后next,可以取消參考該指標以訪問該值。
為了避免令人不快的取消參考,您可以使用指向帶有字串欄位的結構的指標,而不是指向字串的指標。
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
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/512293.html
標籤:http去中间件
上一篇:lwp等效于帶有--data-raw$符號的curl
下一篇:隨后的有序HTTP呼叫
