Middleware Example

Advanced middleware patterns including authentication, rate limiting, and custom middleware

Complete Middleware Example

This example demonstrates various middleware patterns:

package main

import (
    "context"
    "fmt"
    "log"
    "strings"
    "sync"
    "time"
    "github.com/user001/squirrel"
)

// User represents an authenticated user
type User struct {
    ID       string `json:"id"`
    Username string `json:"username"`
    Role     string `json:"role"`
}

// Rate limiter
type RateLimiter struct {
    requests map[string][]time.Time
    mutex    sync.Mutex
    limit    int
    window   time.Duration
}

func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
    return &RateLimiter{
        requests: make(map[string][]time.Time),
        limit:    limit,
        window:   window,
    }
}

func (rl *RateLimiter) Allow(clientIP string) bool {
    rl.mutex.Lock()
    defer rl.mutex.Unlock()
    
    now := time.Now()
    
    // Clean old requests
    if requests, exists := rl.requests[clientIP]; exists {
        var validRequests []time.Time
        for _, reqTime := range requests {
            if now.Sub(reqTime) < rl.window {
                validRequests = append(validRequests, reqTime)
            }
        }
        rl.requests[clientIP] = validRequests
    }
    
    // Check if limit exceeded
    if len(rl.requests[clientIP]) >= rl.limit {
        return false
    }
    
    // Add current request
    rl.requests[clientIP] = append(rl.requests[clientIP], now)
    return true
}

// Global rate limiter (100 requests per minute)
var rateLimiter = NewRateLimiter(100, time.Minute)

// Mock user database
var users = map[string]*User{
    "token123": {ID: "1", Username: "john", Role: "user"},
    "token456": {ID: "2", Username: "admin", Role: "admin"},
}

func main() {
    mux := squirrel.NewSqurlMux()
    
    // Global middleware (applied to all routes)
    mux.Use(loggingMiddleware())
    mux.Use(recoveryMiddleware())
    mux.Use(corsMiddleware())
    mux.Use(rateLimitMiddleware())
    
    // Public routes
    mux.HandleFunc("GET /", homeHandler)
    mux.Use("GET /public", publicHandler)
    
    // Protected routes (require authentication)
    protectedMux := squirrel.NewSqurlMux()
    protectedMux.Use(authMiddleware())
    protectedMux.HandleFunc("GET /profile", profileHandler)
    protectedMux.HandleFunc("GET /dashboard", dashboardHandler)
    
    // Admin routes (require admin role)
    adminMux := squirrel.NewSqurlMux()
    adminMux.Use(authMiddleware())
    adminMux.Use(adminMiddleware())
    adminMux.HandleFunc("GET /admin/users", adminUsersHandler)
    adminMux.HandleFunc("DELETE /admin/users/{id}", adminDeleteUserHandler)
    
    // Mount sub-routers
    mux.Handle("/api/", squirrel.StripPrefix("/api", protectedMux))
    mux.Handle("/api/admin/", squirrel.StripPrefix("/api", adminMux))
    
    fmt.Println("Server with middleware starting on http://localhost:8080")
    log.Fatal(squirrel.ListenAndServe(":8080", mux))
}

// Logging middleware
func loggingMiddleware() squirrel.Middleware {
    return func(next squirrel.HandlerFunc) squirrel.HandlerFunc {
        return func(w *squirrel.Response, r *squirrel.Request) {
            start := time.Now()
            
            // Add request ID
            requestID := fmt.Sprintf("%d", time.Now().UnixNano())
            w.Header().Set("X-Request-ID", requestID)
            
            // Call next handler
            next(w, r)
            
            // Log request details
            duration := time.Since(start)
            log.Printf("[%s] %s %s - %v", requestID, r.Method, r.URL.Path, duration)
        }
    }
}

// Recovery middleware
func recoveryMiddleware() squirrel.Middleware {
    return func(next squirrel.HandlerFunc) squirrel.HandlerFunc {
        return func(w *squirrel.Response, r *squirrel.Request) {
            defer func() {
                if err := recover(); err != nil {
                    log.Printf("Panic recovered: %v", err)
                    w.WriteStatus(500)
                    w.JSON(map[string]string{
                        "error": "Internal server error",
                    })
                }
            }()
            
            next(w, r)
        }
    }
}

// CORS middleware
func corsMiddleware() squirrel.Middleware {
    return func(next squirrel.HandlerFunc) squirrel.HandlerFunc {
        return func(w *squirrel.Response, r *squirrel.Request) {
            w.Header().Set("Access-Control-Allow-Origin", "*")
            w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
            w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
            
            if r.Method == "OPTIONS" {
                w.WriteStatus(200)
                return
            }
            
            next(w, r)
        }
    }
}

// Rate limiting middleware
func rateLimitMiddleware() squirrel.Middleware {
    return func(next squirrel.HandlerFunc) squirrel.HandlerFunc {
        return func(w *squirrel.Response, r *squirrel.Request) {
            clientIP := getClientIP(r)
            
            if !rateLimiter.Allow(clientIP) {
                w.WriteStatus(429)
                w.JSON(map[string]string{
                    "error": "Rate limit exceeded",
                    "retry_after": "60",
                })
                return
            }
            
            next(w, r)
        }
    }
}

// Authentication middleware
func authMiddleware() squirrel.Middleware {
    return func(next squirrel.HandlerFunc) squirrel.HandlerFunc {
        return func(w *squirrel.Response, r *squirrel.Request) {
            authHeader := r.Header.Get("Authorization")
            if authHeader == "" {
                w.WriteStatus(401)
                w.JSON(map[string]string{
                    "error": "Authorization header required",
                })
                return
            }
            
            // Extract token (Bearer token format)
            parts := strings.Split(authHeader, " ")
            if len(parts) != 2 || parts[0] != "Bearer" {
                w.WriteStatus(401)
                w.JSON(map[string]string{
                    "error": "Invalid authorization format",
                })
                return
            }
            
            token := parts[1]
            user, exists := users[token]
            if !exists {
                w.WriteStatus(401)
                w.JSON(map[string]string{
                    "error": "Invalid token",
                })
                return
            }
            
            // Add user to request context
            ctx := context.WithValue(r.Context(), "user", user)
            r = r.WithContext(ctx)
            
            next(w, r)
        }
    }
}

// Admin middleware (requires admin role)
func adminMiddleware() squirrel.Middleware {
    return func(next squirrel.HandlerFunc) squirrel.HandlerFunc {
        return func(w *squirrel.Response, r *squirrel.Request) {
            user, ok := r.Context().Value("user").(*User)
            if !ok {
                w.WriteStatus(500)
                w.JSON(map[string]string{
                    "error": "User context not found",
                })
                return
            }
            
            if user.Role != "admin" {
                w.WriteStatus(403)
                w.JSON(map[string]string{
                    "error": "Admin access required",
                })
                return
            }
            
            next(w, r)
        }
    }
}

// Timing middleware
func timingMiddleware() squirrel.Middleware {
    return func(next squirrel.HandlerFunc) squirrel.HandlerFunc {
        return func(w *squirrel.Response, r *squirrel.Request) {
            start := time.Now()
            
            next(w, r)
            
            duration := time.Since(start)
            w.Header().Set("X-Response-Time", duration.String())
        }
    }
}

// Helper function to get client IP
func getClientIP(r *squirrel.Request) string {
    // Check X-Forwarded-For header
    if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
        return strings.Split(xff, ",")[0]
    }
    
    // Check X-Real-IP header
    if xri := r.Header.Get("X-Real-IP"); xri != "" {
        return xri
    }
    
    // Fall back to RemoteAddr
    return r.RemoteAddr
}

// Handler functions
func homeHandler(w *squirrel.Response, r *squirrel.Request) {
    w.JSON(map[string]string{
        "message": "Welcome to the middleware example",
        "status":  "public",
    })
}

func publicHandler(w *squirrel.Response, r *squirrel.Request) {
    w.JSON(map[string]string{
        "message": "This is a public endpoint",
        "time":    time.Now().Format(time.RFC3339),
    })
}

func profileHandler(w *squirrel.Response, r *squirrel.Request) {
    user := r.Context().Value("user").(*User)
    w.JSON(map[string]interface{}{
        "message": "User profile",
        "user":    user,
    })
}

func dashboardHandler(w *squirrel.Response, r *squirrel.Request) {
    user := r.Context().Value("user").(*User)
    w.JSON(map[string]interface{}{
        "message": "User dashboard",
        "user_id": user.ID,
        "data":    []string{"item1", "item2", "item3"},
    })
}

func adminUsersHandler(w *squirrel.Response, r *squirrel.Request) {
    var userList []*User
    for _, user := range users {
        userList = append(userList, user)
    }
    
    w.JSON(map[string]interface{}{
        "message": "Admin users list",
        "users":   userList,
    })
}

func adminDeleteUserHandler(w *squirrel.Response, r *squirrel.Request) {
    userID := r.PathValue("id")
    
    w.JSON(map[string]interface{}{
        "message": "User deleted (simulated)",
        "user_id": userID,
    })
}

Testing the Middleware

Public Endpoints

curl http://localhost:8080/
curl http://localhost:8080/public

Protected Endpoints (Require Authentication)

curl -H &quote;Authorization: Bearer token123&quote; http://localhost:8080/api/profile
curl -H &quote;Authorization: Bearer token123&quote; http://localhost:8080/api/dashboard

Admin Endpoints (Require Admin Role)

curl -H &quote;Authorization: Bearer token456&quote; http://localhost:8080/api/admin/users
curl -X DELETE -H &quote;Authorization: Bearer token456&quote; http://localhost:8080/api/admin/users/1

Rate Limiting Test

for i in {1..105}; do curl http://localhost:8080/; done

After 100 requests, you will get a 429 rate limit error

Middleware Features

Security Middleware

  • • Authentication with Bearer tokens
  • • Role-based authorization
  • • CORS handling
  • • Rate limiting

Utility Middleware

  • • Request logging
  • • Panic recovery
  • • Response timing
  • • Request ID generation

Advanced Patterns

  • • Middleware chaining
  • • Conditional middleware
  • • Context passing
  • • Sub-router mounting

Error Handling

  • • Structured error responses
  • • HTTP status codes
  • • Graceful degradation
  • • Request validation

Middleware Order

📋 Important: Middleware Execution Order

  1. Recovery (catch panics first)
  2. Logging (log all requests)
  3. CORS (handle preflight requests)
  4. Rate limiting (before expensive operations)
  5. Authentication (verify user identity)
  6. Authorization (check permissions)
  7. Business logic (your handlers)

Production Enhancements

  • Use Redis for distributed rate limiting
  • Implement JWT token validation
  • Add request/response compression
  • Implement circuit breaker patterns
  • Add metrics and monitoring middleware
  • Use structured logging (JSON format)
  • Implement request timeout middleware

Next Steps