想象你在一家餐厅点餐:
- 中间件模式 = 多位服务员接力传递:迎宾员 → 点单员 → 厨师 → 传菜员
- 嵌入式委托 = 一位全能主厨:亲自迎客、点单、烹饪,只在必要时呼叫助手
一、中间件堆叠:层层包裹
典型写法是将处理器像俄罗斯套娃一样层层包裹:
// 日志中间件:记录每笔“订单”
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("📝 记录请求:", r.URL.Path)
next.ServeHTTP(w, r) // 传递给下一位
})
}
// 特殊路由中间件:拦截“隐藏菜单”
func special(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/secret-menu" {
w.Write([]byte("🍣 今日隐藏菜单:三文鱼刺身"))
return // 拦截请求,不再传递
}
next.ServeHTTP(w, r)
})
}
// 启动服务:套娃式组装
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("🍔 普通菜单:汉堡套餐"))
})
// 堆叠顺序:越靠外的中间件越先执行
handler := logging(special(mux))
http.ListenAndServe(":8080", handler)
痛点:中间件超过 5 层后,调用栈变得像迷宫,调试时需逐层追踪 next.ServeHTTP 的流向。
二、嵌入式委托:一位“全能主厨”
通过结构体嵌入 http.ServeMux,重写 ServeHTTP 实现集中控制:
// CustomMux = 全能主厨
type CustomMux struct {
*http.ServeMux // 嵌入标准路由器作为“基础技能”
}
// 主厨亲自处理每笔订单
func (cm *CustomMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 1️⃣ 先记录订单
fmt.Println("👨🍳 主厨接单:", r.URL.Path)
// 2️⃣ 检查是否要点“隐藏菜单”
if r.URL.Path == "/secret-menu" {
w.Write([]byte("🍣 今日隐藏菜单:三文鱼刺身"))
return // 亲自完成,不呼叫助手
}
// 3️⃣ 普通订单交给基础技能处理
cm.ServeMux.ServeHTTP(w, r)
}
// 启动:简洁明了
mux := &CustomMux{ServeMux: http.NewServeMux()}
mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("🍔 普通菜单:汉堡套餐"))
})
http.ListenAndServe(":8080", mux)
优势:请求流一目了然,无需在多层函数间跳跃。
三、混合策略:主厨+专业助手
实际项目中可灵活组合:
// 主厨专注“隐藏菜单”逻辑(业务核心)
type RouteMux struct{ *http.ServeMux }
func (rm *RouteMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/secret-menu" {
w.Write([]byte("🍣 隐藏菜单"))
return
}
rm.ServeMux.ServeHTTP(w, r)
}
// 专业助手处理横切关注点(日志、认证等)
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !isValidToken(r) {
http.Error(w, "❌ 未授权", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// 组装:主厨处理业务 + 助手处理通用逻辑
mux := &RouteMux{ServeMux: http.NewServeMux()}
mux.HandleFunc("/", homeHandler)
handler := authMiddleware(logging(mux)) // 仅2层中间件
四、何时选择哪种模式?
| 场景 | 推荐模式 | 理由 |
|---|---|---|
| 路由逻辑定制(如特殊路径) | 嵌入式委托 | 逻辑集中,避免中间件堆叠 |
| 横切关注点(日志/认证) | 中间件 | 可复用、可插拔,符合关注点分离 |
| 小型服务(<5个路由) | 嵌入式委托 | 简单直接,无过度设计 |
| 大型微服务 | 混合模式 | 核心路由用委托,通用能力用中间件 |
核心思想:让请求流像阅读故事一样线性自然,而非在层层嵌套中迷失方向。嵌入式委托是“少即是多”的实践——当简单方案足够时,不必过早引入复杂抽象。
