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

Published Dec 27, 2017Last updated Apr 20, 2018
So You Wanna Start Developing Web Apps With Go, Huh? — Handle, Handler & HandleFunc

Hello everyone!

In this post, I will try to explain the differences between HandleFunc, Handler, and Handle in Go.

Before jumping into these though, we will take a deep dive into what mux is, what interfaces are, and how to set up routes using these three functions.

But first, we are going to see what interfaces actually are and how they work.

To avoid confusion, I have marked all of the types (interface/struct) in bold and all of the methods/functions in 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 three fields and
Men has two 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 these 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.
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.HandleFunc("/", 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 two 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 the example below:

  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 two
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 second 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 cleaner, 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 parentheses,
otherwise it will throw an error. This is because we are not calling the function, but only passing it as a reference to be called later. This process is commonly known as callback functions.

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

I hope that you were able to understand basics of how interfaces and 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.

Discover and read more posts from Anshul Sanghi
get started