- Home
- Tags
- About

Previous post Next post Back to top Share post

# Writing a Middleware for your HTTP handler – Golang

2021-08-01
4 minute read
golang , middleware , http , handler

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.

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.