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 的深拷贝原理
|