3.4 KiB
3.4 KiB
tags, create time
| tags | 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
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,通过闭包嵌套实现:
// 标准库:闭包嵌套,控制权在闭包内
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() 推进,写法是线性的:
// 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 — 中间件完整机制