Implement Your Own Request Middleware for Go HTTP Server

Implement Your Own Request Middleware for Go HTTP Server

posted Originally published at blog.didiktrisusanto.dev 3 min read

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 returned 401 HTTP status

  • Second request with additional x-signature header passed middleware logic and returned 200 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!

If you read this far, tweet to the author to show them you care. Tweet a Thanks

More Posts

Gopher It! Your Go-To Guide for Glorious Go Backends

Mohit Nagaraj 1 - Jul 20

Laravel Middleware: How To Craft Your Own HTTP Gatekeepers.

Darlington Okorie - May 19

Review your own pull request (PR)

koshunyin - Feb 23

Controlling Memory and CPU Utilization for Go Apps in Kubernetes: GOMEMLIMIT and GOMAXPROCS

stjam - Jul 6

Power Up Your App with Server-Sent Push Notifications

Sunny - Jun 8
chevron_left