Frontend Labs
⋯

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).


Generated from real-life-golang-guide.md · 8 Oct 2026 16:36