Writing a Middleware for your HTTP handler – Golang

4 minute read

If you are a backend developer working daily with HTTP requests then you have most likely already encountered situations where you want a common functionality across all the incoming HTTP requests, which can be as simple as checking if the Content-Type header only has the value application/json if you only support json, or maybe you want to spoof your HTTP request to change the method type from POST,GET or PUT to something else based on the X-HTTP-Method-Override header, or of course authenticate before finally passing the request to the destination HTTP handler.

middlewares

You can achieve the following behaviour by writing a middleware, also known as a filter in some other backend frameworks. You can have as many middlewares as you want, each with a separate responsibility, and can chain them together to funnel incoming HTTP requests.

Writing a middleware in Go is pretty simple, you just need to wrap your middleware around the base HTTP handler, which so to speak is a thin wrapper around your HTTP handler.

Lets start with http package’s ListenAndServe method, which listens for incoming connections and serves with the handler to handle the requests, and lets write a handler for root "/" path which checks for the header Content-Type to see if it’s application/json, because our API only accepts JSON, and respond with following json {"msg":"Hello world!"} to any incoming request:

 1    func main() {
 2        mux := http.NewServeMux()
 3        mux.HandleFunc("/", rootHandler)
 4        http.ListenAndServe(":8080", mux)
 5    }
 6
 7    func rootHandler(w http.ResponseWriter, r *http.Request) {
 8        if r.Header.Get("Content-Type") != "application/json" {
 9                http.Error(w, "Only application/json content type supported", http.StatusUnsupportedMediaType)
10                return
11            }
12        w.Write([]byte(`{"msg":"Hello world!"}`))
13    }

Of course in reality you could have a dozen of handlers each with a different endpoint, but lets keep it simple for this tutorial.

Now let’s assume we have a dozen request handlers in our project and we want each handler to check for Content-Type header as we did in our rootHandler, we want our code to be DRY (Don’t Repeat Yourself).

Which brings us to writing a middleware to check the Content-Type header for each incoming request. Lets write a middleware and call it wrap:

1    func wrap(next http.Handler) http.Handler {
2        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3            // middleware logic
4            next.ServeHTTP(w, r)
5        })
6    }

The wrap function takes in a Handler and returns a Handler, this syntax allows it to be used where we would normally use a request handler, and also allow us to chain multiple middlewares together. Now we can do something like wrap(handler) instead of just using handler, this would call our middleware logic before calling next.ServeHTTP(w, r) which can be the next middleware in the chain or a final call to our request handler.

In the following example, we are wrapping our wrap middleware around mux so every incoming request will pass through our middleware first:

 1    func main() {
 2        mux := http.NewServeMux()
 3        mux.HandleFunc("/", rootHandler)
 4        http.ListenAndServe(":8080", wrap(mux))
 5    }
 6
 7    func rootHandler(w http.ResponseWriter, r *http.Request) {
 8        w.Write([]byte(`{"msg":"Hello world!"}`))
 9    }
10
11    func wrap(next http.Handler) http.Handler {
12        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
13            if r.Header.Get("Content-Type") != "application/json" {
14                http.Error(w, "Only application/json content type supported", http.StatusUnsupportedMediaType)
15                return
16            }
17            next.ServeHTTP(w, r)
18        })
19    }

So far we’ve been using a single middleware, lets see multiple middlewares chained together in action. We can define multiple middlewares with the same signature as of wrap, replacing the comment // middleware logic with our code.

1   func wrap(next http.Handler) http.Handler {
2       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3           // middleware logic
4           next.ServeHTTP(w, r)
5       })
6   }

Lets rename wrap to enforceJSON, and write another middleware which will log every incoming request’s method and path, and chain them both together:

 1    func main() {
 2        mux := http.NewServeMux()
 3        mux.HandleFunc("/", rootHandler)
 4        http.ListenAndServe(":8080", logRequest(enforceJSON(mux)))
 5    }
 6
 7    func rootHandler(w ResponseWriter, r *Request) {
 8        w.Write([]byte(`{"msg":"Hello world!"}`))
 9    }
10
11    func logRequest(next http.Handler) http.Handler {
12        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
13            fmt.Println(r.Method, r.URL.Path)   // logs: GET /v1/users/1234
14            next.ServeHTTP(w, r)
15        })
16    }
17
18    func enforceJSON(next http.Handler) http.Handler {
19        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20            if r.Header.Get("Content-Type") != "application/json" {
21                http.Error(w, "Only application/json content type supported", http.StatusUnsupportedMediaType)
22                return
23            }
24            next.ServeHTTP(w, r)
25        })
26    }

See how we are using logRequest(enforceJSON(mux)) instead of wrap(mux)? Now every incoming HTTP request will be logged and then checked for Content-Type before being passed onto the handler.

A cleaner way to chain multiple handlers is writing a single wrap middleware and chain all the middlewares inside wrap:

1    func wrap(handler http.Handler) http.Handler {
2        handler = enforceJSON(handler)
3        handler = logRequest(handler)
4        return handler
5    }

And then instead of:

1    http.ListenAndServe(":8080", logRequest(enforceJSON(mux)))

We can use:

1    http.ListenAndServe(":8080", wrap(mux))

That’s all on middlewares in golang today. Hope it was useful with your understanding on middlewares or how to write them.