90 lines
3.4 KiB
Markdown
90 lines
3.4 KiB
Markdown
|
|
---
|
|||
|
|
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]] — 中间件完整机制
|