Skip to content

5.8 中间件高级 ​

中间件原理 ​

中间件链式调用 ​

go
package main

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

// 中间件类型
type Middleware func(http.Handler) http.Handler

// 中间件链
type MiddlewareChain struct {
    middlewares []Middleware
}

func NewMiddlewareChain(middlewares ...Middleware) *MiddlewareChain {
    return &MiddlewareChain{middlewares: middlewares}
}

func (c *MiddlewareChain) Append(middleware Middleware) {
    c.middlewares = append(c.middlewares, middleware)
}

func (c *MiddlewareChain) Then(handler http.Handler) http.Handler {
    // 反向包装,确保执行顺序正确
    for i := len(c.middlewares) - 1; i >= 0; i-- {
        handler = c.middlewares[i](handler)
    }
    return handler
}

func (c *MiddlewareChain) ThenFunc(handler http.HandlerFunc) http.Handler {
    return c.Then(handler)
}

// 示例
func main() {
    chain := NewMiddlewareChain(
        LoggingMiddleware,
        RecoveryMiddleware,
        AuthMiddleware,
    )

    handler := chain.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello"))
    })

    http.Handle("/", handler)
    http.ListenAndServe(":8080", nil)
}

链路追踪中间件 ​

OpenTelemetry 集成 ​

go
package middleware

import (
    "context"
    "net/http"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/propagation"
    "go.opentelemetry.io/otel/trace"
)

var tracer = otel.Tracer("http-middleware")

// Trace 中间件
func TraceMiddleware(serviceName string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx := r.Context()

            // 提取 trace context
            propagator := otel.GetTextMapPropagator()
            ctx = propagator.Extract(ctx, propagation.HeaderCarrier(r.Header))

            // 创建 span
            ctx, span := tracer.Start(
                ctx,
                r.URL.Path,
                trace.WithSpanKind(trace.SpanKindServer),
                trace.WithAttributes(
                    attribute.String("http.method", r.Method),
                    attribute.String("http.url", r.URL.String()),
                    attribute.String("http.user_agent", r.UserAgent()),
                ),
            )
            defer span.End()

            // 将 ctx 传递给下游
            r = r.WithContext(ctx)

            // 包装 ResponseWriter 获取状态码
            ww := &wrapWriter{ResponseWriter: w, statusCode: http.StatusOK}
            next.ServeHTTP(ww, r)

            // 设置 span 属性
            span.SetAttributes(
                attribute.Int("http.status_code", ww.statusCode),
            )
        })
    }
}

type wrapWriter struct {
    http.ResponseWriter
    statusCode int
}

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

初始化 OpenTelemetry ​

go
package main

import (
    "context"
    "log"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/jaeger"
    "go.opentelemetry.io/otel/propagation"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)

func InitTracer(serviceName, endpoint string) (*sdktrace.TracerProvider, error) {
    // 创建 Jaeger exporter
    exporter, err := jaeger.New(
        jaeger.WithCollectorEndpoint(
            jaeger.WithEndpoint(endpoint),
        ),
    )
    if err != nil {
        return nil, err
    }

    // 创建 TracerProvider
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String(serviceName),
        )),
    )

    otel.SetTracerProvider(tp)
    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{},
        propagation.Baggage{},
    ))

    return tp, nil
}

func main() {
    tp, err := InitTracer("my-service", "http://localhost:14268/api/traces")
    if err != nil {
        log.Fatal(err)
    }
    defer tp.Shutdown(context.Background())

    // 启动服务...
}

限流中间件 ​

令牌桶算法 ​

go
package middleware

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

    "golang.org/x/time/rate"
)

// 令牌桶限流
type TokenBucketLimiter struct {
    mu       sync.Mutex
    limiters map[string]*rate.Limiter
    r        rate.Limit
    b        int
}

func NewTokenBucketLimiter(r rate.Limit, b int) *TokenBucketLimiter {
    return &TokenBucketLimiter{
        limiters: make(map[string]*rate.Limiter),
        r:        r,
        b:        b,
    }
}

func (t *TokenBucketLimiter) getLimiter(key string) *rate.Limiter {
    t.mu.Lock()
    defer t.mu.Unlock()

    limiter, ok := t.limiters[key]
    if !ok {
        limiter = rate.NewLimiter(t.r, t.b)
        t.limiters[key] = limiter
    }
    return limiter
}

func (t *TokenBucketLimiter) Allow(key string) bool {
    return t.getLimiter(key).Allow()
}

// 限流中间件 (按 IP)
func RateLimitMiddleware(limiter *TokenBucketLimiter) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ip := getClientIP(r)

            if !limiter.Allow(ip) {
                http.Error(w, "rate limit exceeded", http.StatusTooManyRequests)
                return
            }

            next.ServeHTTP(w, r)
        })
    }
}

// 获取客户端 IP
func getClientIP(r *http.Request) string {
    // 检查 X-Forwarded-For
    if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
        return xff
    }
    // 检查 X-Real-IP
    if xri := r.Header.Get("X-Real-IP"); xri != "" {
        return xri
    }
    // 返回 RemoteAddr
    return r.RemoteAddr
}

滑动窗口限流 ​

go
package middleware

import (
    "sync"
    "time"
)

type SlidingWindowLimiter struct {
    mu         sync.Mutex
    windows    map[string][]time.Time
    maxReqs    int
    windowSize time.Duration
}

func NewSlidingWindowLimiter(maxReqs int, windowSize time.Duration) *SlidingWindowLimiter {
    return &SlidingWindowLimiter{
        windows:    make(map[string][]time.Time),
        maxReqs:    maxReqs,
        windowSize: windowSize,
    }
}

func (s *SlidingWindowLimiter) Allow(key string) bool {
    s.mu.Lock()
    defer s.mu.Unlock()

    now := time.Now()
    cutoff := now.Add(-s.windowSize)

    // 获取窗口内的请求
    times := s.windows[key]
    valid := []time.Time{}
    for _, t := range times {
        if t.After(cutoff) {
            valid = append(valid, t)
        }
    }

    // 检查是否超过限制
    if len(valid) >= s.maxReqs {
        s.windows[key] = valid
        return false
    }

    // 添加新请求
    valid = append(valid, now)
    s.windows[key] = valid
    return true
}

// 定期清理过期数据
func (s *SlidingWindowLimiter) Cleanup(interval time.Duration) {
    go func() {
        for range time.Tick(interval) {
            s.mu.Lock()
            now := time.Now()
            cutoff := now.Add(-s.windowSize)

            for key, times := range s.windows {
                valid := []time.Time{}
                for _, t := range times {
                    if t.After(cutoff) {
                        valid = append(valid, t)
                    }
                }
                if len(valid) == 0 {
                    delete(s.windows, key)
                } else {
                    s.windows[key] = valid
                }
            }
            s.mu.Unlock()
        }
    }()
}

熔断中间件 ​

熔断器实现 ​

go
package middleware

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

type State int

const (
    StateClosed State = iota  // 正常状态
    StateOpen                 // 熔断打开
    StateHalfOpen             // 半开状态
)

type CircuitBreaker struct {
    mu           sync.Mutex
    state        State
    failures     int
    maxFailures  int
    resetTimeout time.Duration
    lastFailTime time.Time
    successCount int
}

func NewCircuitBreaker(maxFailures int, resetTimeout time.Duration) *CircuitBreaker {
    return &CircuitBreaker{
        state:        StateClosed,
        maxFailures:  maxFailures,
        resetTimeout: resetTimeout,
    }
}

func (cb *CircuitBreaker) Allow() error {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    switch cb.state {
    case StateOpen:
        // 检查是否应该切换到半开状态
        if time.Since(cb.lastFailTime) > cb.resetTimeout {
            cb.state = StateHalfOpen
            cb.successCount = 0
            return nil
        }
        return errors.New("circuit breaker is open")

    case StateHalfOpen:
        return nil

    default:
        return nil
    }
}

func (cb *CircuitBreaker) RecordSuccess() {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    if cb.state == StateHalfOpen {
        cb.successCount++
        if cb.successCount >= 3 {
            cb.state = StateClosed
            cb.failures = 0
        }
    }
}

func (cb *CircuitBreaker) RecordFailure() {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    cb.failures++
    cb.lastFailTime = time.Now()

    if cb.failures >= cb.maxFailures {
        cb.state = StateOpen
    }
}

// 熔断中间件
func CircuitBreakerMiddleware(cb *CircuitBreaker) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if err := cb.Allow(); err != nil {
                http.Error(w, "service unavailable", http.StatusServiceUnavailable)
                return
            }

            // 包装 ResponseWriter
            ww := &wrapWriter{ResponseWriter: w, statusCode: http.StatusOK}

            next.ServeHTTP(ww, r)

            // 根据状态码记录成功或失败
            if ww.statusCode >= 500 {
                cb.RecordFailure()
            } else {
                cb.RecordSuccess()
            }
        })
    }
}

gobreaker 库使用 ​

go
package main

import (
    "net/http"
    "time"

    "github.com/sony/gobreaker"
)

var cb *gobreaker.CircuitBreaker

func init() {
    st := gobreaker.Settings{
        Name:        "http-client",
        MaxFailures: 5,
        Timeout:     30 * time.Second,
    }
    cb = gobreaker.NewCircuitBreaker(st)
}

func httpClient(url string) ([]byte, error) {
    _, err := cb.Execute(func() (interface{}, error) {
        resp, err := http.Get(url)
        if err != nil {
            return nil, err
        }
        defer resp.Body.Close()
        // 读取响应...
        return nil, nil
    })
    return nil, err
}

请求日志中间件 ​

Zap 结构化日志 ​

go
package middleware

import (
    "net/http"
    "time"

    "go.uber.org/zap"
)

func LoggingMiddleware(logger *zap.Logger) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            start := time.Now()

            // 包装 ResponseWriter
            ww := &wrapWriter{ResponseWriter: w, statusCode: http.StatusOK, written: 0}

            next.ServeHTTP(ww, r)

            // 记录日志
            logger.Info("HTTP request",
                zap.String("method", r.Method),
                zap.String("path", r.URL.Path),
                zap.String("ip", getClientIP(r)),
                zap.String("user_agent", r.UserAgent()),
                zap.Int("status", ww.statusCode),
                zap.Int("bytes", ww.written),
                zap.Duration("duration", time.Since(start)),
            )
        })
    }
}

type wrapWriter struct {
    http.ResponseWriter
    statusCode int
    written    int
}

func (w *wrapWriter) Write(b []byte) (int, error) {
    n, err := w.ResponseWriter.Write(b)
    w.written += n
    return n, err
}

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

认证中间件 ​

JWT 认证 ​

go
package middleware

import (
    "context"
    "net/http"
    "strings"

    "github.com/golang-jwt/jwt/v5"
)

type contextKey string

const UserClaimsKey contextKey = "user_claims"

type Claims struct {
    UserID string   `json:"user_id"`
    Roles  []string `json:"roles"`
    jwt.RegisteredClaims
}

func JWTMiddleware(secret string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            authHeader := r.Header.Get("Authorization")
            if authHeader == "" {
                http.Error(w, "missing authorization header", http.StatusUnauthorized)
                return
            }

            tokenStr := strings.TrimPrefix(authHeader, "Bearer ")

            claims := &Claims{}
            token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
                return []byte(secret), nil
            })

            if err != nil || !token.Valid {
                http.Error(w, "invalid token", http.StatusUnauthorized)
                return
            }

            ctx := context.WithValue(r.Context(), UserClaimsKey, claims)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// 从 context 获取用户
func GetUserFromContext(ctx context.Context) *Claims {
    claims, ok := ctx.Value(UserClaimsKey).(*Claims)
    if !ok {
        return nil
    }
    return claims
}

// 角色授权中间件
func RequireRole(requiredRoles ...string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            claims := GetUserFromContext(r.Context())
            if claims == nil {
                http.Error(w, "unauthorized", http.StatusUnauthorized)
                return
            }

            // 检查角色
            hasRole := false
            for _, reqRole := range requiredRoles {
                for _, userRole := range claims.Roles {
                    if userRole == reqRole {
                        hasRole = true
                        break
                    }
                }
            }

            if !hasRole {
                http.Error(w, "forbidden", http.StatusForbidden)
                return
            }

            next.ServeHTTP(w, r)
        })
    }
}

中间件链管理 ​

条件中间件 ​

go
package middleware

import "net/http"

// 条件应用中间件
func If(condition bool, middleware Middleware) Middleware {
    return func(next http.Handler) http.Handler {
        if condition {
            return middleware(next)
        }
        return next
    }
}

// 仅在特定路径应用中间件
func ForPath(pathPrefix string, middleware Middleware) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if strings.HasPrefix(r.URL.Path, pathPrefix) {
                return middleware(next).ServeHTTP(w, r)
            }
            return next.ServeHTTP(w, r)
        })
    }
}

// 中间件组合
func Chain(middlewares ...Middleware) Middleware {
    return func(next http.Handler) http.Handler {
        for i := len(middlewares) - 1; i >= 0; i-- {
            next = middlewares[i](next)
        }
        return next
    }
}

中间件检查清单 ​

[ ] 中间件执行顺序正确
[ ] 错误处理适当
[ ] 性能影响最小化
[ ] 链路追踪集成
[ ] 限流策略合理
[ ] 熔断器配置适当
[ ] 日志结构化
[ ] 认证授权分离
[ ] 中间件可测试
[ ] 支持热重载配置