中间件 vs 嵌入式委托:Go HTTP 处理的两种哲学

想象你在一家餐厅点餐:

  • 中间件模式 = 多位服务员接力传递:迎宾员 → 点单员 → 厨师 → 传菜员
  • 嵌入式委托 = 一位全能主厨:亲自迎客、点单、烹饪,只在必要时呼叫助手

一、中间件堆叠:层层包裹

典型写法是将处理器像俄罗斯套娃一样层层包裹:

// 日志中间件:记录每笔“订单”
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个路由)嵌入式委托简单直接,无过度设计
大型微服务混合模式核心路由用委托,通用能力用中间件

核心思想:让请求流像阅读故事一样线性自然,而非在层层嵌套中迷失方向。嵌入式委托是“少即是多”的实践——当简单方案足够时,不必过早引入复杂抽象。

0
0
0
0
评论
未登录
暂无评论