中间件模式:构建灵活的 Web 应用
在 Web 开发中,有很多横切关注点(cross-cutting concerns)需要在每个请求中处理:日志记录、身份验证、权限检查、请求限流、CORS 处理等等。如果把这些逻辑都写在每个 handler 里,代码会变得冗余且难以维护。
中间件(Middleware)模式就是为了解决这个问题而生的。它允许我们将这些通用逻辑抽取出来,形成可复用的组件,像管道一样串联起来处理请求。
什么是中间件?
中间件本质上是一个函数,它:
- 接收一个
http.Handler作为输入 - 返回一个新的
http.Handler - 在调用下一个 handler 之前或之后执行一些逻辑
type Middleware func(http.Handler) http.Handler
基础中间件实现
让我们从最简单的日志中间件开始:
package main
import (
"log"
"net/http"
"time"
)
// LoggingMiddleware 记录每个请求的处理时间
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 调用下一个 handler
next.ServeHTTP(w, r)
// 记录日志
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
// 使用中间件包装 mux
handler := LoggingMiddleware(mux)
http.ListenAndServe(":8080", handler)
}
这个中间件在请求处理前记录开始时间,在请求处理后计算耗时并打印日志。
链式中间件
实际项目中,我们通常需要多个中间件。手动嵌套会变得很丑陋:
// 丑陋的方式
handler := Middleware1(Middleware2(Middleware3(mux)))
让我们写一个辅助函数来简化:
// Chain 将多个中间件串联起来
func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}
// 使用方式
handler := Chain(mux,
LoggingMiddleware,
RecoveryMiddleware,
CORSMiddleware,
)
注意中间件的执行顺序:先注册的中间件在最外层,后注册的在最内层。
常用中间件实现
1. 恢复中间件(Panic Recovery)
防止 panic 导致整个服务崩溃:
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 recovered: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
2. 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 == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
3. 请求 ID 中间件
为每个请求生成唯一 ID,方便追踪:
import "github.com/google/uuid"
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 = uuid.New().String()
}
// 将 requestID 添加到响应头
w.Header().Set("X-Request-ID", requestID)
// 将 requestID 添加到 context
ctx := context.WithValue(r.Context(), "requestID", requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
4. 身份验证中间件
检查 JWT Token:
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, "Unauthorized", http.StatusUnauthorized)
return
}
// 验证 token
claims, err := validateToken(token)
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
// 将用户信息添加到 context
ctx := context.WithValue(r.Context(), "user", claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
5. 限流中间件
控制请求频率:
import "golang.org/x/time/rate"
func RateLimitMiddleware(limit rate.Limit, burst int) Middleware {
limiter := rate.NewLimiter(limit, burst)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}
// 使用:每秒最多 10 个请求,突发最多 20 个
handler := Chain(mux, RateLimitMiddleware(10, 20))
带参数的中间件
有时候中间件需要配置参数,可以使用闭包:
// TimeoutMiddleware 设置请求超时
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()
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// 使用
handler := Chain(mux, TimeoutMiddleware(30*time.Second))
路由级别的中间件
有时候我们只想在某些路由上应用中间件:
func main() {
mux := http.NewServeMux()
// 公开路由
mux.HandleFunc("/login", loginHandler)
mux.HandleFunc("/register", registerHandler)
// 受保护的路由
protectedMux := http.NewServeMux()
protectedMux.HandleFunc("/profile", profileHandler)
protectedMux.HandleFunc("/settings", settingsHandler)
// 只给受保护的路由添加认证中间件
protectedHandler := AuthMiddleware(protectedMux)
// 将所有路由组合到主 mux
mainMux := http.NewServeMux()
mainMux.Handle("/", mux)
mainMux.Handle("/api/", protectedHandler)
// 全局中间件
handler := Chain(mainMux,
LoggingMiddleware,
RecoveryMiddleware,
)
http.ListenAndServe(":8080", handler)
}
实战:构建完整的中间件栈
package main
import (
"context"
"log"
"net/http"
"time"
)
// ContextKey 自定义 context key 类型
type ContextKey string
const (
RequestIDKey ContextKey = "requestID"
UserIDKey ContextKey = "userID"
)
// Middleware 中间件类型定义
type Middleware func(http.Handler) http.Handler
// Chain 串联中间件
func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}
// LoggingMiddleware 日志中间件
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
requestID, _ := r.Context().Value(RequestIDKey).(string)
next.ServeHTTP(w, r)
log.Printf("[%s] %s %s %v",
requestID,
r.Method,
r.URL.Path,
time.Since(start),
)
})
}
// RecoveryMiddleware 恢复中间件
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", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
// RequestIDMiddleware 请求 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 = generateID()
}
w.Header().Set("X-Request-ID", requestID)
ctx := context.WithValue(r.Context(), RequestIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// AuthMiddleware 认证中间件
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, "Unauthorized", http.StatusUnauthorized)
return
}
// 简化验证
userID := validateToken(token)
if userID == "" {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), UserIDKey, userID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// CORSMiddleware 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")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
// 公开 API
mux.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"status":"ok"}`))
})
// 受保护的 API
apiMux := http.NewServeMux()
apiMux.HandleFunc("/api/users", usersHandler)
apiMux.HandleFunc("/api/profile", profileHandler)
// 为受保护的路由添加认证中间件
protectedAPI := Chain(apiMux, AuthMiddleware)
// 组合所有路由
mainMux := http.NewServeMux()
mainMux.Handle("/", mux)
mainMux.Handle("/api/", protectedAPI)
// 应用全局中间件
handler := Chain(mainMux,
RecoveryMiddleware,
RequestIDMiddleware,
LoggingMiddleware,
CORSMiddleware,
)
log.Println("Server starting on :8080")
http.ListenAndServe(":8080", handler)
}
func usersHandler(w http.ResponseWriter, r *http.Request) {
userID := r.Context().Value(UserIDKey).(string)
w.Write([]byte("Users for: " + userID))
}
func profileHandler(w http.ResponseWriter, r *http.Request) {
userID := r.Context().Value(UserIDKey).(string)
w.Write([]byte("Profile for: " + userID))
}
func generateID() string {
return time.Now().Format("20060102150405")
}
func validateToken(token string) string {
// 简化实现
if token == "valid-token" {
return "user123"
}
return ""
}
总结
中间件模式是 Go Web 开发的核心模式,它让我们能够:
- 分离关注点:将通用逻辑从业务逻辑中分离
- 代码复用:一次编写,多处使用
- 灵活组合:按需添加和配置中间件
- 易于测试:每个中间件可以独立测试
记住几个最佳实践:
- 中间件应该只做一件事
- 注意中间件的执行顺序
- 使用 context 传递请求级别的数据
- 错误处理要优雅,不要让 panic 传播
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。