net/http.Server în Go

Serverul HTTP configurabil: adresă, handler, limite de timp; ListenAndServe îl pornește, Shutdown îl oprește curat.

Sintaxă
type Server struct {
    Addr              string
    Handler           Handler
    ReadTimeout       time.Duration
    ReadHeaderTimeout time.Duration
    WriteTimeout      time.Duration
    IdleTimeout       time.Duration
    ...
}
func (s *Server) ListenAndServe() error
func (s *Server) Serve(l net.Listener) error
func (s *Server) Shutdown(ctx context.Context) error   // Go 1.8

Ce face net/http.Server

http.ListenAndServe creează în spate un http.Server cu valori implicite. Când ai nevoie de control, îl creezi tu: Addr (adresa), Handler (routerul), și limitele de timp ReadHeaderTimeout, ReadTimeout, WriteTimeout, IdleTimeout, care protejează serverul de clienți lenți sau rău intenționați.

srv.ListenAndServe() pornește serverul și blochează. srv.Shutdown(ctx) îl oprește curat: nu mai acceptă conexiuni noi, așteaptă cererile în curs să termine (cel mult cât permite ctx), apoi returnează. Din acel moment ListenAndServe returnează http.ErrServerClosed, care nu este o eroare reală.

Modelul obișnuit: pornești serverul într-un goroutine, aștepți semnalul de oprire (Ctrl+C, signal.NotifyContext), apoi apelezi Shutdown cu un context de câteva secunde. Așa nu tai cererile utilizatorilor la fiecare actualizare a aplicației.

Ce returnează

ListenAndServe: http.ErrServerClosed după Shutdown, altfel eroarea. Shutdown: nil sau eroarea contextului dacă timpul a expirat.

Exemple

Exemplul 1: Pornire și oprire curată (rulează local)

net.Listen pe portul 0 alege un port liber, iar Serve face ce face ListenAndServe. Cererea lentă pornită înainte de Shutdown apucă să termine.

package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"time"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/comanda", func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(100 * time.Millisecond) // o cerere încă în lucru
		fmt.Fprint(w, "comanda 1001 salvată")
	})
	srv := &http.Server{
		Handler:           mux,
		ReadHeaderTimeout: 5 * time.Second,
		WriteTimeout:      10 * time.Second,
	}
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		fmt.Println(err)
		return
	}
	oprit := make(chan error, 1)
	go func() { oprit <- srv.Serve(ln) }()

	raspuns := make(chan string, 1)
	go func() {
		resp, err := http.Get("http://" + ln.Addr().String() + "/comanda")
		if err != nil {
			raspuns <- err.Error()
			return
		}
		corp, _ := io.ReadAll(resp.Body)
		resp.Body.Close()
		raspuns <- string(corp)
	}()

	time.Sleep(30 * time.Millisecond) // cererea a ajuns la server
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	fmt.Println("Shutdown:", srv.Shutdown(ctx))
	fmt.Println("Clientul a primit:", <-raspuns)
	err = <-oprit
	fmt.Println("Serve a returnat:", err, "| ErrServerClosed:", errors.Is(err, http.ErrServerClosed))
}

Rezultat

Shutdown: <nil>
Clientul a primit: comanda 1001 salvată
Serve a returnat: http: Server closed | ErrServerClosed: true

Exemplul 2: Server de producție cu oprire la Ctrl+C

Programul rulează până la Ctrl+C, apoi le dă cererilor în curs până la 10 secunde să termine.

package main

import (
	"context"
	"errors"
	"log"
	"net/http"
	"os"
	"os/signal"
	"time"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Salut din Go!"))
	})
	srv := &http.Server{
		Addr:              ":8080",
		Handler:           mux,
		ReadHeaderTimeout: 5 * time.Second,
		ReadTimeout:       10 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       2 * time.Minute,
	}

	go func() {
		log.Println("Ascult pe", srv.Addr)
		if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
			log.Fatal(err)
		}
	}()

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()
	<-ctx.Done()

	log.Println("Opresc serverul...")
	ctxOprire, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctxOprire); err != nil {
		log.Println("oprire forțată:", err)
	}
	log.Println("Gata")
}

Rezultat posibil (în terminal)

2026/10/19 12:00:00 Ascult pe :8080
^C2026/10/19 12:05:13 Opresc serverul...
2026/10/19 12:05:13 Gata

Greșeli frecvente

  • Tratezi http.ErrServerClosed ca pe o eroare reală (log.Fatal(srv.ListenAndServe())) și programul iese înainte ca Shutdown să termine. Verifică eroarea cu errors.Is.
  • Lași programul să iasă imediat după ce apelezi Shutdown într-un goroutine: cererile în curs sunt tăiate. main trebuie să aștepte ca Shutdown să returneze.
  • Lași toate limitele de timp la zero (fără limită) pe un server public: clienții lenți pot epuiza conexiunile (atacul „Slowloris”). Setează cel puțin ReadHeaderTimeout.

Comenzi înrudite

  • net/http.ListenAndServe: Pornește un server web pe adresa dată (ex. „:8080”) și răspunde la cereri cu handler-ul dat; rulează până la oprire.
  • net/http.TimeoutHandler: Împachetează un handler cu o limită de timp: dacă nu termină în dt, clientul primește 503 cu mesajul dat.
  • net/http.NewServeMux: Creează un router (ServeMux) propriu care alege handler-ul după metoda și calea cererii; HandleFunc și Handle adaugă rute.
  • context.WithTimeout: Creează un context care se anulează singur după durata dată; limita de timp standard pentru cereri și interogări.