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 a ServeHTTP method! It's the first ServeHTTP method called in the "chain"

3: http.serverHandler contains a reference to the Server and uses it to call the server's Handler (which, remember, is often an instance of ServeMux, 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 calls ServeHTTP 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.