net/http.ResponseWriter în Go

Interfața prin care handler-ul construiește răspunsul: antete (Header), cod de stare (WriteHeader) și corp (Write).

Sintaxă
type ResponseWriter interface {
    Header() Header
    Write([]byte) (int, error)
    WriteHeader(statusCode int)
}

Ce face net/http.ResponseWriter

Handler-ul primește un http.ResponseWriter (de obicei numit w) și scrie răspunsul prin el, în trei pași, în această ordine: setează antetele cu w.Header().Set(...), trimite codul de stare cu w.WriteHeader(201), apoi scrie corpul cu w.Write, fmt.Fprintf(w, ...) sau json.NewEncoder(w).Encode(...).

Ordinea contează: primul Write trimite automat codul 200 și antetele, iar după acel moment schimbările de antete și un nou WriteHeader sunt ignorate (Go afișează în jurnal superfluous response.WriteHeader call). Dacă nu setezi Content-Type, Go îl ghicește din primii 512 octeți ai corpului.

Ce returnează

Header() returnează harta de antete a răspunsului. Write returnează numărul de octeți scriși și o eroare.

Exemple

Exemplul 1: Antete, cod de stare și corp

package main

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

func creeazaComanda(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Header().Set("Location", "/comenzi/1001")
	w.WriteHeader(http.StatusCreated)
	fmt.Fprint(w, `{"id": 1001, "stare": "nouă"}`)
}

func main() {
	rec := httptest.NewRecorder()
	creeazaComanda(rec, httptest.NewRequest("POST", "/comenzi", nil))

	rez := rec.Result()
	fmt.Println("Status:  ", rez.Status)
	fmt.Println("Tip:     ", rez.Header.Get("Content-Type"))
	fmt.Println("Location:", rez.Header.Get("Location"))
	fmt.Println("Corp:    ", rec.Body.String())
}

Rezultat

Status:   201 Created
Tip:      application/json; charset=utf-8
Location: /comenzi/1001
Corp:     {"id": 1001, "stare": "nouă"}

Exemplul 2: Tipul de conținut ghicit și ordinea greșită

Primul răspuns nu setează Content-Type, deci Go îl deduce. În al doilea, antetul și codul sunt setate după Write și se pierd.

package main

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

func main() {
	html := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "<!DOCTYPE html><h1>Salut</h1>")
	})
	rec := httptest.NewRecorder()
	html.ServeHTTP(rec, httptest.NewRequest("GET", "/", nil))
	fmt.Println("Ghicit:", rec.Header().Get("Content-Type"))

	gresit := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("text"))
		w.Header().Set("X-Versiune", "2") // prea târziu
	})
	rec = httptest.NewRecorder()
	gresit.ServeHTTP(rec, httptest.NewRequest("GET", "/", nil))
	rez := rec.Result()
	fmt.Println("Cod:", rez.StatusCode, "X-Versiune:", rez.Header.Get("X-Versiune") == "")
}

Rezultat

Ghicit: text/html; charset=utf-8
Cod: 200 X-Versiune: true

Greșeli frecvente

  • Setezi antete după ce ai scris corpul. Antetele și codul pleacă la primul Write; setează-le înainte.
  • Apelezi w.WriteHeader(http.StatusOK) după http.Error sau după Write: apare avertismentul superfluous response.WriteHeader call și codul nu se schimbă.
  • Uiți return după un răspuns de eroare și handler-ul continuă să scrie în același răspuns.

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.Error: Error trimite un răspuns de eroare cu cod și mesaj text; NotFound trimite 404; StatusText dă textul unui cod (404 → Not Found).
  • net/http.Handler: Interfața oricărui obiect care răspunde la cereri HTTP (metoda ServeHTTP); HandlerFunc transformă o funcție în Handler.
  • encoding/json.NewEncoder: Creează un Encoder care scrie JSON direct într-un io.Writer (răspuns HTTP, fișier, ecran); Encode scrie o valoare.