net/http.TimeoutHandler în Go

Împachetează un handler cu o limită de timp: dacă nu termină în dt, clientul primește 503 cu mesajul dat.

Sintaxă
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler

Ce face net/http.TimeoutHandler

http.TimeoutHandler(h, 2*time.Second, "serverul este ocupat") returnează un handler care rulează h cu o limită de timp. Dacă h termină la timp, răspunsul lui ajunge normal la client. Dacă nu, clientul primește 503 Service Unavailable cu mesajul dat, iar scrierile ulterioare ale lui h eșuează cu http.ErrHandlerTimeout.

Contextul cererii (r.Context()) primit de h este anulat la expirare, deci un handler bine scris se oprește singur. Spre deosebire de WriteTimeout din http.Server, limita se aplică doar handler-elor pe care le împachetezi și clientul primește un răspuns clar.

Parametri

Parametru Descriere
h Handler-ul de limitat.
dt Timpul maxim.
msg Corpul răspunsului 503 (gol = un mesaj HTML implicit).

Ce returnează

Un http.Handler nou.

Exemple

Exemplul 1: Raport rapid și raport lent

package main

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

func raport(w http.ResponseWriter, r *http.Request) {
	durata, _ := time.ParseDuration(r.URL.Query().Get("durata"))
	select {
	case <-time.After(durata):
		fmt.Fprint(w, "raport generat")
	case <-r.Context().Done():
		// limita a expirat; ne oprim
	}
}

func main() {
	h := http.TimeoutHandler(http.HandlerFunc(raport), 100*time.Millisecond, "raportul durează prea mult, încearcă mai târziu")
	for _, d := range []string{"10ms", "2s"} {
		rec := httptest.NewRecorder()
		h.ServeHTTP(rec, httptest.NewRequest("GET", "/raport?durata="+d, nil))
		fmt.Printf("durata=%-5s %d %s\n", d, rec.Code, rec.Body.String())
	}
}

Rezultat

durata=10ms  200 raport generat
durata=2s    503 raportul durează prea mult, încearcă mai târziu

Exemplul 2: Limită doar pe o rută

Doar /export are limită. Cu mesajul gol, Go trimite o mică pagină HTML implicită.

package main

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

func main() {
	lent := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		select {
		case <-time.After(time.Second):
			fmt.Fprint(w, "export gata")
		case <-r.Context().Done():
		}
	})
	mux := http.NewServeMux()
	mux.Handle("/export", http.TimeoutHandler(lent, 50*time.Millisecond, ""))
	mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "pong") })
	srv := httptest.NewServer(mux)
	defer srv.Close()

	for _, cale := range []string{"/ping", "/export"} {
		resp, err := http.Get(srv.URL + cale)
		if err != nil {
			fmt.Println(err)
			return
		}
		corp, _ := io.ReadAll(resp.Body)
		resp.Body.Close()
		fmt.Printf("%-8s %s | %s\n", cale, resp.Status, corp)
	}
}

Rezultat

/ping    200 OK | pong
/export  503 Service Unavailable | <html><head><title>Timeout</title></head><body><h1>Timeout</h1></body></html>

Greșeli frecvente

  • Handler-ul ignoră r.Context() și continuă să lucreze după expirare: clientul a primit 503, dar serverul consumă resurse degeaba.
  • Folosești TimeoutHandler pentru răspunsuri transmise în flux (streaming): nu suportă http.Flusher.

Comenzi înrudite

  • net/http.Server: Serverul HTTP configurabil: adresă, handler, limite de timp; ListenAndServe îl pornește, Shutdown îl oprește curat.
  • context.WithTimeout: Creează un context care se anulează singur după durata dată; limita de timp standard pentru cereri și interogări.
  • net/http.Handler: Interfața oricărui obiect care răspunde la cereri HTTP (metoda ServeHTTP); HandlerFunc transformă o funcție în Handler.