Skip to content

Instantly share code, notes, and snippets.

@mfonism
Created October 12, 2023 12:28
Show Gist options
  • Save mfonism/7e15be07897d53d5867d5ae6496b87fa to your computer and use it in GitHub Desktop.
Save mfonism/7e15be07897d53d5867d5ae6496b87fa to your computer and use it in GitHub Desktop.
WECOLLECTIONS by Kiwi

On those confusingly named things in net/http

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 in http.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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment