net/http.Cookie în Go

Cookie descrie un cookie; SetCookie îl trimite clientului; Request.Cookie citește cookie-ul trimis de browser.

Sintaxă
type Cookie struct {
    Name, Value string
    Path, Domain string
    Expires  time.Time
    MaxAge   int
    Secure, HttpOnly bool
    SameSite SameSite
    ...
}
func SetCookie(w ResponseWriter, cookie *Cookie)
func (r *Request) Cookie(name string) (*Cookie, error)

Ce face net/http.Cookie

Un cookie este o pereche nume-valoare pe care serverul o trimite browserului, iar browserul o trimite înapoi la fiecare cerere: așa țin minte site-urile sesiunea, limba sau coșul. http.SetCookie(w, &http.Cookie{...}) adaugă antetul Set-Cookie în răspuns, deci se apelează înainte de a scrie corpul.

r.Cookie("sesiune") citește cookie-ul primit; dacă lipsește, returnează eroarea http.ErrNoCookie. r.Cookies() le returnează pe toate.

Câmpuri importante: Path (pe ce căi se trimite, de obicei "/"), MaxAge (secunde de viață; negativ șterge cookie-ul), HttpOnly (JavaScript nu îl poate citi, protecție XSS), Secure (doar prin HTTPS) și SameSite (protecție CSRF).

Ce returnează

SetCookie: nimic. Request.Cookie: *http.Cookie și o eroare.

Exemple

Exemplul 1: Setarea și citirea unui cookie

package main

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/limba", func(w http.ResponseWriter, r *http.Request) {
		http.SetCookie(w, &http.Cookie{
			Name:     "limba",
			Value:    r.URL.Query().Get("l"),
			Path:     "/",
			MaxAge:   30 * 24 * 3600,
			HttpOnly: true,
			SameSite: http.SameSiteLaxMode,
		})
		fmt.Fprint(w, "limba salvată")
	})
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		c, err := r.Cookie("limba")
		if errors.Is(err, http.ErrNoCookie) {
			fmt.Fprint(w, "limba implicită: ro")
			return
		}
		fmt.Fprint(w, "limba aleasă: ", c.Value)
	})

	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, httptest.NewRequest("GET", "/limba?l=en", nil))
	setCookie := rec.Header().Get("Set-Cookie")
	fmt.Println("Set-Cookie:", setCookie)

	rec = httptest.NewRecorder()
	mux.ServeHTTP(rec, httptest.NewRequest("GET", "/", nil))
	fmt.Println(rec.Body.String())

	req := httptest.NewRequest("GET", "/", nil)
	req.AddCookie(&http.Cookie{Name: "limba", Value: "en"})
	rec = httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	fmt.Println(rec.Body.String())
}

Rezultat

Set-Cookie: limba=en; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax
limba implicită: ro
limba aleasă: en

Exemplul 2: Ștergerea unui cookie

Un cookie se șterge trimițându-l din nou cu același nume și cale și MaxAge negativ; Go îl scrie în antet ca Max-Age=0, adică „expiră acum”.

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
)

func main() {
	iesire := func(w http.ResponseWriter, r *http.Request) {
		http.SetCookie(w, &http.Cookie{Name: "sesiune", Value: "", Path: "/", MaxAge: -1})
		fmt.Fprint(w, "te-ai delogat")
	}
	rec := httptest.NewRecorder()
	iesire(rec, httptest.NewRequest("POST", "/iesire", nil))
	fmt.Println(rec.Header().Get("Set-Cookie"))
	fmt.Println(rec.Body.String())
}

Rezultat

sesiune=; Path=/; Max-Age=0
te-ai delogat

Greșeli frecvente

  • Apelezi http.SetCookie după ce ai scris corpul: antetul nu mai pleacă și cookie-ul nu este setat.
  • Pui în cookie date sensibile sau în care ai încredere (admin=true, prețuri). Utilizatorul poate modifica orice cookie; păstrează doar un identificator de sesiune aleatoriu (vezi crypto/rand.Text).
  • Ștergi cookie-ul cu alt Path decât cel cu care a fost creat: browserul îl consideră alt cookie și îl păstrează.

Comenzi înrudite

  • net/http.Header: Antetele unei cereri sau ale unui răspuns: Get citește, Set înlocuiește, Add adaugă încă o valoare, Del șterge.
  • net/http.ResponseWriter: Interfața prin care handler-ul construiește răspunsul: antete (Header), cod de stare (WriteHeader) și corp (Write).
  • crypto/rand.Text: Returnează un text aleatoriu sigur (26 de caractere A-Z și 2-7, cel puțin 128 de biți), gata de folosit ca token sau secret.