go
54 lines · 8 steps
Composing HTTP middleware in Go
A middleware type and a Chain helper let you wrap an http.Handler in reusable layers for logging and auth.
Explained by
highlit
1package server
2
3import (
4 "context"
5 "log"
6 "net/http"
7 "time"
8)
9
10type Middleware func(http.Handler) http.Handler
11
12func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
13 for i := len(middlewares) - 1; i >= 0; i-- {
14 h = middlewares[i](h)
15 }
16 return h
17}
18
19func Logging(logger *log.Logger) Middleware {
20 return func(next http.Handler) http.Handler {
21 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22 start := time.Now()
23 rw := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
24 next.ServeHTTP(rw, r)
25 logger.Printf("%s %s %d %s", r.Method, r.URL.Path, rw.status, time.Since(start))
26 })
27 }
28}
29
30func RequireAuth(next http.Handler) http.Handler {
31 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
32 token := r.Header.Get("Authorization")
33 if token == "" {
34 http.Error(w, "unauthorized", http.StatusUnauthorized)
35 return
36 }
37 ctx := context.WithValue(r.Context(), userKey, token)
38 next.ServeHTTP(w, r.WithContext(ctx))
39 })
40}
41
42type statusRecorder struct {
43 http.ResponseWriter
44 status int
45}
46
47func (r *statusRecorder) WriteHeader(code int) {
48 r.status = code
49 r.ResponseWriter.WriteHeader(code)
50}
51
52type contextKey string
53
54const userKey contextKey = "user"
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Middleware is just a function that takes a handler and returns a wrapped handler, so layers compose freely.
- 2Wrapping the ResponseWriter is the standard trick for observing status codes the handler sets.
- 3context.WithValue passes per-request data down the chain without changing handler signatures.
Related explainers
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/composing-http-middleware-in-go-explained-go-7ad1/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.