Skip to content

Day 086 — http.Handler & HandlerFunc

Month 4 · Week 1 · ⬅ Day 085 · Day 087 ➡ · Journal index

🎯 Learning Objective

Understand the one interface the whole net/http ecosystem is built on, http.Handler, and the HandlerFunc adapter that turns plain functions into it.

📚 Topics

  • http.Handler, ServeHTTP, http.HandlerFunc, the function adapter pattern
  • ResponseWriter mechanics, handler values vs functions

📖 Reading / Sources

📝 Notes

  • The entire server contract is one method: type Handler interface { ServeHTTP(http.ResponseWriter, *http.Request) }. Everything — mux, middleware, file server — is just a Handler → [[http-handler]].
  • HandlerFunc is the classic adapter: a named function type whose own ServeHTTP calls the function. type HandlerFunc func(w, r) + func (f HandlerFunc) ServeHTTP(w, r) { f(w, r) } lets a plain func satisfy the interface → [[function-adapter]].
  • mux.HandleFunc(pattern, fn) is sugar for mux.Handle(pattern, http.HandlerFunc(fn)).
  • ResponseWriter is an interface, not a buffer: writing to it streams bytes. Header() must be set before WriteHeader; a Write without WriteHeader sends 200 first. You cannot change the status after the body starts.
  • A handler runs on its own goroutine per request; don't share mutable state without synchronisation → ties back to [[mutex]] / [[channels]].
  • The request *http.Request carries a Context() that is cancelled when the client disconnects — pass it to downstream calls (context is first-param) → [[context]].
  • Returning a Handler from a function (a constructor) is how middleware and dependency injection work; you wrap one Handler to produce another.

💻 Code Examples

// HandlerFunc adapts a plain function to the http.Handler interface.
type greeter struct{ name string } // a struct handler, when you need state

func (g greeter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello from %s\n", g.name)
}

mux := http.NewServeMux()
mux.Handle("GET /svc", greeter{name: "svc-a"})       // value implements Handler
mux.HandleFunc("GET /ping", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("pong")) // implicit WriteHeader(200)
})

See it wired up in examples/month-04/servemux/main.go · Run: go run ./examples/month-04/servemux

🏋️ Exercises / Practice

Exercise Status Link
Wrap handlers (Chain + Recoverer return Handlers) exercises/month-04/week-1/middleware

🐛 Mistakes Made

  • Passed a method value with the wrong signature to HandleFunc → it must be exactly func(http.ResponseWriter, *http.Request).
  • Called w.WriteHeader(201) after w.Write(...); the 201 was ignored (status already 200) and the server logged "superfluous WriteHeader call".

❓ Open Questions

  • When is a struct handler better than a closure capturing dependencies? (Struct handlers shine when you need methods + shared fields; closures are lighter for one-offs.)

🧠 Active Recall (answer without looking)

  1. Q: What single method must a type implement to be an http.Handler?
A `ServeHTTP(w http.ResponseWriter, r *http.Request)`.
  1. Q: What does http.HandlerFunc(f) give you?
A It converts a plain `func(w, r)` into a value whose `ServeHTTP` just calls `f`, so the function satisfies the `Handler` interface.

🪶 Feynman Reflection

http.Handler is the universal plug: anything that can answer a request fits the same socket. HandlerFunc is an adapter that lets a bare function pretend to be a full object, which is why Go web code is mostly small functions plus wrappers.

🕳️ Knowledge Gaps

  • The optional interfaces a ResponseWriter may also implement (http.Flusher, http.Hijacker) and when they matter.

✅ Summary

I see that the whole net/http stack is one interface; functions become handlers via HandlerFunc, and constructors that return handlers unlock middleware.

⏭️ Next Steps / Prep for Tomorrow

  • Day 087: routing with chi — sub-routers, URL params, and route groups.

Time spent Difficulty Confidence
90 min 🟦🟦⬜⬜⬜ 🟦🟦🟦⬜⬜

Suggested commit: docs(journal): http.Handler and HandlerFunc (day 086)