net/http: Go's Batteries-Included HTTP Stack
Go ships a complete HTTP server and client in the standard library.
Search across all documentation pages
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.
net/http implements HTTP/1.1 and HTTP/2 servers and clients with routing (ServeMux), handler interfaces, connection pooling, TLS, and utilities for testing and proxying.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.
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.
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 |
ListenAndServe is production-ready out of the box - It works for demos, but missing timeouts, TLS, and graceful shutdown will bite under real traffic.func(http.Handler) http.Handler; frameworks only collect and order wrappers.http.Get is fine inside handlers - http.DefaultClient has no timeout and shares global state; use a dedicated http.Client per dependency.{id} segments without gorilla/mux.Close() response bodies leaks connections and exhausts the pool.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.
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.
Each *http.Request carries Context() that cancels on client disconnect and server timeout boundaries.
Handlers should pass it to all blocking downstream calls.
http.DefaultServeMux is a global singleton.
Prefer a dedicated http.NewServeMux() per service so tests and libraries do not register conflicting routes.
Yes for TLS listeners with ALPN negotiation.
Cleartext h2c requires explicit Server and Transport configuration.
Handler is an interface with ServeHTTP.
HandlerFunc is a function type that implements Handler, letting you write func(w, r) handlers without a struct.
Yes.
Terminate TLS at the ingress or pod, set ReadHeaderTimeout, and use Shutdown for rolling deploys.
httptest.ResponseRecorder captures responses in memory.
httptest.NewServer spins a real listener for integration-style tests.
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.
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.
The connection aborts unless recovery middleware catches the panic and returns 500.
Always add recovery at the outermost middleware layer in production.
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.
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).
Reviewed by Chris St. John·Last updated Jul 16, 2026