理解 Context 的生命周期,有助于更好地组织代码和处理请求。
一个请求的完整流程:
请求到达
↓
从 sync.Pool 获取 Context
↓
重置 Context 状态
↓
执行中间件链(前半部分)
↓
执行处理器
↓
执行中间件链(后半部分)
↓
返回响应
↓
Context 放回 sync.Pool
Gin 使用 sync.Pool 复用 Context:
type Engine struct {
RouterGroup
pool sync.Pool
// ...
}
func New() *Engine {
engine := &Engine{
// ...
}
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
func (engine *Engine) allocateContext() *Context {
return &Context{engine: engine}
}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.Writer.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
type Context struct {
handlers HandlersChain
index int8
}
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
func (c *Context) Abort() {
c.index = abortIndex
}
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.New()
r.Use(func(c *gin.Context) {
fmt.Println("1. 中间件A - 前")
c.Next()
fmt.Println("1. 中间件A - 后")
})
r.Use(func(c *gin.Context) {
fmt.Println("2. 中间件B - 前")
c.Next()
fmt.Println("2. 中间件B - 后")
})
r.Use(func(c *gin.Context) {
fmt.Println("3. 中间件C - 前")
c.Next()
fmt.Println("3. 中间件C - 后")
})
r.GET("/test", func(c *gin.Context) {
fmt.Println("4. 处理器执行")
c.String(http.StatusOK, "OK")
})
r.Run(":8080")
}
输出顺序:
1. 中间件A - 前
2. 中间件B - 前
3. 中间件C - 前
4. 处理器执行
3. 中间件C - 后
2. 中间件B - 后
1. 中间件A - 后
r.Use(func(c *gin.Context) {
fmt.Println("中间件1 - 前")
c.Next()
fmt.Println("中间件1 - 后")
})
r.Use(func(c *gin.Context) {
fmt.Println("中间件2 - 前")
c.Abort()
fmt.Println("中间件2 - 后")
})
r.Use(func(c *gin.Context) {
fmt.Println("中间件3 - 前")
c.Next()
fmt.Println("中间件3 - 后")
})
r.GET("/abort", func(c *gin.Context) {
fmt.Println("处理器")
c.String(200, "OK")
})
输出:
中间件1 - 前
中间件2 - 前
中间件2 - 后
中间件1 - 后
Abort() 后,后续的中间件和处理器都不会执行。
r.GET("/response", func(c *gin.Context) {
c.Writer.Write([]byte("第一部分\n"))
c.Writer.Flush()
time.Sleep(time.Second)
c.Writer.Write([]byte("第二部分\n"))
c.Writer.Flush()
})
响应可以在处理器执行过程中逐步写入,不一定要等到最后。
每次请求结束后,Context 会被重置:
func (c *Context) reset() {
c.Writer = &responseWriter{}
c.Params = c.Params[0:0]
c.handlers = nil
c.index = -1
c.fullPath = ""
c.Keys = nil
c.Errors = c.Errors[0:0]
c.Accepted = nil
c.queryCache = nil
c.formCache = nil
}
可以在不同阶段执行代码:
r.Use(func(c *gin.Context) {
start := time.Now()
fmt.Println("请求开始")
defer func() {
if err := recover(); err != nil {
fmt.Println("发生 panic:", err)
}
fmt.Println("请求结束, 耗时:", time.Since(start))
}()
c.Next()
})
func RequestLifecycle() gin.HandlerFunc {
return func(c *gin.Context) {
requestID := uuid.New().String()
c.Set("requestID", requestID)
start := time.Now()
path := c.Request.URL.Path
log.Printf("[%s] 请求开始: %s %s", requestID, c.Request.Method, path)
defer func() {
if err := recover(); err != nil {
log.Printf("[%s] Panic: %v", requestID, err)
c.AbortWithStatusJSON(500, gin.H{"error": "服务器错误"})
}
duration := time.Since(start)
status := c.Writer.Status()
log.Printf("[%s] 请求结束: %s %s - %d - %v",
requestID,
c.Request.Method,
path,
status,
duration,
)
metrics.RecordRequest(path, c.Request.Method, status, duration)
}()
c.Next()
}
}
r.Use(RequestLifecycle())
Context 的生命周期从请求到达开始,到响应返回结束。理解中间件链的执行顺序和 Next()、Abort() 的作用,是掌握 Gin 的关键。使用 defer 可以方便地在请求结束时执行清理代码。Context 的复用机制提高了性能,但也意味着不要在请求结束后持有 Context 的引用。