http.Handle
vs http.Handler
vs Http.HandlerFunc
net/http
gives us a function http.Handle
, for attaching handlers to paths.
A handler is anything that satisfies the http.Handler
interface, which is defined this way (I'm refusing to Google sh!t up, so I'm probably going to get the syntax wrong, and I crave your forgiveness for that):
interface Handler {
ServeHttp(w http.ResponseWriter, r *http.Request)
}
So, the type signature of the function http.Handle
looks this way:
func http.Handle(path string, handler http.Handler)
Great.
Now, if we want to write handlers with this knowledge, the most immediately obvious way (at least to me) would be to create a struct type that satisfies the http.Handler
interface.
// create the struct
type HandlerByKiwi struct {}
// make it satisfy the http.Handler interface
func (hbk HandlerByKiwi) ServeHttp(w http.ResponseWriter, r *http.Request) {
doStuffHere...
}
That done, we can attach this handler to a route:
http.Handle("/tiwa/savage/", HandlerByKiwi{})
And that should work, I think.
So, for every path we need to handle, we need to create a struct that satisfies the http.Handler
interface, and does the correct thing while doing so.
I can imagine this becoming tedious. Plus, we'd end up with N
struct definitions that we'd only use once per definition!
Surely, there must be a better way!
Can we take inspiration from handlers in the wild, and get away with functions?
Like, what do we need to do so that attaching handlers to routes looks this simple?
http.Handle("/tiwa/savage/", functionByKiwi)
Where functionByKiwi
is defined thusly:
func functionByKiwi(w http.ResponseWriter, r *http.Request) {
doStuffHere...
}
Yeah, what do we need to do?
Let's put on our thinking caps.
🧢 🧢
And maybe set off the thinking clock.
⏰ Tick.
⏰ Tock.
⏰ Tick.
⏰ Tock.
⏰ Ti...
Well, it turns out that all we need to do is make the function satisfy the http.Handler
interface!
And how do we do that?
The key is in the fact that we can make functions satisfy interfaces by wrapping them in custom types that have been written to satisfy the interfaces.
I'm sure I could word that better. But that's the key. And as soon as the realization that functions can be made to satisfy interfaces hit you, you start feeling like Barry Allen!
Okay. Let's do this.
We start by writing a custom type that will wrap our function.
type FunctionWrapperByKiwi func(w http.ResponseWriter, r *http.Request)
This says that the type FunctionWrapperByKiwi
is going to wrap any function that has that signature there. So, once you take such a function, and pass it to the wrapper like this FunctionWrapperByKiwi(functionByKiwi)
, you can think of the result as a value of the type FunctionWrapperByKiwi
.
This is the same way we take a float64
value, like say 3.0
, and wrap it with the int
wrapper, like int(3.0)
, and the result is now a value of the type int
.
var f float64
var i int
x = 5.7
y = int(x)
Yeah. Casting!
Now that we know we can cast a function into some custom type, we need to make that custom type satisfy the http.Handler
interface:
// create the custom wrapper type
type FunctionWrapperByKiwi func(w http.ResponseWriter, r *http.Request)
// make it satisfy the http.Handler interface
func (fwbk FunctionWrapperByKiwi) ServeHttp(w http.ResponseWriter, r *http.Request) {
fwbk(w, r)
}
And ta-dah!
Now, we can cast our function into a handler, and use attach this resulting value to a path.
http.Handle("/tiwa/savage/", FunctionWrapperByKiwi(functionByKiwi))
Great.
So, our ancestors have gone through this before us, and have blessed us with a function wrapper for turning functions with the same signature as functionByKiwi
into a handler.
http/net
exports a wrapper type HandlerFunc
, which we can use to cast such functions into handlers, eliminating the need for us to write a wrapper type ourselves.
http.Handle("/tiwa/savage/", http.HandlerFunc(functionByKiwi))
Long live the (name of the) ancestors.
RECAP
- To attach a handler to a path, we use the
http.Handle
function - The
http.Handle
function takes a string and a handler and does the correct thing with them - A handler is any value that satisfies the
http.Handler
interface - Functions can be casted into types that satisfy the
http.Handler
interface by wrapping them inhttp.HandlerFunc
YOUR TURN
Think on these things I've written down. Read more articles on this subject. Go to sleep. When you wake up, try to recall the things you've learnt in the way I've done here -- trust me, that helps concepts stick!