Ce face context.WithValue
context.WithValue(parinte, cheie, valoare) returnează un context nou care conține valoarea. Oricine primește acest context (sau un context creat din el) o citește cu ctx.Value(cheie), care returnează any (o convertești cu v.(Tip)) sau nil dacă cheia lipsește. Anularea și termenul limită sunt moștenite de la părinte.
Cheia trebuie să fie de un tip propriu, neexportat (de exemplu type cheieUtilizator struct{}), ca să nu se ciocnească cu cheile altor pachete; un string simplu este o greșeală frecventă. Modelul obișnuit: pachetul oferă funcții care ascund cheia, CuUtilizator(ctx, u) și UtilizatorDin(ctx).
Valorile din context sunt pentru date care țin de cerere și trec prin mai multe straturi: ID-ul cererii pentru jurnale, utilizatorul autentificat de un middleware, informații de urmărire. Nu le folosi pentru parametri opționali ai funcțiilor: aceia se dau explicit.
Parametri
| Parametru | Descriere |
|---|---|
parent |
Contextul părinte. |
key |
Cheia; un tip propriu, comparabil. |
val |
Valoarea păstrată. |
Ce returnează
Un context nou cu valoarea adăugată.
Exemple
Exemplul 1: ID-ul cererii în jurnale
package main
import (
"context"
"fmt"
)
type cheieCerere struct{}
func cuIDCerere(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, cheieCerere{}, id)
}
func idCerere(ctx context.Context) string {
id, ok := ctx.Value(cheieCerere{}).(string)
if !ok {
return "fara-id"
}
return id
}
func log(ctx context.Context, mesaj string) {
fmt.Printf("[%s] %s\n", idCerere(ctx), mesaj)
}
func salveazaComanda(ctx context.Context) {
log(ctx, "salvez comanda în baza de date")
}
func main() {
ctx := cuIDCerere(context.Background(), "req-7f3a")
log(ctx, "cerere primită")
salveazaComanda(ctx)
log(context.Background(), "mesaj fără cerere")
}
Rezultat
[req-7f3a] cerere primită
[req-7f3a] salvez comanda în baza de date
[fara-id] mesaj fără cerere
Exemplul 2: Utilizatorul pus de un middleware
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
)
type cheieUtilizator struct{}
type Utilizator struct {
Nume string
Admin bool
}
func autentificare(urmator http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
u := Utilizator{Nume: "anonim"}
if r.Header.Get("X-Token") == "tok-ana" {
u = Utilizator{Nume: "Ana", Admin: true}
}
ctx := context.WithValue(r.Context(), cheieUtilizator{}, u)
urmator.ServeHTTP(w, r.WithContext(ctx))
})
}
func panou(w http.ResponseWriter, r *http.Request) {
u := r.Context().Value(cheieUtilizator{}).(Utilizator)
fmt.Fprintf(w, "Salut, %s (admin: %v)", u.Nume, u.Admin)
}
func main() {
h := autentificare(http.HandlerFunc(panou))
for _, token := range []string{"", "tok-ana"} {
req := httptest.NewRequest("GET", "/panou", nil)
req.Header.Set("X-Token", token)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
fmt.Println(rec.Body.String())
}
}
Rezultat
Salut, anonim (admin: false)
Salut, Ana (admin: true)
Greșeli frecvente
- Folosești un
stringca cheie (WithValue(ctx, "user", u)): alt pachet poate folosi aceeași cheie și îți suprascrie valoarea.go vetși alte unelte avertizează; folosește un tip propriu. - Faci
ctx.Value(k).(Tip)fără, ok: dacă valoarea lipsește, programul se oprește cu panică. - Pui în context conexiunea la baza de date sau setările aplicației: dependențele se transmit explicit (câmpuri în structuri, parametri), nu ascunse în context.
Comenzi înrudite
context.Context: Interfața care duce prin program semnalul de anulare, termenul limită și valori legate de o cerere (ctx).context.Background: Contextul rădăcină, gol: nu se anulează, nu are termen și nici valori; de la el pornesc toate celelalte contexte.context.WithoutCancel: Creează un context copil care păstrează valorile părintelui, dar nu se anulează când părintele este anulat.net/http.Request: Structura unei cereri HTTP: metoda, URL-ul, antetele, corpul și contextul (Context()) care se anulează când clientul pleacă.