HTTP中間件是Go Web開發中的核心概念,它允許我們在HTTP請求-響應週期中插入自定義邏輯,從而增強應用的功能和性能。本文將深入探討Go中HTTP中間件的實現、應用和最佳實踐。

什麼是HTTP中間件?

HTTP中間件是一種設計模式,它包裝HTTP處理程序,在請求到達實際處理程序之前或之後執行特定操作。中間件形成一個處理鏈,每個中間件都可以:

  • 修改請求或響應
  • 執行認證和授權
  • 記錄日誌
  • 處理錯誤
  • 實現緩存
  • 限制速率

基本中間件模式

在Go中,中間件通常是一個函數,它接受一個http.Handler並返回一個新的http.Handler

package main

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

// 基礎中間件模板
type Middleware func(http.Handler) http.Handler

// 應用多箇中間件
func ApplyMiddleware(h http.Handler, middlewares ...Middleware) http.Handler {
    for _, middleware := range middlewares {
        h = middleware(h)
    }
    return h
}

// 日誌中間件
func LoggingMiddleware(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 %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// 認證中間件
func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token != "Bearer valid-token" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func main() {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, World!")
    })
  
    // 應用中間件
    wrappedHandler := ApplyMiddleware(handler, LoggingMiddleware, AuthMiddleware)
  
    http.Handle("/", wrappedHandler)
    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

性能相關的中間件

1. 緩存中間件

import (
    "crypto/sha1"
    "encoding/hex"
    "net/http"
    "sync"
    "time"
)

type cacheEntry struct {
    data       []byte
    headers    http.Header
    statusCode int
    expiry     time.Time
}

type CacheMiddleware struct {
    cache map[string]*cacheEntry
    mutex sync.RWMutex
    ttl   time.Duration
}

func NewCacheMiddleware(ttl time.Duration) *CacheMiddleware {
    return &CacheMiddleware{
        cache: make(map[string]*cacheEntry),
        ttl:   ttl,
    }
}

func (c *CacheMiddleware) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 只緩存GET請求
        if r.Method != "GET" {
            next.ServeHTTP(w, r)
            return
        }
      
        // 生成緩存鍵
        key := c.generateKey(r)
      
        // 檢查緩存
        c.mutex.RLock()
        entry, exists := c.cache[key]
        c.mutex.RUnlock()
      
        if exists && time.Now().Before(entry.expiry) {
            // 從緩存返回響應
            for k, v := range entry.headers {
                w.Header()[k] = v
            }
            w.WriteHeader(entry.statusCode)
            w.Write(entry.data)
            return
        }
      
        // 創建自定義ResponseWriter來捕獲響應
        crw := &capturingResponseWriter{ResponseWriter: w}
        next.ServeHTTP(crw, r)
      
        // 緩存響應
        if crw.statusCode == http.StatusOK {
            c.mutex.Lock()
            c.cache[key] = &cacheEntry{
                data:       crw.data,
                headers:    crw.Header().Clone(),
                statusCode: crw.statusCode,
                expiry:     time.Now().Add(c.ttl),
            }
            c.mutex.Unlock()
        }
    })
}

func (c *CacheMiddleware) generateKey(r *http.Request) string {
    hash := sha1.New()
    hash.Write([]byte(r.URL.String()))
    return hex.EncodeToString(hash.Sum(nil))
}

type capturingResponseWriter struct {
    http.ResponseWriter
    statusCode int
    data       []byte
}

func (w *capturingResponseWriter) WriteHeader(code int) {
    w.statusCode = code
    w.ResponseWriter.WriteHeader(code)
}

func (w *capturingResponseWriter) Write(data []byte) (int, error) {
    w.data = append(w.data, data...)
    return w.ResponseWriter.Write(data)
}

2. 壓縮中間件

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

type GzipMiddleware struct{}

func (g *GzipMiddleware) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 檢查客户端是否支持gzip
        if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
            next.ServeHTTP(w, r)
            return
        }
      
        // 設置gzip響應頭
        w.Header().Set("Content-Encoding", "gzip")
      
        gz := gzip.NewWriter(w)
        defer gz.Close()
      
        grw := &gzipResponseWriter{ResponseWriter: w, Writer: gz}
        next.ServeHTTP(grw, r)
    })
}

type gzipResponseWriter struct {
    http.ResponseWriter
    Writer *gzip.Writer
}

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

3. 速率限制中間件

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

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) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        clientIP := r.RemoteAddr
      
        rl.mutex.Lock()
        defer rl.mutex.Unlock()
      
        now := time.Now()
      
        // 清理過期的請求記錄
        if requests, exists := rl.requests[clientIP]; exists {
            var validRequests []time.Time
            for _, t := range requests {
                if now.Sub(t) <= rl.window {
                    validRequests = append(validRequests, t)
                }
            }
            rl.requests[clientIP] = validRequests
        }
      
        // 檢查是否超過限制
        if len(rl.requests[clientIP]) >= rl.limit {
            http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
            return
        }
      
        // 記錄當前請求
        rl.requests[clientIP] = append(rl.requests[clientIP], now)
      
        next.ServeHTTP(w, r)
    })
}

功能增強中間件

1. 請求ID中間件

import (
    "context"
    "crypto/rand"
    "encoding/hex"
    "net/http"
)

type contextKey string

const RequestIDKey contextKey = "requestID"

func RequestIDMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        requestID := r.Header.Get("X-Request-ID")
        if requestID == "" {
            // 生成隨機請求ID
            buf := make([]byte, 8)
            rand.Read(buf)
            requestID = hex.EncodeToString(buf)
        }
      
        // 設置響應頭
        w.Header().Set("X-Request-ID", requestID)
      
        // 將請求ID添加到上下文
        ctx := context.WithValue(r.Context(), RequestIDKey, requestID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

2. 超時中間件

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

func TimeoutMiddleware(timeout time.Duration) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx, cancel := context.WithTimeout(r.Context(), timeout)
            defer cancel()
          
            r = r.WithContext(ctx)
          
            done := make(chan bool, 1)
            go func() {
                next.ServeHTTP(w, r)
                done <- true
            }()
          
            select {
            case <-done:
                return
            case <-ctx.Done():
                http.Error(w, "Request timeout", http.StatusRequestTimeout)
            }
        })
    }
}

第三方中間件庫

除了自定義中間件,Go社區也提供了許多優秀的中間件庫:

1. Gorilla Handlers

import (
    "github.com/gorilla/handlers"
    "net/http"
    "os"
)

func main() {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, World!"))
    })
  
    // 日誌記錄到標準輸出
    loggedHandler := handlers.LoggingHandler(os.Stdout, handler)
  
    // CORS支持
    corsHandler := handlers.CORS(
        handlers.AllowedOrigins([]string{"*"}),
        handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE"}),
    )(loggedHandler)
  
    http.Handle("/", corsHandler)
    http.ListenAndServe(":8080", nil)
}

2. Alice - 優雅的中間件鏈

import (
    "github.com/justinas/alice"
    "net/http"
)

func main() {
    common := alice.New(
        LoggingMiddleware,
        RequestIDMiddleware,
    )
  
    authRequired := common.Append(AuthMiddleware)
  
    http.Handle("/public", common.ThenFunc(publicHandler))
    http.Handle("/private", authRequired.ThenFunc(privateHandler))
  
    http.ListenAndServe(":8080", nil)
}

func publicHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Public content"))
}

func privateHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Private content"))
}

最佳實踐

1. 中間件順序很重要

// 正確的順序
chain := alice.New(
    RecoveryMiddleware,    // 首先處理panic
    LoggingMiddleware,     // 然後記錄日誌
    AuthMiddleware,        // 接着進行認證
    RateLimitingMiddleware,// 最後進行速率限制
).Then(handler)

2. 使用上下文傳遞數據

func UserMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 從認證token獲取用户信息
        user := getUserFromToken(r)
      
        ctx := context.WithValue(r.Context(), "user", user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func userHandler(w http.ResponseWriter, r *http.Request) {
    user := r.Context().Value("user").(*User)
    // 使用用户信息
}

3. 錯誤處理中間件

func RecoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("Recovered from panic: %v", err)
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

性能考慮

  1. 避免不必要的中間件:每個中間件都會增加處理時間,只在需要的地方使用
  2. 合理使用緩存:對靜態內容或變化不頻繁的數據使用緩存
  3. 異步處理:對於耗時的操作,考慮使用異步處理
  4. 連接池:合理配置數據庫和HTTP客户端連接池

結論

Go中的HTTP中間件是構建強大、可維護Web應用的關鍵工具。通過合理設計和組合中間件,我們可以:

  • 增強應用功能(認證、日誌、錯誤處理等)
  • 提升性能(緩存、壓縮、速率限制等)
  • 保持代碼的模塊化和可測試性
  • 實現橫切關注點的集中管理

掌握中間件模式不僅能讓你的Go Web應用更加健壯,還能顯著提升開發效率和系統性能。