Day 088 — Middleware Chains¶
Month 4 · Week 1 · ⬅ Day 087 · Day 089 ➡ · Journal index
🎯 Learning Objective¶
Build composable HTTP middleware with the standard library: the
func(http.Handler) http.Handler pattern, a Chain combinator, and the
ordering rules that make it predictable.
📚 Topics¶
- Middleware type, wrapping,
Chain, ordering (outermost-first) - RequestID / Logger / Recoverer, capturing status,
contextvalues
📖 Reading / Sources¶
📝 Notes¶
- A middleware is a function that wraps a handler and returns a handler:
type Middleware func(http.Handler) http.Handler. It can run code before and afternext.ServeHTTP, short-circuit, or mutate the request → [[http-handler]]. - Composition order: with a
Chain(A, B, C),Ais the outermost layer — it runs first on the way in and last on the way out. ImplementChainby wrapping inside-out (for i := len-1; i>=0; i--) somws[0]ends up outside. - Recoverer must be outer (near the top) so it can catch panics from any
inner layer/handler.
recover()only catches panics on the same goroutine, so a panic in a goroutine the handler spawned is NOT caught → ties to [[panic-recover]]. - To capture the status code, wrap
ResponseWriterin a small struct that records the code in itsWriteHeader. The stdlib writer won't tell you the status after the fact. - Pass per-request data via
context:r.WithContext(context.WithValue(...)), thennext.ServeHTTP(w, r2). Use an unexported key type so keys can't collide across packages → [[context-keys]]. Don't smuggle optional params this way; use it for request-scoped values (request id, auth principal). - These are plain
net/httpmiddleware — identical shape to chi'smiddlewarepackage, so they're portable between routers → [[router-choice]].
💻 Code Examples¶
type Middleware func(http.Handler) http.Handler
// Chain(A, B)(h) == A(B(h)): request flows A -> B -> h.
func Chain(mws ...Middleware) Middleware {
return func(final http.Handler) http.Handler {
for i := len(mws) - 1; i >= 0; i-- { // wrap inside-out
final = mws[i](final)
}
return final
}
}
func Recoverer(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if v := recover(); v != nil { // same-goroutine only
http.Error(w, "internal server error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
srv := &http.Server{Addr: ":8088", Handler: Chain(RequestID, Logger, Recoverer)(mux)}
Full code:
examples/month-04/middleware/main.go· Run:go run ./examples/month-04/middleware
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
Chain ordering + panic Recoverer -> 500 |
✅ | exercises/month-04/week-1/middleware |
🐛 Mistakes Made¶
- Wrote
Chainlooping forward, which putmws[0]innermost — the logger then saw the post-recover status, not the original. Looping backward fixed the order. - Used a plain
stringcontext key and collided with another package's value. Switched to an unexportedtype ctxKey int.
❓ Open Questions¶
- Should Recoverer re-panic after responding (so an upstream test harness sees it), or swallow it? (Production: log + 500; tests sometimes want the panic.)
🧠 Active Recall (answer without looking)¶
- Q: In
Chain(A, B, C), which middleware runs first on the way in?
A
`A` — the first argument is the outermost layer, so it runs first inbound and last outbound.- Q: Why might a Recoverer fail to catch a panic from a handler?
A
`recover()` only catches panics on its **own goroutine**; a panic inside a goroutine the handler launched crashes the process.🪶 Feynman Reflection¶
Middleware is gift-wrapping for a handler: each layer adds paper around the box,
and a request unwraps from the outside in, then the response wraps back up. A
Chain just stacks the wrappers in the order I list them, outermost first.
🕳️ Knowledge Gaps¶
- Streaming responses through a wrapped
ResponseWriterwhile preservinghttp.Flusher(need to forward optional interfaces).
✅ Summary¶
I can write and compose stdlib middleware, control inbound/outbound order with a
Chain, capture status codes, and pass request-scoped values through context
safely.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 089: JSON request/response helpers — strict decoding and uniform errors.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: feat(examples): composable net/http middleware chain (day 088)