24. net/http fundamentals: client, server, middleware
24.1 The server side
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUser)
mux.HandleFunc("POST /users", createUser)
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
log.Fatal(srv.ListenAndServe())
Always set explicit timeouts on http.Server — the zero-value
defaults are "no timeout," which turns a slow or malicious client into a
resource leak (this is exactly the kind of gap the WAF chapter, 49,
defends against from the outside).
24.2 The client side
client := &http.Client{Timeout: 5 * time.Second}
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close() // ALWAYS — otherwise connections leak from the pool
Reuse a single http.Client (it's safe for concurrent use and pools
connections internally) rather than constructing one per request.
24.3 Middleware: the http.Handler wrapping pattern
func withLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
})
}
func withRecover(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic: %v", err)
http.Error(w, "internal error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
handler := withLogging(withRecover(mux))
log.Fatal(http.ListenAndServe(":8080", handler))
Every middleware in Go is a function that takes a Handler and returns a
Handler — no framework needed, just closures composing in order. This
exact shape reappears as the WAF middleware chain in Chapter 49.
24.4 Real-world example
A production Go API almost always layers: recover → request-ID injection
→ logging → auth → rate limiting → the actual mux — each layer a
one-function Handler-wrapping Handler, composed once at startup. No
runtime dependency injection container required.
Applied in the series: HTTPS guide, Part 9: a production server with every timeout set (Ch 19), a reverse proxy (Ch 20), and phase-by-phase request tracing with net/http/httptrace (Ch 17).