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.ErrServerClosedca pe o eroare reală (log.Fatal(srv.ListenAndServe())) și programul iese înainte caShutdownsă termine. Verifică eroarea cuerrors.Is. - Lași programul să iasă imediat după ce apelezi
Shutdownîntr-un goroutine: cererile în curs sunt tăiate.maintrebuie să aștepte caShutdownsă 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.