DEV Community

Cover image for [Go] Understanding net/http package - Handle(r) family
UponTheSky
UponTheSky

Posted on • Edited on

[Go] Understanding net/http package - Handle(r) family

*image credit to Renee French, the source is the one of the official Go blog posts

TL; DR

  • "Handler"s are types, whereas "Handle"s are functions that register them.
  • HandlerFunc can be understood as a "wrapper" that equips a plain function with the interface Handler.
  • All you need to understand is the type Handler; everything else follows from it.

Introduction: A family of confusing naming - Handle(r) family

As a newbie who recently having started digging into Go, I find it fascinating in that I can basically live without relying on third party packages. And one of the best things I think you can do with only using the standard packages, is to run a server using net/http.

However, once I began looking into tutorials and other learning materials(like gowebexamples.com) for using this package, the family of “Handle(r)” was a little bit confusing to understand in its naming. I believe I am not the only one:

  • Handler and HandlerFunc
  • Handle and HandleFunc

So in this short article, I would like to explain how I understand their differences and my own tip to distinguish them in a simple way.

Distinguishing the family members from naming

Let’s talk about Handler and Handle first. If you read the documentation of the net/http package, Handler is an interface that implements ServeHTTP function, whereas Handle is either a function or a method belonging to the type *ServeMux. Namely, Handle function/method registers a given Handler to its tied (Default)ServeMux.

You can find almost the identical relationship between HandlerFunc and HandleFunc. So here is my first personal tip to understand the “Handle(r)” family:

1)"Handler"s are types, whereas "Handle"s are functions that register them.

But understanding the difference between Handler and HandlerFunc is not that simple. See this explanation of HandlerFunc in the documentation:

The HandlerFunc type is an adapter to allow the use of ordinary functions as HTTP handlers. If f is a function with the appropriate signature, HandlerFunc(f) is a Handler that calls f.

So HandleFunc is an "adapter". The term "adapter" would be probably from the adapter design pattern, which simply means that an adapter changes the interface without touching the internal logic of the adapted target. Thus, the explanation here can be understood as

2)HandlerFunc can be understood as a "wrapper" that equips a plain function with the interface Handler.

and this second tip becomes more obvious once we read this line from the source code of the package:

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}
Enter fullscreen mode Exit fullscreen mode

So when we convert a function of type func(w http.ResponseWriter, r *http.Request) into a HandlerFunc, we must have in mind that the function will behave exactly the same as ServeHTTP(w, r).

The type Handler is at the heart of the family

Now from the tips 1) and 2), we can infer the following takeaway which I would like to emphasize as a separate tip:

3)All you need to understand is the type Handler; everything else follows from it.

But what does it mean by "understanding Handler"? Although the low-level implementation can be found(for example, the method Serve), I would like to mention that it is an interface with only one method. Since we only have to implement one single method ServeHTTP, we can construct a handler with more stateful layers.

For example:

type WrappedHandler struct {
  // some internal variables representing states
}

func (wh *WrappedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  // …
}

type WrapperHandler struct {
  // additional variables 

  wrapped *WrappedHandler
}

func (wh *WrapperHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  // do some layered logics

  // run the wrapped handler’s logic
  wh.wrapped.ServeHTTP(w, r)
}
Enter fullscreen mode Exit fullscreen mode

Moreover, using HandlerFunc, we can make a middleware in a simple way following this pattern(I learned it from the book Learning Go, 2nd ed. by Jon Bodner).

func middleware(handler http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 
    // do some middleware stuffs

    // call the original handler’s ServeHTTP
    handler.ServeHTTP(w, r)
  })
}

Enter fullscreen mode Exit fullscreen mode

Conclusion

The three tips mentioned here are totally personal. However, I find it fairly useful whenever I get confused with reading the names of the members of “Handle(r)” family, clearly distinguishing what each type/function does. Moreover, from understanding the roles of them, I can be more familiar with the structure of how net/http package works under the hood.

Top comments (0)