131 lines
3.9 KiB
Markdown
131 lines
3.9 KiB
Markdown
|
|
---
|
|||
|
|
tags: [后端, Go, Gin, 中间件, 架构]
|
|||
|
|
create time: 2026-04-27 12:51
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# c.Abort() 终止机制
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
本文档解答中间件文档中的思考题:当中间件 A 调用 `c.Abort()` 后,后续中间件、handler 以及 A 自身代码的执行情况。
|
|||
|
|
|
|||
|
|
## 正文
|
|||
|
|
|
|||
|
|
### 思考题
|
|||
|
|
|
|||
|
|
如果中间件 A 中调用了 `c.Abort()`(不调用 `c.Next()`),中间件 B 和 handler 还会执行吗?那 A 中 `c.Abort()` 之后的代码还会执行吗?
|
|||
|
|
|
|||
|
|
**两个问题的答案都是「不会执行」。**
|
|||
|
|
|
|||
|
|
### 1. 中间件 B 和 handler 还会执行吗?
|
|||
|
|
|
|||
|
|
**不会。** `c.Abort()` 会立即终止整个中间件链。
|
|||
|
|
|
|||
|
|
要理解原因,需要看 `c.Next()` 的内部实现——它按索引遍历 `c.handlers` 切片:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func (c *Context) Next() {
|
|||
|
|
c.index++
|
|||
|
|
for ; c.index < int8(len(c.handlers)); c.index++ {
|
|||
|
|
c.handlers[c.index](c)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`c.Abort()` 内部做了两件事:
|
|||
|
|
|
|||
|
|
1. 设置 `c.IsAborted = true` 标记
|
|||
|
|
2. **将 `c.index` 设置为 handler 链的长度**,使得 `c.Next()` 的 for 循环条件立即不满足
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart LR
|
|||
|
|
subgraph 正常流程
|
|||
|
|
N1["c.index = 0"] --> N2["c.handlers[0] 执行"] --> N3["c.Next() → c.index++"] --> N4["c.handlers[1] 执行"] --> N5["继续..."]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph Abort 流程
|
|||
|
|
A1["c.index = 0"] --> A2["c.handlers[0] 执行"] --> A3["c.Abort()"] --> A4["c.index 设为链长度"] --> A5["c.Next() → for 条件不满足,直接退出"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
style A4 fill:#FF6B6B
|
|||
|
|
style A5 fill:#FFD700
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
所以中间件 B 和 handler **都会被跳过**。
|
|||
|
|
|
|||
|
|
### 2. A 中 c.Abort() 之后的代码还会执行吗?
|
|||
|
|
|
|||
|
|
**不应该执行。** 因为 `c.Abort()` 调用之后紧接着就是 `return`:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func jwtAuth() gin.HandlerFunc {
|
|||
|
|
return func(c *gin.Context) {
|
|||
|
|
token := c.GetHeader("Authorization")
|
|||
|
|
if token == "" {
|
|||
|
|
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "missing token"})
|
|||
|
|
c.Abort()
|
|||
|
|
return // ← 立即返回,后面的代码不会执行
|
|||
|
|
}
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 忘记 return 的后果
|
|||
|
|
|
|||
|
|
如果忘记写 `return`,Go 语法层面 `c.Abort()` 之后的代码**仍然会执行**,但后续再调用 `c.Next()` 时中间件链会被终止。这会导致**后置处理逻辑意外执行**,是典型的 Bug:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 危险写法!
|
|||
|
|
func badMiddleware() gin.HandlerFunc {
|
|||
|
|
return func(c *gin.Context) {
|
|||
|
|
start := time.Now()
|
|||
|
|
|
|||
|
|
if someCondition {
|
|||
|
|
c.Abort()
|
|||
|
|
// 忘记 return → 继续执行下面代码
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
c.Next() // ← 由于 c.index 已被设为链长度,这里不会进入下一个中间件
|
|||
|
|
// 但「下面的」后置代码会执行!
|
|||
|
|
|
|||
|
|
// 这段后置代码在不应该执行的时候被执行了
|
|||
|
|
log.Printf("耗时: %v", time.Since(start))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. AbortWithStatus
|
|||
|
|
|
|||
|
|
Gin 还提供了 `c.AbortWithStatus(code)`,它在调用 `c.Abort()` 的同时写入响应状态码(body 为空),适合预检请求等场景:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func cors() gin.HandlerFunc {
|
|||
|
|
return func(c *gin.Context) {
|
|||
|
|
if c.Request.Method == "OPTIONS" {
|
|||
|
|
c.AbortWithStatus(http.StatusNoContent)
|
|||
|
|
return // 同样需要 return
|
|||
|
|
}
|
|||
|
|
c.Next()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
也有 `c.AbortWithStatusJSON(code, json)`,在 Abort 的同时写入 JSON 响应体。
|
|||
|
|
|
|||
|
|
## 总结
|
|||
|
|
|
|||
|
|
| 调用 `c.Abort()` 后 | 结果 |
|
|||
|
|
|---|---|
|
|||
|
|
| 后续中间件(B、C…) | ❌ 不执行 |
|
|||
|
|
| Handler | ❌ 不执行 |
|
|||
|
|
| 后续中间件链中的 `c.Next()` | ❌ 不会进入下一个 |
|
|||
|
|
| A 中 `c.Abort()` 之后的代码 | ❌ 不应该执行(必须紧跟 `return`) |
|
|||
|
|
|
|||
|
|
**核心规则:`c.Abort()` 之后必须紧跟 `return`。** 这是 Gin 中间件写作的铁律。
|
|||
|
|
|
|||
|
|
## 关联笔记
|
|||
|
|
|
|||
|
|
- [[GIN/3-middleware]] — 中间件完整机制,包含 Abort 的思考题
|
|||
|
|
- [[GIN/4-context-lifecycle]] — Context 的深拷贝原理
|