context.WithValue în Go

Creează un context copil care poartă o pereche cheie-valoare (ID de cerere, utilizator autentificat), citită cu ctx.Value(cheie).

Sintaxă
func WithValue(parent Context, key, val any) Context
func (ctx Context) Value(key any) any

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 string ca 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ă.