golib/http/middleware

http/middleware

A simple zero-cost middleware handler for Go's native net/http ServeMux.
(only 50 lines long, using only net/http and slices)

Turns tedious this:

mux.HandleFunc("GET /api/version", logRequests(timeRequests(getVersion)))
mux.HandleFunc("GET /api/data", logRequests(timeRequests(requireAuth(requireAdmin(getData)))))

Into organizable this:

mw := middleware.New(logRequests, timeRequests)
mux.HandleFunc("GET /api/version", mw.Handle(getVersion))

authMW := m.Use(requireAuth, requireAdmin)
mux.HandleFunc("GET /api/data", authMW.Handle(getData))

Using stdlib this:

type Middleware func(http.HandlerFunc) http.HandlerFunc

Zero-cost because each invocation of mv.Handle(handler) composes the function calls exactly the same way as when done manually.
(the setup is done during route initialization and has no additional impact on requests)

Usage

  • Create with middleware.New(middlewares...)
  • Extend with mw.Use(midlewares...) (copies and appends)
  • Apply with specific.Handle(handler)
package main

import (
   "net/http"

   "github.com/therootcompany/golib/middleware"
)

func main() {
   mux := http.NewServeMux()

   mw := middleware.New(logRequests, basicAuth)
   mux.HandleFunc("GET /api/data", mw.Handle(getData))
   mux.HandleFunc("POST /api/data", mw.Handle(postData))

   adminMW := mw.Use(requireAdmin)
   mux.HandleFunc("DELETE /api/data", adminMW.Handle(deleteData))

   http.ListenAndServe(":8080", mux)
}

Example Middleware

Middleware is any function that wraps and returns the built-in http.HandlerFunc handler type.

type Middleware func(http.HandlerFunc) http.HandlerFunc

Example: Request logger

func logRequests(next http.HandlerFunc) http.HandlerFunc {
   return func(w http.ResponseWriter, r *http.Request) {

      start := time.Now()
      next.ServeHTTP(w, r)
      log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
   }
}

Example: Basic Auth Verifier, using envauth

import "github.com/therootcompany/golib/auth/envauth"

var creds = envauth.BasicCredentials{
   Username: os.Getenv("BASIC_AUTH_USERNAME"),
   Password: os.Getenv("BASIC_AUTH_PASSWORD"),
}

func basicAuth(next http.HandlerFunc) http.HandlerFunc {
   return func(w http.ResponseWriter, r *http.Request) {

      user, pass, _ := r.BasicAuth()
      if err := creds.Verify(user, pass); err != nil {
         http.Error(w, "Unauthorized", http.StatusUnauthorized)
         return
      }

      next.ServeHTTP(w, r)
   }
}

Example: Admin role checker

func requireAdmin(next http.HandlerFunc) http.HandlerFunc {
   return func(w http.ResponseWriter, r *http.Request) {

      // Assume JWT in context with roles
      jws := r.Context().Value("jwt").(jwt.JWS)
      if !slices.Contains(jws.Claims.Roles, "admin") {
         http.Error(w, "Forbidden: Admin access required", http.StatusForbidden)
         return
      }

      next.ServeHTTP(w, r)
   }
}