--- tags: [后端, Go, Gin, 中间件, net/http] create time: 2026-04-27 13:00 --- # Gin vs net/http 中间件模式对比 ## 概述 对比 Gin 中间件与 Go 标准库 `net/http` 装饰器模式的本质区别,分析各自在简洁性和灵活性上的优劣。 ## 正文 ### 1. 类型签名差异 **标准库模式:** `func(http.Handler) http.Handler` - 中间件接收**下一个 handler**,返回**新的 handler** - 本质是**装饰器模式**(Decorator Pattern),层层嵌套 - 是**函数式组合**:`middleware3(middleware2(middleware1(handler)))` **Gin 模式:** `func(*gin.Context)` - 中间件接收 `*gin.Context`,通过 `c.Next()` **主动推进**到下一个 - 本质是**责任链模式**(Chain of Responsibility),串联执行 - 是**命令式链式调用**:`m1 → m2 → m3 → handler` ```mermaid flowchart LR subgraph stdlib["标准库:装饰器嵌套"] S1["middleware3"] --> S2["middleware2"] --> S3["middleware1"] --> S4["handler"] end subgraph gin["Gin:责任链推进"] G1["m1"] --> G2["c.Next()"] --> G3["m2"] --> G4["c.Next()"] --> G5["handler"] end style stdlib fill:#F0F0F0 style gin fill:#F0F0F0 ``` ### 2. 执行控制权 标准库模式中,中间件**完全控制**是否调用下一个 handler,通过闭包嵌套实现: ```go // 标准库:闭包嵌套,控制权在闭包内 func logging(next http.Handler) http.Handler { return func(w http.ResponseWriter, r *http.Request) { start := time.Now() // 前置 next.ServeHTTP(w, r) // 推进(可选择不调用) // 后置 } } // 必须显式嵌套:h := logging(auth(cors(handler))) ``` Gin 模式中,中间件**显式调用** `c.Next()` 推进,写法是线性的: ```go // Gin:线性写法,c.Next() 就是推进 func logging(c *gin.Context) { start := time.Now() // 前置 c.Next() // 推进 // 后置 } // 注册即可:r.Use(logging, auth, cors) ``` ### 3. 各自优缺点 | 维度 | `net/http` 装饰器模式 | Gin 责任链模式 | |------|----------------------|---------------| | **简洁性** | 嵌套深时阅读困难(括号地狱) | 线性注册,一目了然 | | **灵活性** | 高:可以完全跳过 `next`、包装 `ResponseWriter`/`Request` | 中:依赖 `c.Context` 传递状态,`c.Abort()` 中断链 | | **状态传递** | 靠 `context.Context`(类型安全) | 靠 `c.Keys`(`interface{}`,方便但类型不安全) | | **可观测性** | 需要自己包装 `ResponseWriter` 才能读状态码 | `c.Writer.Status()` 直接获取 | | **框架耦合** | 无,纯标准库,可跨框架复用 | 强耦合 Gin 的 `*gin.Context` | | **函数式风格** | 天然支持组合/高阶函数 | 命令式,更像过滤器链 | ### 4. 结论 **Gin 的方案更简单,标准库的方案更灵活。** - **简单性上** Gin 胜出:线性 `Use()` 注册 + `c.Next()` 推进,不用写嵌套闭包,新人上手快。 - **灵活性上** 标准库胜出:你可以用 `http.RoundTripper`、`http.Handler` 包装任意层,甚至写中间件组合子。Gin 被绑定在 `*gin.Context` 上,跨框架复用困难。 **实际建议**:如果在 Gin 生态内,用 Gin 中间件就够了。如果需要写可复用的中间件库(同时支持 Gin、Echo、标准库),应该写标准库风格的 `func(http.Handler) http.Handler`,然后用适配层桥接到各框架。 ## 关联笔记 - [[GIN/3-middleware]] — 中间件完整机制