Middleware in an HTTP server in Go allows you to wrap the logic of your requests with reusable utilities, such as logging, authentication, or response modification. This helps in maintaining a cleaner and more organized codebase by separating concerns and adhering to the DRY (Don't Repeat Yourself) principle.
What is Middleware?
Middleware acts as a chain of handlers which are executed sequentially prior to your main request handler. Each middleware performs its duties before or after calling the next middleware in the chain.
Here is a basic example: consider an HTTP server where we want to log every request and ensure all endpoints are secured.
Creating a Simple Middleware in Go
If you're building an HTTP server using Go's standard library, you typically define a function that matches the signature:
func(yourHandler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Do something before calling the handler, e.g., logging
yourHandler.ServeHTTP(w, r)
// Do something after calling the handler, e.g., metrics collection
}
}
Let's start by setting up a logging middleware:
package main
import (
"fmt"
"log"
"net/http"
"time"
)
// Logger middleware logs each HTTP request.
func Logger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
})
}
func HelloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
}
func main() {
http.Handle("/", Logger(http.HandlerFunc(HelloHandler)))
fmt.Println("Starting server on :8080")
http.ListenAndServe(":8080", nil)
}
Adding Authentication Middleware
Let's add another piece of middleware for enforcing basic authentication:
func BasicAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok || user != "admin" || pass != "admin" {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Unauthorized"))
return
}
next.ServeHTTP(w, r)
})
}
func main() {
http.Handle("/", Logger(BasicAuth(http.HandlerFunc(HelloHandler))))
fmt.Println("Starting server on :8080")
http.ListenAndServe(":8080", nil)
}
Chaining Multiple Middlewares
You can keep chaining as many middleware functions as you need. Consider the sequence of calling them and ensure that each one's responsibility is clear and independent.
Conclusion
By using middleware in Go, you can efficiently manage cross-cutting concerns, maximize code reusability, and maintain a cleaner code structure. This pattern is particularly useful in web application development using Go's robust standard libraries.