Writing a Middleware for your HTTP handler – Golang
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.