vault backup: 2026-04-27 22:55:05
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
---
|
||||
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 的深拷贝原理
|
||||
Reference in New Issue
Block a user