blog bg

August 13, 2024

Writing Middleware in Go

Share what you learn in this blog to prepare for your interview, create your forever-free profile now, and explore how to monetize your valuable knowledge.

Middleware is a crucial part of building robust and scalable web applications. It provides a mechanism to modify incoming requests and outgoing responses, allowing you to implement features such as logging, authentication, error handling, and more. In this blog post, we'll explore how to write middleware in Go using the popular net/http package.

 

What is Middleware?

Middleware is essentially a function that wraps an HTTP handler. It can process requests before they reach the final handler and modify responses before they are sent back to the client. This is particularly useful for cross-cutting concerns like:

  • Logging request details
  • Authentication and authorization
  • Input validation
  • Response compression
  • Error handling
  •  

Creating Middleware in Go

Let's start by setting up a simple HTTP server in Go.

 

Basic HTTP Server

First, create a new Go project and initialize a basic HTTP server.

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    http.HandleFunc("/", helloHandler)
    http.ListenAndServe(":8080", nil)
}

 

Running this code will start a web server on http://localhost:8080 that responds with "Hello, World!" to any request.

 

Writing Your First Middleware

Middleware in Go is typically a function that takes an http.Handler and returns another http.Handler. Here’s a simple logging middleware that logs each incoming request.

package main

import (
    "log"
    "net/http"
    "time"
)

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("Started %s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
        log.Printf("Completed %s in %v", r.URL.Path, time.Since(start))
    })
}

func main() {
    helloHandler := http.HandlerFunc(helloHandler)
    http.Handle("/", loggingMiddleware(helloHandler))
    http.ListenAndServe(":8080", nil)
}

 

In this middleware, we log the HTTP method and URL path at the start of the request and log the time taken to process the request after the handler has executed.

 

Chaining Multiple Middleware

You can chain multiple middleware functions to handle different concerns. Here’s how you can do it:

func chainMiddleware(h http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
    for _, middleware := range middlewares {
        h = middleware(h)
    }
    return h
}

func main() {
    helloHandler := http.HandlerFunc(helloHandler)

    middlewares := []func(http.Handler) http.Handler{
        loggingMiddleware,
        // Add more middleware here
    }

    http.Handle("/", chainMiddleware(helloHandler, middlewares...))
    http.ListenAndServe(":8080", nil)
}

 

Advanced Middleware Patterns

For more complex scenarios, you might want to handle response modification or wrap multiple handlers. Here’s an example of a middleware that compresses the response using gzip:

import (
    "compress/gzip"
    "io"
    "net/http"
    "strings"
)

func gzipMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
            next.ServeHTTP(w, r)
            return
        }

        gz := gzip.NewWriter(w)
        defer gz.Close()

        w.Header().Set("Content-Encoding", "gzip")
        gzw := gzipResponseWriter{Writer: gz, ResponseWriter: w}
        next.ServeHTTP(gzw, r)
    })
}

type gzipResponseWriter struct {
    io.Writer
    http.ResponseWriter
}

func (w gzipResponseWriter) Write(b []byte) (int, error) {
    return w.Writer.Write(b)
}

func main() {
    helloHandler := http.HandlerFunc(helloHandler)

    middlewares := []func(http.Handler) http.Handler{
        loggingMiddleware,
        authMiddleware,
        gzipMiddleware,
    }

    http.Handle("/", chainMiddleware(helloHandler, middlewares...))
    http.ListenAndServe(":8080", nil)
}

 

 

 

Conclusion

Middleware is a powerful concept that allows you to build modular and reusable components in your Go web applications. By understanding how to write and chain middleware, you can handle various cross-cutting concerns effectively. Whether you're logging requests, handling authentication, or compressing responses, middleware provides a clean and elegant way to manage these tasks.

291 views

Please Login to create a Question