net/http: Go's Batteries-Included HTTP Stack
Go ships a complete HTTP server and client in the standard library.
You can build APIs, proxies, and CLI tools that call remote services without importing a web framework.
net/http Basics collects runnable snippets; sibling articles cover routing, clients, middleware, HTTP/2, testing, and production configuration.
Summary
net/httpimplements HTTP/1.1 and HTTP/2 servers and clients with routing (ServeMux), handler interfaces, connection pooling, TLS, and utilities for testing and proxying.- Insight: Many Go services never need a third-party framework because the stdlib already covers listening, parsing, routing, timeouts, and graceful shutdown when configured correctly.
- Key Concepts: Handler, ServeMux, http.Server, http.Client, Transport, middleware, context, ReverseProxy.
- When to Use: Microservices, internal APIs, sidecars, CLIs, and gateways where you want minimal dependencies and full control over the HTTP stack.
- Limitations/Trade-offs: No batteries for validation, ORM, or opinionated project layout; you compose middleware yourself; production tuning (timeouts, TLS, shutdown) is your responsibility.
- Related Topics: context cancellation, chi/gin/echo routers, gRPC gateways, observability middleware, graceful shutdown patterns.
Foundations
HTTP in Go centers on one interface:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}Anything that can answer an HTTP request implements ServeHTTP.
http.HandlerFunc adapts plain functions, so most handlers are closures or small structs.
On the server side, http.Server owns the listener, TLS config, and timeout fields.
http.ListenAndServe is a convenience wrapper that creates a default Server and blocks until the process exits or the listener fails.
Routing goes through http.ServeMux, which maps host, method, and path patterns to handlers.
Go 1.22 expanded pattern matching so routes like GET /users/{id} and POST /items/ are first-class without a third-party router.
On the client side, http.Client sends requests.
Each client holds an http.Transport that manages TCP connections, TLS handshakes, keep-alives, and HTTP/2 upgrade when enabled.
The default http.DefaultClient has no request timeout and reuses a shared transport - fine for quick scripts, risky in servers.
Middleware is not a stdlib keyword.
It is the pattern of wrapping one http.Handler with another: logging, auth, panic recovery, and request IDs all compose as nested wrappers around your business handler.
Mechanics & Interactions
A request enters through the listener, is parsed into *http.Request, matched by the mux, and dispatched to the innermost handler.
The handler writes to http.ResponseWriter; headers and status must be set before the body streams.
r.Context() carries cancellation when the client disconnects or server timeouts fire.
Downstream code should pass that context into database and RPC calls.
Client net/http Server
| |
|------ TCP / TLS ------------>|
| | ReadHeaderTimeout
| | ServeMux match
| | middleware chain
| | Handler.ServeHTTP
|<----- response --------------|
Outbound calls mirror the path in reverse: Client.Do selects or dials a connection from the pool, writes the request, reads the response, and returns the body (which the caller must close).
| Component | Server role | Client role |
|---|---|---|
ServeMux | Route to handler | N/A |
http.Server | Timeouts, TLS, shutdown | N/A |
http.Client | N/A | Send requests, honor context |
Transport | N/A | Pool connections, HTTP/2 |
httputil.ReverseProxy | Forward upstream | N/A |
Frameworks like chi, gin, and echo still sit on net/http handlers.
They add richer routing, binding, and middleware registries but export http.Handler compatible entry points.
Advanced Considerations & Applications
Production services set explicit server timeouts: ReadHeaderTimeout, ReadTimeout, WriteTimeout, and IdleTimeout each guard a different failure mode.
Slowloris attacks, hung uploads, and idle connection leaks are all stdlib problems you solve with fields on http.Server, not framework magic.
HTTP/2 is enabled by default for TLS listeners in recent Go releases.
Cleartext HTTP/2 (h2c) requires explicit configuration when you terminate TLS at a load balancer and speak h2c internally.
httputil.ReverseProxy implements gateway and BFF patterns: rewrite paths, inject headers, and stream bodies without buffering entire payloads.
Pair it with http.Transport tuned for upstream keep-alives.
Observability hooks attach at middleware boundaries: wrap ResponseWriter to capture status codes, propagate trace IDs from headers into context, and emit metrics per route pattern.
| Approach | Strength | Weakness | Best Fit |
|---|---|---|---|
Raw net/http | Minimal deps, full control | More boilerplate for routing/features | Small APIs, proxies, sidecars |
| chi / mux routers | Stdlib-compatible, light | Still no validation/ORM | REST services wanting better routes |
| gin / echo | Fast DX, binding helpers | Heavier opinions | Teams wanting batteries |
| gRPC + grpc-gateway | Strong contracts | Not HTTP-native | Service meshes, internal RPC |
Common Misconceptions
ListenAndServeis production-ready out of the box - It works for demos, but missing timeouts, TLS, and graceful shutdown will bite under real traffic.- You need a framework to write middleware - Middleware is just
func(http.Handler) http.Handler; frameworks only collect and order wrappers. http.Getis fine inside handlers -http.DefaultClienthas no timeout and shares global state; use a dedicatedhttp.Clientper dependency.- ServeMux cannot do RESTful routing - Go 1.22+ patterns support methods, wildcards, and
{id}segments without gorilla/mux. - Closing the response body is optional on the server - On the client, failing to
Close()response bodies leaks connections and exhausts the pool.
FAQs
What does "batteries-included" mean for net/http?
The standard library provides server, client, TLS, HTTP/2, test helpers, and reverse proxy utilities in one import path.
It does not include validation, templating beyond basic helpers, or project scaffolding.
When should I reach for a framework instead?
Reach for chi, gin, or echo when you want grouped routes, parameter binding, or a large middleware ecosystem out of the box.
Stay on raw net/http when dependency count and explicit control matter more than DX shortcuts.
How does net/http relate to context?
Each *http.Request carries Context() that cancels on client disconnect and server timeout boundaries.
Handlers should pass it to all blocking downstream calls.
Is the default ServeMux safe to use?
http.DefaultServeMux is a global singleton.
Prefer a dedicated http.NewServeMux() per service so tests and libraries do not register conflicting routes.
Does net/http support HTTP/2?
Yes for TLS listeners with ALPN negotiation.
Cleartext h2c requires explicit Server and Transport configuration.
What is the difference between Handler and HandlerFunc?
Handler is an interface with ServeHTTP.
HandlerFunc is a function type that implements Handler, letting you write func(w, r) handlers without a struct.
Can I use net/http behind Kubernetes ingress?
Yes.
Terminate TLS at the ingress or pod, set ReadHeaderTimeout, and use Shutdown for rolling deploys.
How do I test handlers without a real network?
httptest.ResponseRecorder captures responses in memory.
httptest.NewServer spins a real listener for integration-style tests.
Why do I need a custom Transport?
http.DefaultTransport pooling is shared and its defaults may not match your latency or TLS needs.
A dedicated Transport per outbound dependency isolates tuning and timeouts.
Is net/http fast enough for high QPS?
Yes when handlers avoid per-request allocations, reuse clients, and set idle connection limits.
Profile before assuming a framework is faster - often the bottleneck is application logic, not the stdlib.
What happens if I panic inside a handler?
The connection aborts unless recovery middleware catches the panic and returns 500.
Always add recovery at the outermost middleware layer in production.
How does graceful shutdown work?
Call server.Shutdown(ctx) to stop accepting new connections and wait for in-flight requests.
Pair with signal handling in main and a bounded shutdown context.
Related
- net/http Basics - runnable starting examples
- HTTP Servers, Handlers & ServeMux Patterns - routing detail
- HTTP Clients, Transport & Connection Pooling - outbound calls
- Middleware Chains & Request Context - wrapping handlers
- Production net/http Configuration Checklist - deploy readiness
- Cancellation Propagation in HTTP Handlers - context from requests
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).