The first code smell we encounter when writing a web application in Go is code duplication. Before processing the request, we will often need to log the request, convert app errors into HTTP 500 errors, authenticate users, etc. And we need to do most of these things for each handler.
We create a very simple app from scratch with the net/http
package of the standard library:
import (
"net/http"
"fmt"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome!")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
We have a function with 2 args: a response writer and a request. In addition to having a function, we can implement the http.Handler
interface to any struct.
type handler struct {}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome!")
}
func main() {
http.Handle("/", handler)
http.ListenAndServe(":8080", nil)
}
Any struct with the method ServeHTTP(http.ResponseWriter, *http.Request)
will be implementing http.Handler
and will be usable with the Go muxer (http.Handle(pattern, handler)
function).
We now want to add a simple log of the time spent to process each request:
func indexHandler(w http.ResponseWriter, r *http.Request) {
t1 := time.Now()
fmt.Fprintf(w, "Welcome!")
t2 := time.Now()
log.Printf("[%s] %q %v\\n", r.Method, r.URL.String(), t2.Sub(t1))
}
func main() {
http.HandleFunc("/", indexHandler)
http.ListenAndServe(":8080", nil)
}
Easy enough. Here's what it will output:
[GET] / 1.43ms
[GET] /about 1.98ms
Now we add a second handler because, let's face it, there are not many apps with only one route.
func aboutHandler(w http.ResponseWriter, r *http.Request) {
t1 := time.Now()
fmt.Fprintf(w, "You are on the about page.")
t2 := time.Now()
log.Printf("[%s] %q %v\\n", r.Method, r.URL.String(), t2.Sub(t1))
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
t1 := time.Now()
fmt.Fprintf(w, "Welcome!")
t2 := time.Now()
log.Printf("[%s] %q %v\\n", r.Method, r.URL.String(), t2.Sub(t1))
}
func main() {
http.HandleFunc("/about", aboutHandler)
http.HandleFunc("/", indexHandler)
http.ListenAndServe(":8080", nil)
}
Code duplication detected! We could create a function with a closure. But if we have multiple functions like that, it will become as bad as callback spaghetti in Javascript. We don't want that.
We want something like the middleware systems of Rack, Ring, Connect.js and other similar solutions. What we would like is to chain multiple handlers. We already have this kind of handlers in the standard library: http.StripPrefix(prefix, handler)
and http.TimeoutHandler(handler, duration, message)
. They both take a handler as one of their arguments and they both return a handler. So we can write a handler and pass another handler to it.
loggingHandler(recoverHandler(indexHandler))
So a middleware would be something like func (http.Handler) http.Handler
This way we pass a handler and returns a handler. At the end we have one handler and can be called with http.Handle(pattern, handler)
func main() {
http.Handle("/", loggingHandler(recoverHandler(indexHandler)))
http.ListenAndServe(":8080", nil)
}