II. It's HTTP Handlers All the Way Down
The previous article covered how HTTP servers work in Go. In it, I mentioned Handlers quite a bit. Let's finally figure those out!
The http.Handler
is just an interface:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
The http.Server
has a Handler
property, which wants a http.Handler
:
# From stdlib http module:
type Server struct {
// snip
Handler Handler // handler to invoke, http.DefaultServeMux if nil
}
Most properties in the Server
have a long comment describing it. This property does not, but the little comment it does have tells us it uses the DefaultServeMux
if it's nil. That's the behavior we saw in the previous article.
In the example in the last article, we created a ServeMux
and a Server
:
mux := http.NewServeMux()
mux.HandleFunc("/", func(writer http.ResponseWriter, r *http.Request) {
writer.WriteHeader(200)
fmt.Fprint(writer, "HELLO")
})
srv := &http.Server{
Handler: mux,
}
We passed a http.ServeMux
to the server's Handler. That ServeMux
object satisifes the http.Handler
interface because it has a ServeHTTP
method.
The interface doesn't care what the ServeHTTP
method does (that's not the job of an interface). The Mux's ServeHTTP
method happens to match an HTTP request to a user-defined http.Handler
(yes, another Handler!) and calls ServeHTTP
on that Handler.
That process looks a bit like the below, where the ServeHTTP
method that's on the ServeMux
struct matches a user-defined Handler and calls ServeHTTP
on it:
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// snip
// Match the request URI to
// a user-registered route
h, _ := mux.Handler(r)
// The user-defined route is
// an instance of http.Handler
h.ServeHTTP(w, r)
}
When I saw a "user-defined handler", I mean this thing:
mux.HandleFunc("/", func(writer http.ResponseWriter, r *http.Request) {
writer.WriteHeader(200)
fmt.Fprint(writer, "HELLO")
})
This is a bit confusing at first. The ServeMux
satisfies the Handler
interface, but it also then matches an incoming request to a registered handler. The registered handler also satisfies the Handler
interface, and so we can call ServeHTTP
on that handler.
That happens a few more times! It's a whole chain of Handlers! Here's roughly what the code path is:
1: An incoming request (eventually) triggers the creation of a
http.serverHandler
(a private, unexported object)
2:
http.serverHandler
has aServeHTTP
method! It's the firstServeHTTP
method called in the "chain"
3:
http.serverHandler
contains a reference to theServer
and uses it to call the server'sHandler
(which, remember, is often an instance ofServeMux
, although it doesn't have to be)
4: Ours is a Mux, and the Mux matches the incoming requestss route to a
http.HandlerFunc
(the function we provided as a handler), and callsServeHTTP
on that handler!
We can look at the code a bit to see that more clearly.
The chain of Handler calls roughly looks like this, which I copied/pasted from stdlib, and then tweaked to make sense:
// A. Deep in http/server.go...
// This is the top-level ServeHTTP call
// serveHandler has a reference to the Server
sh := serverHandler{srv: c.server}
sh.ServeHTTP(w, w.req)
// B. Within serverHandler.ServeHttp()...
// A Server is a prop of the serverHandler.
// It calls the server's Handler (ServeMux).
handler := sh.srv.Handler
if handler == nil {
// Look, the default ServeMux!
handler = DefaultServeMux
}
handler.ServeHTTP(rw, req)
// C. Within handler.ServeHTTP()...
// Our handler function has been converted to a HandlerFunc
// which gives it the ServeHTTP() method that's called here:
userDefinedHandler := mux.Handler(request)
userDefinedHandler.ServeHTTP(w, r)
🐢 It's Handlers all the way down.
HandlerFunc
In the code comments directly above I mentioned that our handler function is "converted" to a http.HandlerFunc
.
Here's the handler function we defined previously:
mux := http.NewServeMux()
// We pass a regular function as a handler function
mux.HandleFunc("/", func(writer http.ResponseWriter, r *http.Request) {
writer.WriteHeader(200)
fmt.Fprint(writer, "HELLO")
})
The function we passed there actually satisifes Handler
even though it's not explicitly named ServeHTTP
and is not typed as a http.Handler
! However, we actually called ServeHTTP()
on it, and it ran our handler code. That's weird!
// Somehow this runs the handler code we wrote
// even tho what we passed was a regular func()
// and didn't define something with a ServeHTTP
// method on it.
userDefinedHandler := mux.Handler(request)
userDefinedHandler.ServeHTTP(w, r)
The trick is the http.HandlerFunc
"conversion". Let's see how that works with an example.
It does some whack nonsense, just bear with me.
package main
import (
"fmt"
"net"
"net/http"
)
// MyHandler is of type "func", with a specific function signature. Weird!
type MyHandler func(http.ResponseWriter, *http.Request)
// ServeHTTP adds a function with the correct
// signature to make it satisfy http.Handler.
// Note that MyHandler `m` is *callable* as a
// function. We can, and do, call `m(w, r)`!
func (m MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
m(w, r)
}
func main() {
mux := http.NewServeMux()
// Our function gets turned into an instance of MyHandler,
// which provides method ServeHTTP, and calls the func we
// passed into mux.Handle() here.
//
// * Note that this method requires us to use mux.Handle,
// not mux.HandleFunc
mux.Handle("/", MyHandler(func(writer http.ResponseWriter, r *http.Request) {
writer.WriteHeader(200)
fmt.Fprint(writer, "HELLO")
}))
srv := &http.Server{
Handler: mux,
}
ln, err := net.Listen("tcp", ":80")
if err != nil {
panic(err)
}
srv.Serve(ln)
}
This is a bit weird unless you're pretty familiar with Golang. The pattern was new to me.
In Golang, you can just define your own types. Above, we defined (named) a type MyHandler
. Its of type func
! That function has a specific signature.
We then give MyHandler
a ServeHTTP
method! This was new to me - I'm used to creating struct
types and adding methods to those, but here we created MyHandler
as type func
... and then we added a method on that!
To repeat myself, this is the part I'm talking about:
// MyHandler is of type "func", with a specific function signature. Weird!
type MyHandler func(http.ResponseWriter, *http.Request)
// ServeHTTP adds a function with the correct
// signature to make it satisfy http.Handler.
// Note that MyHandler `m` is *callable* as a
// function. We can, and do, call `m(w, r)`!
func (m MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
m(w, r)
}
By adding the ServeHTTP
method, the MyHandler
type now satisfies http.Handler
interface.
What's weird is that ServeHTTP
calls m(writer, r)
. Turns out...you can do that. Since MyHandler
is a func
you can just call it like a function.
func (m MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// We can actually call "m" as a function. Neat!
// "m" is from `func(m MyHandler)` where I named
// the instance of this function "m"
m(w, r)
}
After we define all of that, we pass a regular ole' function with our handling code (status 200, string "HELLO"
), but we typecast that handler function as a MyHandler
. As I've said before, MyHandler
satisfies interface http.Handler
, so we're effectively able to take our regular function and pretend it's an http.Handler
.
Fun fact: This is what the stdlib does.
It turns out MyHandler
is an exact copy of the following stdlib code from http/server.go
, which defines type http.HandlerFunc
:
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
Note the comment from the stdlib code:
HandlerFunc type is an adapter to allow the use of ordinary functions as HTTP handlers.
We allowed ourselves to write an "ordinary function" as a handler even though we technically needed to satify interface http.Handler
. Instead of creating a struct (or whatever) and adding a method ServeHTTP()
on it, we can just pass a function to mux.HandleFunc
.
Being asked to pass a regular function is just syntactic sugar, and also confusing. It hides an important implementation detail of Golang's http
module - handlers!
(Maybe the core team was as sick as Handlers we are, and hid them from us.)
What's the Point?
In the first post, I mentioned asking Caddy's community forum a question. That question came from trying to figure out where the hell ServeHTTP
came from. I couldn't find the complete code path.
The whole point of me writing this is basically my excitment in finally figuring it out.
The really nifty surprise was seeing how (ab)used the http.Handler
type is! It's everywhere!
Sidenote - the thing we did above might actually be useful for you to explicitly use. Here's an example of how this pattern might help with error handling in your own http applications.
Not only are Handlers all over the place, they often call each other in a chain. It's almost like a design pattern! Yes, that's a hint!
We'll next see how we can use this knowledge in some cool ways.