26.1 http.Handler and http.HandlerFunc
Alright, let’s get our hands dirty with the real meat and potatoes of the Go HTTP server: http.Handler and http.HandlerFunc. This isn’t some abstract, ivory-tower concept; it’s the fundamental contract, the interface that everything else is built upon. If you understand this, you understand how the entire net/http package holds together.
The core of it all is the http.Handler interface. I love its simplicity. It’s so small, you might trip over it.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
That’s it. The entire interface. If your type has a ServeHTTP method that takes those two arguments, congratulations, it’s a Handler. The server will know what to do with it. This is the genius of Go’s implicit interfaces. Your struct doesn’t need to say “I implement Handler”; it just does if it has the right method. This means you can wrap anything in this behavior.
Now, you’re probably thinking, “Writing a whole struct for every little route sounds like a pain in the neck.” The Go authors thought so too. Enter http.HandlerFunc. This is where things get a little clever, and honestly, a bit beautiful once it clicks.
The HandlerFunc Type Magic Trick
http.HandlerFunc isn’t a function; it’s a type. Specifically, it’s a type defined as a function:
type HandlerFunc func(ResponseWriter, *Request)
Here’s the magic: This type, HandlerFunc, has a ServeHTTP method. Yes, a function type has a method. Welcome to Go. That method, behind the scenes, just calls itself.
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r) // Just call the function we're representing
}
This is the bridge. It lets you take any ordinary function with the signature func(ResponseWriter, *Request) and convert it to the HandlerFunc type. And since HandlerFunc has a ServeHTTP method, it now satisfies the http.Handler interface. Poof! Your function is now a Handler.
This is why you can do this:
func greet(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, world!")
}
// Later, in your main function:
http.Handle("/greet", http.HandlerFunc(greet))
Look closely. greet is just a function. We’re converting it to the HandlerFunc type by wrapping it: http.HandlerFunc(greet). Now it’s a Handler, and we can pass it to http.Handle, which expects a Handler. This conversion is the secret sauce.
The http.HandleFunc Shortcut
Because converting functions to Handlers is so common, the package gives you a shortcut: http.HandleFunc. This function does the conversion for you internally.
http.HandleFunc("/greet", greet) // Does the HandlerFunc conversion automatically
It’s convenient, absolutely. But I want you to understand what’s happening under the hood because that knowledge is power. When you see http.HandleFunc, you should mentally replace it with “this is taking my function, turning it into a Handler, and registering it.”
Why This All Matters: Flexibility
This design—a tiny interface and a function adapter—isn’t just academic. It’s incredibly powerful and flexible. It means you can build complex handlers out of simple parts.
You can have a struct-based Handler that holds configuration, database connections, or other state:
type GreetHandler struct {
Greeting string
DB *sql.DB
}
func (h *GreetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Message: %s", h.Greeting)
// Use h.DB for something useful
}
And you can use simple functions for trivial tasks. They coexist perfectly because they both fulfill the same contract. The server doesn’t care if your Handler is a 50-field struct or a two-line function; if it has ServeHTTP, it’s game.
This also enables the entire concept of middleware, which we’ll tackle next. Middleware is just a Handler that wraps another Handler. The entire ecosystem is built on this one, simple, brilliant idea. Master it, and you can bend the net/http package to your will.