c.Abort() 之后,代码会立刻返回吗?后面的 handler、外层中间件的后半段会不会执行?AbortWithStatus、AbortWithStatusJSON、AbortWithError 各写了什么?这篇从 Next 的循环入手,用打印顺序和响应把这些行为一条条核对。
context.go 的 Next(Gin v1.12.0):
188 | func (c *Context) Next() {
189 | c.index++
190 | for c.index < safeInt8(len(c.handlers)) {
191 | if c.handlers[c.index] != nil {
192 | c.handlers[c.index](c)
193 | }
194 | c.index++
195 | }
196 | }
context.go 的 Abort(Gin v1.12.0):
207 | func (c *Context) Abort() {
208 | c.index = abortIndex
209 | }
context.go 的 IsAborted(Gin v1.12.0):
199 | func (c *Context) IsAborted() bool {
200 | return c.index >= abortIndex
201 | }
context.go: 57 | const abortIndex int8 = math.MaxInt8 >> 1Next 就是一个循环,条件是当前下标小于 handler 个数;Abort 把下标改成 63,循环自然结束。它没有 panic、没有 return,没有任何控制流跳转。
/x
[M1 前]
[M2 前]
[handler] 执行
[M2 后]
[M1 后] IsAborted = false
→ 状态 200 正文 "ok"
/x?deny=1
[M1 前]
[M2 前]
[M2] Abort() 之后这一行仍然执行
[M2 后]
[M1 后] IsAborted = true
→ 状态 200 正文 ""调用 c.Abort() 之后,当前函数后面的代码照常执行,所以通常写成 c.Abort(); return。外层中间件的 c.Next() 之后的代码也照常执行,c.IsAborted() 在那里是 true,日志中间件可以用它来记录“被拦截”。
Abort() → 状态 200 正文 "" Content-Type=""
AbortWithStatus(401) → 状态 401 正文 "" Content-Type=""
AbortWithStatusJSON(403, ...) → 状态 403 正文 "{\"err\":\"forbidden\"}" Content-Type="application/json; charset=utf-8"
AbortWithError(400, err) → 状态 400 正文 "" Content-Type=""
Abort() 后 c.JSON(418, ...) → 状态 418 正文 "{\"t\":1}" Content-Type="application/json; charset=utf-8"
AbortWithStatus(401) 后 c.JSON(200, ...) → 状态 401 正文 "{\"t\":1}" Content-Type="application/json; charset=utf-8"AbortWithStatus 会立刻 WriteHeaderNow,所以之后 c.JSON(200, …) 改不了状态,但仍会往已发出的响应里写正文,客户端会收到“状态 401、正文是 JSON”的怪响应。只想改状态再写正文的话,用 c.Abort() 加 c.JSON(…),或者直接 AbortWithStatusJSON。
一条路由挂 61 个 handler → 注册成功
一条路由挂 62 个 handler → 注册成功
一条路由挂 63 个 handler → panic: too many handlers
一条路由挂 64 个 handler → panic: too many handlers下标是 int8,63 被留给 abortIndex,所以一条路由连全局、Group、自己的 handler 在内最多 62 个,注册第 63 个时 panic。常见项目里远远用不到,但自动生成或循环拼接 handler 时要注意。