net/http.Handler în Go

Interfața oricărui obiect care răspunde la cereri HTTP (metoda ServeHTTP); HandlerFunc transformă o funcție în Handler.

Sintaxă
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)

Ce face net/http.Handler

Tot serverul HTTP din Go se sprijină pe o singură interfață: http.Handler, cu metoda ServeHTTP(w, r). Routerul (ServeMux), serverul de fișiere, TimeoutHandler și orice tip al tău cu această metodă sunt handler-e și se pot combina între ele.

http.HandlerFunc este un tip-funcție care are metoda ServeHTTP: http.HandlerFunc(f) transformă o funcție obișnuită într-un Handler. Așa se scriu și middleware-urile: funcții care primesc un handler și returnează altul, care face ceva înainte și după (jurnalizare, autentificare, măsurarea timpului).

Ce returnează

HandlerFunc(f) este o conversie de tip, nu un apel: rezultatul este un http.Handler.

Exemple

Exemplul 1: Tip propriu ca handler

Structura ține date (aici un contor), iar metoda ServeHTTP o face handler.

package main

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

type Contor struct {
	vizite atomic.Int64
}

func (c *Contor) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	n := c.vizite.Add(1)
	fmt.Fprintf(w, "Ești vizitatorul cu numărul %d", n)
}

func main() {
	var h http.Handler = &Contor{}
	for range 3 {
		rec := httptest.NewRecorder()
		h.ServeHTTP(rec, httptest.NewRequest("GET", "/", nil))
		fmt.Println(rec.Body.String())
	}
}

Rezultat

Ești vizitatorul cu numărul 1
Ești vizitatorul cu numărul 2
Ești vizitatorul cu numărul 3

Exemplul 2: Middleware cu HandlerFunc

autentificare primește un handler și returnează altul, care verifică un antet înainte să-l apeleze pe cel original.

package main

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

func autentificare(urmator http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("X-Cheie") != "secret123" {
			http.Error(w, "acces interzis", http.StatusUnauthorized)
			return
		}
		urmator.ServeHTTP(w, r)
	})
}

func main() {
	panou := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Panoul de administrare")
	})
	h := autentificare(panou)

	for _, cheie := range []string{"", "secret123"} {
		req := httptest.NewRequest("GET", "/admin", nil)
		if cheie != "" {
			req.Header.Set("X-Cheie", cheie)
		}
		rec := httptest.NewRecorder()
		h.ServeHTTP(rec, req)
		fmt.Printf("cheie=%q -> %d %s\n", cheie, rec.Code, strings.TrimSpace(rec.Body.String()))
	}
}

Rezultat

cheie="" -> 401 acces interzis
cheie="secret123" -> 200 Panoul de administrare

Greșeli frecvente

  • Dai o funcție simplă unde se cere un http.Handler (mux.Handle("/", f)): nu compilează. Folosește mux.HandleFunc sau http.HandlerFunc(f).
  • Într-un middleware uiți return după răspunsul de eroare și apelezi totuși handler-ul următor: clientul primește două răspunsuri amestecate.

Comenzi înrudite

  • net/http.HandleFunc: Înregistrează o rută în routerul global (DefaultServeMux): HandleFunc primește o funcție, Handle un Handler.
  • net/http.NewServeMux: Creează un router (ServeMux) propriu care alege handler-ul după metoda și calea cererii; HandleFunc și Handle adaugă rute.
  • net/http.ResponseWriter: Interfața prin care handler-ul construiește răspunsul: antete (Header), cod de stare (WriteHeader) și corp (Write).
  • net/http.TimeoutHandler: Împachetează un handler cu o limită de timp: dacă nu termină în dt, clientul primește 503 cu mesajul dat.