前置知识: Go

Go与中间件

3 minIntermediate2026/6/14

HTTP中间件模式

概述

中间件是一种包装模式,在请求处理前后插入通用逻辑,如日志记录、认证、限流等。中间件将横切关注点与业务逻辑分离,使代码更清晰、更易维护。Go 的 net/http 原生支持中间件模式,不需要框架即可实现。

基础概念

在开始编码之前,需要理解中间件的几个核心概念

  • Handler:处理 HTTP 请求的函数或对象,签名为 func(w http.ResponseWriter, r *http.Request)
  • 中间件函数:接收一个 Handler,返回一个新的 Handler,在原 Handler 前后添加逻辑。
  • 中间件链:多个中间件按顺序组合,请求依次经过每个中间件,响应反向返回。
  • 洋葱模型:请求从外层中间件进入,到达核心 Handler 后,响应从内层向外层返回。

快速上手

最简单的中间件 — 日志记录:

package main

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

// 日志中间件
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("请求开始: %s %s", r.Method, r.URL.Path)

        next.ServeHTTP(w, r) // 调用下一个 Handler

        log.Printf("请求完成: %s %s 耗时: %v", r.Method, r.URL.Path, time.Since(start))
    })
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("你好!"))
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", helloHandler)

    // 用中间件包装路由
    handler := loggingMiddleware(mux)
    http.ListenAndServe(":8080", handler)
}

详细用法

1. 中间件的基本模式

// 标准中间件签名
func Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 前置逻辑:在 next.ServeHTTP 之前执行
        fmt.Println("请求前")

        next.ServeHTTP(w, r) // 调用下一个 Handler

        // 后置逻辑:在 next.ServeHTTP 之后执行
        fmt.Println("请求后")
    })
}

2. 认证中间件

func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "未认证", http.StatusUnauthorized)
            return // 不调用 next,请求被拦截
        }

        // 验证 token
        claims, err := validateToken(token)
        if err != nil {
            http.Error(w, "令牌无效", http.StatusUnauthorized)
            return
        }

        // 将用户信息存入 Context
        ctx := context.WithValue(r.Context(), "userID", claims.UserID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

3. CORS 中间件

func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.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 == http.MethodOptions {
            w.WriteHeader(http.StatusOK)
            return
        }

        next.ServeHTTP(w, r)
    })
}

4. 请求 ID 中间件

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 == "" {
            requestID = generateUUID()
        }

        ctx := context.WithValue(r.Context(), "requestID", requestID)
        w.Header().Set("X-Request-ID", requestID)

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

5. 恢复中间件(防崩溃)

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("panic 已恢复: %v\n%s", err, debug.Stack())
                http.Error(w, "服务器内部错误", http.StatusInternalServerError)
            }
        }()

        next.ServeHTTP(w, r)
    })
}

6. 组合多个中间件

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

// 使用
handler := Chain(mux,
    recoveryMiddleware,  // 最外层:恢复 panic
    loggingMiddleware,   // 日志
    corsMiddleware,      // CORS
    authMiddleware,      // 认证(最内层)
)

7. 响应包装器

如果中间件需要读取响应状态码,需要包装 ResponseWriter:

type responseRecorder struct {
    http.ResponseWriter
    statusCode int
}

func (r *responseRecorder) WriteHeader(code int) {
    r.statusCode = code
    r.ResponseWriter.WriteHeader(code)
}

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        recorder := &responseRecorder{ResponseWriter: w, statusCode: 200}

        next.ServeHTTP(recorder, r)

        log.Printf("%s %s %d %v", r.Method, r.URL.Path, recorder.statusCode, time.Since(start))
    })
}

常见场景

场景一:API 网关中间件栈

handler := Chain(mux,
    recoveryMiddleware,
    requestIDMiddleware,
    loggingMiddleware,
    corsMiddleware,
    rateLimitMiddleware(limiter),
    authMiddleware,
)

场景二:路由级中间件

某些路由需要认证,某些不需要:

mux := http.NewServeMux()

// 公开路由
mux.HandleFunc("GET /api/public", publicHandler)

// 受保护路由
protectedMux := http.NewServeMux()
protectedMux.HandleFunc("GET /api/users", listUsers)
protectedMux.HandleFunc("POST /api/users", createUser)

// 只对受保护路由应用认证中间件
mux.Handle("/api/users/", authMiddleware(protectedMux))

场景三:超时中间件

func timeoutMiddleware(duration time.Duration) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx, cancel := context.WithTimeout(r.Context(), duration)
            defer cancel()

            done := make(chan struct{})
            go func() {
                next.ServeHTTP(w, r.WithContext(ctx))
                close(done)
            }()

            select {
            case <-done:
                return
            case <-ctx.Done():
                http.Error(w, "请求超时", http.StatusGatewayTimeout)
            }
        })
    }
}

注意事项与常见错误

  1. 中间件顺序很重要:最外层的中间件最先执行前置逻辑,最后执行后置逻辑。恢复中间件应该放在最外层。

  2. 不要忘记调用 next:如果中间件不调用 next.ServeHTTP,请求链会中断,后续的 Handler 不会执行。

  3. Context 值的类型安全:使用自定义型作为 Context 的键,避免冲突:

type contextKey string
const userIDKey contextKey = "userID"
ctx := context.WithValue(r.Context(), userIDKey, "123")
  1. goroutine 泄漏:在中间件中启动 goroutine 时,确保它能正确退出,特别是当请求被取消时。

  2. ResponseWriter 只能写入一次:如果中间件已经写入了响应,后续的 Handler 不应该再写入。

  3. 中间件应该是无状态的:中间件不应该存储请求相关的状态。每个请求的数据应该通过 Context 传递。

进阶用法

使用 chi 路由器的中间件

chi 提供了更优雅的中间件管理:

import "github.com/go-chi/chi/v5"
import "github.com/go-chi/chi/v5/middleware"

r := chi.NewRouter()

// 全局中间件
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)

// 路由组中间件
r.Route("/admin", func(r chi.Router) {
    r.Use(authMiddleware) // 仅 admin 路由需要认证
    r.Get("/dashboard", adminDashboard)
})

条件中间件

func conditionalMiddleware(condition func(r *http.Request) bool, mw func(http.Handler) http.Handler) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if condition(r) {
                mw(next).ServeHTTP(w, r)
            } else {
                next.ServeHTTP(w, r)
            }
        })
    }
}

// 仅对 API 路径应用认证
conditionalMiddleware(func(r *http.Request) bool {
    return strings.HasPrefix(r.URL.Path, "/api/")
}, authMiddleware)