This page looks best with JavaScript enabled

Difference Between Handler, Handle and HandlerFunc

 ·   ·  ☕ 6 min read

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.

What is an Interface?

In golang, we declare an interface like this:

1
2
3
4
type geometry interface {
    area() float64
    perim() float64
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type rect struct {
    width, height float64
}

func (r rect) area() float64 {
    return r.width * r.height
}
func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type circle struct {
    radius float64
}

func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
}

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:


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.

URL structure

URL structure

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 to ServeHTTP(ResponseWriter, *Request). This is what official documentation says.

https://github.com/golang/go/blob/master/src/net/http/server.go#L62-L88

1
2
3
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

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:

1
2
3
func ServeHTTP(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, "I am writing to the response")
}

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:

1
2
3
4
5
6
type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry // slice of entries sorted from longest to shortest.
	hosts bool       // whether any patterns contain hostnames
}

ServerMux is the canonical abstraction of all routes and handlers.

ServerMux has 4 public methods, namely:

1
2
3
4
func (mux *ServeMux) Handle(pattern string, handler http.Handler)
func (mux *ServeMux) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request)

I’ll go through them one by one.

  1. mux.Handle(pattern string, handler Handler) - This takes a URL pattern and a type which implements a Handler. What is a Handler once again? An interface with method signature of ServeHTTP(w http.ResponseWriter, r *http.Request).
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
type hotdog int

func (m hotdog) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "dog doggy doggggy")
}

func main() {
    var d hotdog

    mux := http.NewServeMux()
    mux.Handle("/dog/", d)

    http.ListenAndServe(":8080", mux)
}

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.

  1. 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.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func madDog(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "I am a mad dog.")
}

func main() {
    var d hotdog

    mux := http.NewServeMux()
    mux.Handle("/dog/", d)
    mux.Handle("/maddog/", madDog)

    http.ListenAndServe(":8080", mux)
}

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.

Handle wants a type with ServeHTTP implemented.

Handle wants a type with ServeHTTP implemented.

To overcome this, we can wrap our function with http.HandlerFunc which makes the function mux.Handle compatible.

  1. 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:

FAQ

  1. What is http.Handle vs mux.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().

  1. 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.

Share on

santosh
WRITTEN BY
santosh
Pipeline & Backend Developer