I believed this topic is a lot in the internet but I want to write it anyway, because documenting your own learning journey and share it to the world is good right?
Request middleware performs some specific function on the HTTP request or response at a specific stage in the HTTP pipeline before the business logic / app handler.
Currently middleware
is a common term but in different programming language or framework sometimes it called filter
. For illustration could be like this
As you can see, middleware could be layered for more than one before hitting app handler or business logic then later it will return the response. HTTP Request comes in to first layer or middleware, check the necessary and if all conditions are passed then it allowed to continue the journey. If failed, it could also returned the failed response anyway.
Basic Middleware
Let say we have simple HTTP API server
func rootHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := defaultResponse{
Status: "OK",
Description: "Success",
}
j, _ := json.Marshal(resp)
w.Header().Add("Content-Type", "application/json")
w.Write(j)
})
}
func main() {
mux := http.NewServeMux()
mux.Handle("GET /", rootHandler())
s := &http.Server{
Addr: ":8080",
Handler: mux,
}
// ...
}
if we run the program go run main.go
and visit http://localhost:8080
it will return 200 success with JSON response:
{
"status": "OK",
"description": "Success"
}
Now lets create our first middleware. Our middleware is to write a simple log that indicating the request is passing through. The middleware should be a function like this
func logMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Print("Executing logMiddleware")
next.ServeHTTP(w, r)
})
}
Then we will modify the mux
handler to use the middleware
func main() {
mux := http.NewServeMux()
mux.Handle("/", logMiddleware(rootHandler()))
// ...
}
The result is when we’re visiting the API, it also wrote the log that already specified in the middleware logic.
Adding More Middleware
Interesting. Now try to add more middleware. This time we will check if the API request header contains x-signature
, if not exists then we will return error.
Lets create the middleware function
func requestSignatureMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Print("Executing requestSignatureMiddleware")
h := r.Header.Get("x-signature")
if h == "" {
resp := errorResponse{
Status: http.StatusUnauthorized,
Error: "INVALID_MISSING_SIGNATURE",
Message: "missing request signature",
}
j, _ := json.Marshal(resp)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write(j)
return
}
next.ServeHTTP(w, r)
})
}
We want to call logMiddleware
first then continue to requestSignatureMiddleware
. Our mux
handler would be look like this now
func main() {
mux := http.NewServeMux()
mux.Handle("/", logMiddleware(requestSignatureMiddleware(rootHandler())))
// ...
}
Here’s the result of the combined middlewares
First request without
x-signature
header will returned401
HTTP statusSecond request with additional
x-signature
header passed middleware logic and returned200
HTTP status.
Cleaning Up
You may notice that our mux
handler with multiple middlewares is little bit messy because it is calling function into function. What if we have many middlewares?
logMiddleware(requestSignatureMiddleware(authMiddleware(rateLimitMiddleware(rootHandler)))))
Hard to see.
Here’s what we can do, create a chain
function to loop all middleware functions. You can named the function whatever you want.
// Middleware type for cleaner middleware chaining
type Middleware func(http.Handler) http.Handler
// Chain creates a single middleware from multiple middlewares
func Chain(middlewares ...Middleware) Middleware {
return func(next http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
next = middlewares[i](next)
}
return next
}
}
Initiate the Chain
with our middlewares and finally call it in our mux
handler.
func main() {
mux := http.NewServeMux()
// Create a chain of middlewares
middlewareChain := Chain(
func(next http.Handler) http.Handler { return logMiddleware(next) },
func(next http.Handler) http.Handler { return requestSignatureMiddleware(next) },
// Add more middlewares here as needed
)
mux.Handle("GET /", middlewareChain(rootHandler()))
// ...
}
Now it’s more manageable and it will still got same result as before.
So here’s the full code of this experiment https://gist.github.com/didikz/98de868f20887285f5aabc80f4dddcfa
Happy coding!
Top comments (0)