Introduction
Are you lost between Handle, HandleFunc, and HandlerFunc? If the answer is yes then this post is for you. To understand these pretty well we need to be familiar with interafces. Although interface
is just a keyword, it’s confusing at first when you are switching from Python like language; which does not have a similar keyword.
In meantime, please consider connecting with me on LinkedIn
What is an Interface?
In golang, we declare an interface like this:
|
|
In above code, the lines inside the curly brackets are the function signatures of two method namely area()
and perim()
. Whatever type implements those signature becomes a geometry
.
Let’s take an example of such an interface.
|
|
On line 1-3 I have defined a new type which represents a rectangle. On line 5-10 I have implemented the geometry
interface by defining the logic to calculate the area and perimeter of rectangle.
The beauty of interfaces it that there can be any type which can implement this interface. It also complies with Open-close principle. Below is an example of another shape implementing geometry
interface.
|
|
As you can see on line 5-10, the signature of the method is same, just the implementation differs. This way we can have as many shape as we want. There are many such examples in real-world for interfaces.
Take an example of a car; a car manufacturer should first define an interface for an engine. This could be a spec which defines what part of the engine connects to what other part of the car. The interface could then be implemented by another engine and could be used in the same car because this new engine has the same interface like the first one.
If you are looking for an example in software world. You should always develop your application by first declaring an interface. Imagine an interface that interacts with database. One should be using an interface to connect to database which would have method signature like insert, delete etc. Now we can have any database engine no matter mongo or postgres as soon as we implement the insert and delete.
Related reading:
- https://gobyexample.com/interfaces
- https://medium.com/better-programming/a-real-world-example-of-go-interfaces-98e89b2ddb67
A Client and a Server
Let’s first establish the premise of the story. There is a server and there is a client. Although these both could be on same machine, but in real life there are different machines involved, and we will assume the same.
The client is someone requesting resources from the server. The request goes through all the network layers and reaches the HTTP server, which itself is an application layer protocol. The server’s job is to reply with a response. Somewhere between request and response happens goes the application logic.
When request reaches to the server, it goes through something called a multiplexer. The job of the multiplexer is to match a route to specific handlers.
What is a handler? Handler
is a function which holds chunks of code which is suppose to run for every request made at that route. A handler function has to fulfill a certain signature, which you’ll see in next section.
What is a route? Route is a path of the URL.
What is http.Handler?
http.Handler
- this is an interface. Anything that implements this interface is a handler. Implementing this interface means any function which has signature matching toServeHTTP(ResponseWriter, *Request)
. This is what official documentation says.
https://github.com/golang/go/blob/master/src/net/http/server.go#L62-L88
|
|
The pointer to Request above is an struct which holds data from the client. ResponseWriter is an interface which allows us to respond to the request elegantly.
Simple example:
|
|
http.ResponseWriter
has a method signature of Write([]byte) (int, error)
which means any type that implements io.Writer
can also write to the w
; io.WriteString
being one of them.
Easy to understand, right? Now let’s add server multiplexer or router to the scene.
The Server Multiplexer
http.NewServeMux
returns a ServeMux, which looks like this:
|
|
ServerMux
is the canonical abstraction of all routes and handlers.
ServerMux has 4 public methods, namely:
|
|
I’ll go through them one by one.
mux.Handle(pattern string, handler Handler)
- This takes a URL pattern and a type which implements aHandler
. What is a Handler once again? An interface with method signature ofServeHTTP(w http.ResponseWriter, r *http.Request)
.
|
|
Above we can use d
variable, which is a hotdog
type which implements the Handler interface. The underlaying data type could be anything. In this case, it’s it. But it could have been a struct without any side-effect.
http.HandlerFunc
- HandlerFunc is a kind of a helper function that converts a standalone function (more on this next) to what mux.Handle takes. Let’s add into example above.
|
|
As you can see in the following screenshot, I can’t simply use the madDog
function as argument to http.ServeMux.Handle
. This is because mux.Handle is looking for a type which implements a ServerHTTP method.
To overcome this, we can wrap our function with http.HandlerFunc
which makes the function mux.Handle compatible.
http.HandleFunc
- http.HandleFunc takes a standalone function instead of taking a type which implements Handler inteface.
http.ListenAndServe takes a server address and any object which implements http.Handler to start a server. Normally we put a ServeMux, but it will also take any custom type which implements ServeHTTP(w http.ResponseWriter, r *http.Response)
.
Related reading:
- https://medium.com/@perennial.sky/understand-handle-handler-and-handlefunc-in-go-e2c3c9ecef03
- https://rickyanto.com/understanding-go-standard-http-libraries-servemux-handler-handle-and-handlefunc/
FAQ
- What is
http.Handle
vsmux.Handle
?
They both are same. When you use http.Handle, program will automatically create a default server multiplexer. But in most cases developers create a new mux. mux := http.NewServeMux()
.
- In above example you once passed Handler and then a Mux to the
ListenAndServe
. How is that?
ServeMux implements ServeHTTP(w http.ResponseWriter, r *http.Request)
, so it’s also a handler.
Conclusion
Today we have learned about quite a few about some of the most used functions and struct in Go standard library. If you have liked the post, please share it with your connection and subscribe below for new updates.