Context 生命周期

理解 Context 的生命周期,有助于更好地组织代码和处理请求。

请求处理流程

一个请求的完整流程:

请求到达
    ↓
从 sync.Pool 获取 Context
    ↓
重置 Context 状态
    ↓
执行中间件链(前半部分)
    ↓
执行处理器
    ↓
执行中间件链(后半部分)
    ↓
返回响应
    ↓
Context 放回 sync.Pool

Context 的创建

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 - 后

Abort 的影响

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 重置

每次请求结束后,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 的引用。