In net/http, how do you write a func(http.Handler) http.Handler wrapper, and where does next.ServeHTTP sit?
answer
- same type in, same type out
- a closure that remembers next
- http.HandlerFunc adapts the closure
- the call splits in-code from out-code
- outer body once, inner body per request
basics
~20 sA net/http middleware takes the next http.Handler and returns a new one. Inside its ServeHTTP it runs setup code, calls next.ServeHTTP(w, r) to invoke the wrapped handler, then runs teardown code after that call returns.
solid answer
~40 sThe convention is `func(next http.Handler) http.Handler`. The wrapper closes over `next` and returns a new handler, almost always an `http.HandlerFunc` closure, so it satisfies the same `http.Handler` interface the thing it wraps satisfies -- that sameness is what lets wrappers nest. Inside that closure, everything written *before* `next.ServeHTTP(w, r)` runs on the way in, `next.ServeHTTP(w, r)` runs the whole rest of the chain and the final handler, and everything after that line runs on the way out once the inner handler has returned. If the wrapper returns without calling `next.ServeHTTP`, nothing deeper in the chain runs at all. Note also that the outer function body executes once, at wiring time, while only the returned closure runs per request -- so one-off setup belongs outside the closure.
code
go · 11 lines// Middleware is the shape every wrapper in the shared package uses.
type Middleware func(http.Handler) http.Handler
func WithTiming(next http.Handler) http.Handler {
// this body runs once, when the chain is built
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now() // on the way in
next.ServeHTTP(w, r) // the whole rest of the chain runs here
log.Printf("%s %s took %s", r.Method, r.URL.Path, time.Since(start))
})
}go deeper
Be ready to write the wrapper from memory: take next, return http.HandlerFunc, call next.ServeHTTP in the middle. Know that code above that call runs first and code below it runs after the inner handler finishes.
Explain the mechanics: the closure captures next, http.HandlerFunc adapts a function to the interface, the outer body runs once at wiring time and the inner closure once per request, and next.ServeHTTP is an ordinary synchronous call.
Show the judgment a reviewer applies: exactly one next.ServeHTTP per path, response mutations only before it, defer for cleanup that must survive a panic, and no shared mutable state captured across concurrent requests.
Own the shape as a contract. When several services import one middleware package, insisting on plain func(http.Handler) http.Handler rather than a bespoke interface keeps every wrapper usable with any router, any http.Server and httptest, forever.
## The interface everything hangs off `net/http` has exactly one server-side abstraction: ``` type Handler interface { ServeHTTP(ResponseWriter, *Request) } ``` A middleware in Go is not a framework concept, a registered plugin, or an interceptor the runtime knows about. It is just a **function that takes an `http.Handler` and returns an `http.Handler`**: ``` func WithX(next http.Handler) http.Handler ``` Because the input and the output are the same type, the result can be fed straight into another wrapper of the same shape. That is the entire mechanism behind "middleware chains" in Go: ordinary function composition over one interface. ## The two function bodies, and why the distinction matters A wrapper has two nested bodies, and they run at completely different times. 1. **The outer body** runs once, when you build the chain at start-up. This is where you can do work you do not want to repeat per request: compile a regexp, read config, allocate a shared object. 2. **The returned handler's body** runs once per request. This is where the request-scoped work goes. The returned handler is usually built with `http.HandlerFunc`, the adapter type whose `ServeHTTP` method simply calls the function itself. So `http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ... })` turns a plain function into an `http.Handler`. The closure captures `next`, which is how the wrapper remembers what to call. ## Before, after, and never Inside the returned handler there are three positions, and each means something different: - **Before `next.ServeHTTP(w, r)`** -- runs on the way in, before the wrapped handler has seen the request. Anything that must influence the response (setting a response header, replacing the request via `r.WithContext`, rejecting the request outright) has to happen here. - **The `next.ServeHTTP(w, r)` call itself** -- this is a synchronous, ordinary function call. It does not return until the wrapped handler, and everything the wrapped handler wraps, has finished. It is not a "continue" signal to a pipeline; the rest of the chain literally runs inside your stack frame. - **After `next.ServeHTTP(w, r)` returns** -- runs on the way out. By then the inner handler has usually already written the response, so this position is for *observing*: measuring elapsed time, emitting a log line, releasing something you acquired on the way in. There is a fourth possibility: **not calling `next.ServeHTTP` at all**. A wrapper that writes a response and returns short-circuits the request -- nothing deeper in the chain, and not the final handler, ever runs. Go has no separate "abort" API for this; you just return. Code that must run on the way out *even if the inner handler panics* goes in a `defer` inside the returned handler rather than on a plain line after the call, because a panic unwinds past plain statements but still runs deferred functions. ## Parameterised wrappers When a wrapper needs configuration, the usual shape is a constructor that returns the wrapper: ``` func WithHeader(key, value string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set(key, value) next.ServeHTTP(w, r) }) } } ``` Now there are three nested bodies: the configuration call, the wiring call, and the per-request handler. A shared middleware package that several services import is nearly always written this way, so each service can supply its own values while the shape stays uniform. ## Why this shape and not a method or an interface You could define your own `Middleware` interface, and some codebases do (`type Middleware func(http.Handler) http.Handler` is a common named alias, purely for readability). But because the plain function type already composes and already fits every helper that accepts `http.Handler`, anything more elaborate buys nothing. A wrapper written this way works with any router that accepts an `http.Handler`, any `http.Server`, and `httptest` in tests, without adaptation. ## What a reviewer looks for Given a wrapper in a pull request, the things worth checking are: does it call `next.ServeHTTP` exactly once on every path where the request should continue; is anything that must be set on the response done *before* that call; is per-request state kept in the closure's local variables rather than in package-level variables shared by every concurrent request; and does the wrapper return early only where a short-circuit is genuinely intended.
- What happens if your wrapper never calls next.ServeHTTP?The request is short-circuited: neither the rest of the chain nor the final handler runs, and the client gets whatever your wrapper wrote (or an empty 200 if it wrote nothing). Go has no separate abort call -- returning early is the abort. That is exactly how a rejection layer is written, so an accidental early return is a real bug class.
- Why is the returned handler usually an http.HandlerFunc rather than a new named type?`http.HandlerFunc` is an adapter: it is a function type whose `ServeHTTP` method calls the function itself. Wrapping a closure in it produces an `http.Handler` in one line, with no struct, no field for `next`, and no separate method declaration. A named struct type is only worth it when the wrapper needs exported configuration or its own methods.
- Should cleanup after the inner handler go on a plain line or in a defer?Use `defer` whenever the cleanup must happen even if the inner handler panics -- releasing a slot, closing something, emitting a final log line. A panic unwinds past plain statements written after `next.ServeHTTP` but still runs deferred functions. Plain lines are fine only for work that is meaningless when the request blew up.
- Where do you put work that should happen once rather than per request?In the outer function body, before the `return http.HandlerFunc(...)`. That body executes a single time, when the chain is assembled at start-up. Anything inside the returned closure runs on every request and on many goroutines at once, so compiling patterns or reading configuration there wastes work and invites shared-state races.
Each wrapper is an envelope around the one inside it. Opening happens outermost-first on the way in; sealing happens innermost-first on the way back out.
saying these in an interview costs you the question
- Thinks next.ServeHTTP schedules the handler asynchronously
- Puts per-request state in a package-level variable
- Sets a response header after next.ServeHTTP returns
- Calls next.ServeHTTP twice on one request path
- Believes net/http calls the inner handler automatically
- Does per-request setup in the outer function body