So You Wanna Start Developing Web Apps With Go Huh? — Handle, Handler & HandleFunc

Hello guys. I am back with my second post from the series “So You Wanna Start Developing Web Apps With Go Huh?”. As the title suggests, in this post we will be having a look into what HandleFunc, Handler and Handle are. Before jumping into these though, we will take a deep dive into what mux is and how to setup routes using these 3 functions. But first, we are going to see what interfaces actually are and how they work.

To avoid confusions, I have marked all the types (interface/struct) as bold and all the methods/functions as italics.

Interfaces are similar to structs but the most important difference is, structs are used to define fields whereas interfaces are used to define methods or a set of methods. A basic example of both is as follows:

type Shape interface {
area() float64
}
type Person struct {
Name string
Age int
}

What this means is, a value of type Person will have a Name and an Age field. But a value of type Shape will have an area method that can be called using value.area(). This method returns a float64 value. Take a look at this example (Taken from go-book.appspot.com):

type Human struct {
name string
age int
phone string
}

func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

func (h *Human) Sing(lyrics string) {
fmt.Println("La la, la la la, la la la la la...", lyrics)
}


type Men interface {
SayHi()
Sing(string)
}

So, were you able to figure out what’s going on with this code? Let me tell you. The two things that you must have understood by now is the type Human is a struct and the type Men is an interface. Human has 3 fields and Men has 2 methods. We then define a function like this:

func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

The (h *Human) between func and method name defines which type can have this method. In this case, after defining the above function, type *Human will have SayHi() method. Similarly, second function makes type *Human have Sing(lyrics string) method. Now you can call this two methods on type *Human as follows:

var h *Human
h.SayHi()
h.Sing("Some Lyrics...")

Since, type *Human can now use all the methods of type Men, we say that type *Human implements the type Men interface. Or as Todd Macleod (Twitter: @Todd_McLeod) describes it as a joke:

An interface says, “Hey baby, if you have these methods, then you’re my type.”

If you want to read about interfaces in detail, do check out this page https://go-book.appspot.com/interfaces.html

ServeMux or just mux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL. For example, you want to execute a function named Hello whenever a user visits www.example.com and a function named Info whenever a user visits www.example.com/info, you can use either the Handle method or the HandleFunc method from the ServeMux.

A basic example of a mux in Go would be:

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api", apiFunc)
mux.HandleFun("/", indexFunc)
}
func apiHandler(w http.ResponseWriter, r *http.Request) {
fmt.Printf(w, "Hello API")
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Printf(w, "Hello Index")
}

http.NewServeMux() is the default serve mux in go from the net/http package. This piece of code will essentially send all the requests coming to or from “/api” to the apiFunc Function. In this case, it will print “Hello API” on the browser. Similarly, it does the same thing for “/” path. You can also define different functions for different requests (GET, POST, etc.) to the same URL or path.

Handler is an interface that has a method called ServeHttp which takes a value of type ResponseWriter and another of type *Request as Parameters.

type Handler interface {
ServeHttp( ResponseWriter, *Request )
}

ListenAndServe is a function that takes a Handler and returns an error (nil if no error).

func ListenAndServe(addr string, handler Handler) error

We saw that a mux is used for routing. When you look through the documentation, you will notice that http.NewServeMux() returns a pointer to a serve mux (*ServeMux) and this type *ServeMux has a ServeHttp method. Therefore, any value of type *ServeMux will essentially implement the Handler interface (we already saw how this works). Since ListenAndServe wants a handler of type Handler and any value of type *ServeMux implements the Handler interface, we can pass *ServeMux to the ListenAndServe function.

Note :- If you put nil in the ListenAndServe function instead of a value of type Handler, it will automatically utilize the DefaultServeMux.

To put this simply, type Handler is an interface that has ServeHttp method. Type *ServeMux also has the same ServeHttp method. Since both of these have same method, any value of type *ServeMux will essentially implement Handler interface.

Type *ServeMux also has 2 other important methods, Handle and HandleFunc. Handle takes two parameters, first — a pattern or path of type string that you want to manage route for, and second — a handler of type Handler. This handle function then calls the ServeHttp method from Handler interface with the required parameters to do something when a user reaches the path passed to Handle function. For Example:

type timeHandler struct {
format string
}

func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tm := time.Now().Format(th.format)
w.Write([]byte("The time is: " + tm))
}

In the above example, we define a method on type timeHandler having the signature ServeHTTP(w http.ResponseWriter, r *http.Request). This is all we need to do to make custom handlers. Once this is done, we can use the handle function to manage a route as follows:

func main() {
mux := http.NewServeMux()

th := &timeHandler{format: time.RFC1123}
mux.Handle("/time", th)

log.Println("Listening...")
http.ListenAndServe(":3000", mux)
}

We initialize a variable th of type timeHandler and populate the format field. This way of defining a variable and initializing it is known as a composite literal in Golang. Since we already defined a method on timeHandler with signature ServeHTTP(w http.ResponseWriter, r *http.Request), timeHandler implements the Handler interface and we can pass it to the Handle function. This is precisely what is happening in the above example.

Before we jump to HandleFunc function, let’s have a look at below example:

func SomeName (w http.ResponseWriter, r *http.Request) {
Some Code
To Be executed
}

What do you think of the function above? We can see that it takes the same 2 parameters as ServeHttp method. So, does this function also implement Handler interface?

No! This is a function separate from any type of interface. The methods from type Handler are not attached to this and so it doesn’t implement the Handler interface. It is just its own function that happens to take the same parameters as ServeHttp method. To put it simply, individual functions like this and methods from particular interfaces are not the same even if they take the same parameters.

This function is what a HandleFunc method from *ServeMux takes as the 2nd parameter. In simple words, HandleFunc takes a path string as first parameter and a function (which takes ResponseWriter and *Request as parameters) as second parameter. A basic HandleFunc method looks like this:

func main() {
mux := http.NewServeMux()

mux.HandleFunc("/time", timeHandler(w http.ResponseWriter, r *http.Request) {
tm := time.Now().Format()
w.Write([]byte("The time is: " + tm))
})

log.Println("Listening...")
http.ListenAndServe(":3000", mux)
}

To make the code look clean, you can also do the same thing as follows:

func timeHandler (w http.ResponseWriter, r *http.Request) {
tm := time.Now().Format()
w.Write([]byte("The time is: " + tm))
}
func main() {
mux := http.NewServeMux()

mux.HandleFunc("/time", timeHandler)

log.Println("Listening...")
http.ListenAndServe(":3000", mux)
}

Note :- When passing a function as second parameter to the HandleFunc method, you only need to pass the name of the function without the parenthesis otherwise it will throw an error.

I hope that after reading up to here, you are clear about Handler, Handle and HandleFunc. Finally, let’s dive into what the two parameters of type ResponseWriter and Request are for.

Let’s assume that w is of type http.ResponseWriter and r is of type *Request for the upcoming examples.

The first parameter w, is a struct with properties and methods that are related to the response that the server will send to the client. In other words, we can define certain things in our response (headers, cookies, response body, status code etc.) and then send it to the client. On the other hand, Request struct stores values received from the client. This also includes many things like the method of request (GET, POST, etc.), cookies (that are already stored in client’s browser), headers (type of data, etc.), body (the actual data of the request made by client like form data, etc.), etc.

You might have noticed that one is a of type ResponseWriter itself but second is of type Pointer to a Request (*Request). This is because the Request struct is a very large struct so copying it would be resource expensive. That’s why we use a pointer to a Request struct rather than copying it every time the client makes a request. This is a common practice in Golang to make the programs consume less resources when the structs are large.

This is it for the 2nd post. I hope that you were able to understand basics of how interfaces & methods work and the difference between Handle, Handler and HandleFunc. If you have any suggestions for edits or additions to this post, do let me know. If you want to talk about Go, programming in general or just anything, feel free to contact me.

And also, share this post with anyone who might gain something from the post. This was my second post from the series “So You Wanna Start Developing Web Apps With Go Huh?”. In the next post, we will see how to handle different data types (both in request and response) such as multipart/form, application/json or just simple text. I have never worked with XML so I won’t be able to explain that, though.

And lastly, follow me on medium/facebook/twitter to stay updated about this series. If this post helped you out, do leave a like/recommend. Cheers!!