161 lines
6.4 KiB
Markdown
161 lines
6.4 KiB
Markdown
+++
|
||
draft = false
|
||
date = 2021-08-01T22:05:04+05:00
|
||
title = "Writing a Middleware for your HTTP handler – Golang"
|
||
description = ""
|
||
slug = ""
|
||
authors = ["Basit Ali"]
|
||
tags = ["golang", "middleware", "http", "handler"]
|
||
categories = []
|
||
externalLink = ""
|
||
series = []
|
||
+++
|
||
|
||
|
||
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:
|
||
|
||
|
||
```go
|
||
func main() {
|
||
mux := http.NewServeMux()
|
||
mux.HandleFunc("/", rootHandler)
|
||
http.ListenAndServe(":8080", mux)
|
||
}
|
||
|
||
func rootHandler(w http.ResponseWriter, r *http.Request) {
|
||
if r.Header.Get("Content-Type") != "application/json" {
|
||
http.Error(w, "Only application/json content type supported", http.StatusUnsupportedMediaType)
|
||
return
|
||
}
|
||
w.Write([]byte(`{"msg":"Hello world!"}`))
|
||
}
|
||
```
|
||
|
||
|
||
|
||
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`:
|
||
|
||
```go
|
||
func wrap(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
// middleware logic
|
||
next.ServeHTTP(w, r)
|
||
})
|
||
}
|
||
```
|
||
|
||
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:
|
||
|
||
|
||
```go
|
||
func main() {
|
||
mux := http.NewServeMux()
|
||
mux.HandleFunc("/", rootHandler)
|
||
http.ListenAndServe(":8080", wrap(mux))
|
||
}
|
||
|
||
func rootHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Write([]byte(`{"msg":"Hello world!"}`))
|
||
}
|
||
|
||
func wrap(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
if r.Header.Get("Content-Type") != "application/json" {
|
||
http.Error(w, "Only application/json content type supported", http.StatusUnsupportedMediaType)
|
||
return
|
||
}
|
||
next.ServeHTTP(w, r)
|
||
})
|
||
}
|
||
```
|
||
|
||
|
||
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.
|
||
|
||
>```go
|
||
> func wrap(next http.Handler) http.Handler {
|
||
> return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
> // middleware logic
|
||
> next.ServeHTTP(w, r)
|
||
> })
|
||
> }
|
||
>```
|
||
|
||
Lets rename `wrap` to `enforceJSON`, and write another `middleware` which will log every incoming request's method and path, and chain them both together:
|
||
|
||
```go
|
||
func main() {
|
||
mux := http.NewServeMux()
|
||
mux.HandleFunc("/", rootHandler)
|
||
http.ListenAndServe(":8080", logRequest(enforceJSON(mux)))
|
||
}
|
||
|
||
func rootHandler(w ResponseWriter, r *Request) {
|
||
w.Write([]byte(`{"msg":"Hello world!"}`))
|
||
}
|
||
|
||
func logRequest(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
fmt.Println(r.Method, r.URL.Path) // logs: GET /v1/users/1234
|
||
next.ServeHTTP(w, r)
|
||
})
|
||
}
|
||
|
||
func enforceJSON(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
if r.Header.Get("Content-Type") != "application/json" {
|
||
http.Error(w, "Only application/json content type supported", http.StatusUnsupportedMediaType)
|
||
return
|
||
}
|
||
next.ServeHTTP(w, r)
|
||
})
|
||
}
|
||
```
|
||
|
||
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`:
|
||
|
||
```go
|
||
func wrap(handler http.Handler) http.Handler {
|
||
handler = enforceJSON(handler)
|
||
handler = logRequest(handler)
|
||
return handler
|
||
}
|
||
```
|
||
|
||
And then instead of:
|
||
```go
|
||
http.ListenAndServe(":8080", logRequest(enforceJSON(mux)))
|
||
```
|
||
|
||
We can use:
|
||
```go
|
||
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. |