Go HTTP 中间件基础:标准库 Handler 与链式调用
在微服务架构中,中间件是横切关注点(认证、日志、限流)的核心载体。Go 标准库 net/http 提供了极简但强大的接口,所有中间件模式都建立在 Handler 与 HandlerFunc 之上。理解这两个类型,是掌握 OAuth2.0 认证中间件的前提。
1. Handler 与 HandlerFunc 接口
http.Handler 是 Go HTTP 处理的核心契约,只定义一个方法:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
http.HandlerFunc 是一个函数类型适配器,允许普通函数直接实现 Handler 接口:
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
| 类型 | 本质 | 使用场景 |
|---|---|---|
http.Handler |
接口(1 个方法) | 定义处理单元、中间件包装 |
http.HandlerFunc |
函数类型 + 接口实现 | 将普通函数转为 Handler |
关键理解: 任何实现了
ServeHTTP 方法的类型都是 Handler。中间件本质上是一个接收 Handler 并返回新 Handler 的函数。
2. 中间件链式调用原理
中间件链式调用的核心模式是 装饰器模式:每个中间件包装下一个 Handler,形成嵌套调用栈。请求从最外层中间件进入,依次向内传递,响应则反向流出。
func Logger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("[%s] %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r) // 调用下一个 Handler
})
}
func Auth(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, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
链式组合方式:
http.Handle("/api", Logger(Auth(finalHandler)))
执行顺序: 请求 → Logger(日志)→ Auth(认证)→ finalHandler(业务)→ 响应反向返回。
3. 自定义中间件示例:请求 ID 注入
以下示例展示如何编写一个完整的自定义中间件,为每个请求注入唯一追踪 ID:
package middleware
import (
"context"
"crypto/rand"
"encoding/hex"
"net/http"
)
type contextKey string
const RequestIDKey contextKey = "request_id"
func generateRequestID() string {
b := make([]byte, 8)
rand.Read(b)
return hex.EncodeToString(b)
}
// RequestID 中间件:为每个请求生成唯一 ID 并注入 context
func RequestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := generateRequestID()
ctx := context.WithValue(r.Context(), RequestIDKey, id)
w.Header().Set("X-Request-ID", id)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// 从 context 中提取请求 ID 的辅助函数
func GetRequestID(ctx context.Context) string {
if id, ok := ctx.Value(RequestIDKey).(string); ok {
return id
}
return ""
}
并发安全注意: 每个请求的
context 是独立的,因此 context.WithValue 在并发环境下是安全的。但中间件内部如果使用共享变量(如计数器、缓存),必须加锁或使用原子操作。
4. 中间件组合与路由集成
实际项目中,中间件通常按固定顺序组合。以下表格列出常见中间件及其推荐顺序:
| 顺序 | 中间件 | 职责 |
|---|---|---|
| 1 | PanicRecovery | 捕获 panic,返回 500 |
| 2 | RequestID | 注入追踪 ID |
| 3 | AccessLog | 记录请求日志 |
| 4 | RateLimiter | 限流保护 |
| 5 | Auth | OAuth2.0 认证 |
| 6 | BusinessHandler | 业务逻辑 |
使用第三方路由库(如 gorilla/mux 或 chi)时,中间件组合更简洁:
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Get("/api/user", userHandler)
r.Post("/api/order", orderHandler)
5. 中间件最佳实践
- 单一职责: 每个中间件只做一件事(认证、日志、限流),便于测试和复用。
- Context 传递: 使用
context.WithValue在中间件之间传递请求级数据,避免全局变量。 - 尽早返回: 认证失败、限流触发等场景应尽早返回错误,避免无效调用。
- Panic 恢复: 最外层中间件应包含
recover(),防止单个请求崩溃整个服务。 - 避免阻塞: 中间件内不要执行长时间阻塞操作(如数据库查询),应异步处理或使用超时控制。
核心总结: Go 标准库的 Handler 接口是中间件体系的基石。通过
func(http.Handler) http.Handler 模式,可以构建出清晰、可组合、并发安全的中间件链。本章的 OAuth2.0 认证中间件将基于此模式实现。