[Go] function decorator in http.HandleFunc
Functions in golang is one of first class citizens. It means function is very powerful and flexible. We could assign functions to variable, and function itself can also receive Func argument and return Func. With these feature, it’s easy to extend behavior of existing functions. Yes, it’s decorator in func level, much light and small. Unlike class decorator we have to write many code on class level, just write some code in function level and all done. It’s very simple and light, like golang itself.
Examples
Let’s see some examples. If you have written some go backend application, must know http.HandlerFunc .
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
We have to write func with this signature to match HandlerFunc
func (w http.ResponseWriter, r *http.Request)
So we make a function to return “Hello World”.
func HelloHanlder(w http.ResponseWriter, r *http.Request){
body:= "Hello World"
w.Write([]byte{body})
}
How could we use function feature in HandleFunc?
- Middleware:
We could write a wrapper function to add additional behavior, like log , authentication, etc..
func LogMiddleware(h http.HandlerFunc) http.HandlerFunc{
return func(w http.ResponseWriter, r *http.Request){
log.Fatal(.....)
h.ServeHttp(w,r)
}
}
how to use it
// original
http.Handle("/", HelloHanlder)
// with log wrapper
http.Handle("/", LogMiddleware(HelloHanlder))
We could see the middle use function argument and return to extend function. And it can be extended many time without change HelloHanlder.
http.Handle("/", LogN(.....Log2(Log1(HelloHanlder))))
Implement open-close principle in function level!
2. Pass additional arguments to HandleFunc:
If we would like to reuse HelloHandler to say “Hello John” or “Hello Mary”, will find one issue. Only functions match http.Handler signature can be passed to http.Handle. We can’t add new arguments to Handler. So some solutions can be used like struct or global variable:
type User stringfunc (u User) HelloHanlder(w http.ResponseWriter, r *http.Request){
body:= "Hello "+ u
w.Write([]byte{body})
}...........
var u User = "Tom"
http.Handle("/", u.HelloHanlder)
but it’s too complex. Let’s make it more simple
func HelloHanlder(w http.ResponseWriter, r *http.Request, name string){
body:= "Hello "+ name
w.Write([]byte{body})
}
http.Handle("/", func(w http.ResponseWriter, r *http.Request){
HelloHanlder(w, r, "Tom")
})
ok, it looks like more simple. Can be better? Yes, with first class function feature!
func HelloHanlder(name string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request){
body:= "Hello "+ name
w.Write([]byte{body})
}
}
http.Handle("/", HelloHanlder("Tom"))
New refactoring returns http.HandlerFunc for http.Handle, and add only one additional argument in the same function. Simple and clear.
Conclusion
First-class function is a great gift in golang. Flexible , powerful, and can make code clear and short. Every gopher should try to put it in toolbox.