自定义中间件:耗时、请求 ID 与错误恢复
本章解决什么问题:内置中间件不够用时需要自己写 gin.HandlerFunc。本章用三个高频需求示范——记录耗时、注入请求 ID、自定义 panic 恢复,并总结自定义中间件的通用骨架。
通用骨架
自定义中间件 = 一个工厂函数返回 gin.HandlerFunc,闭包捕获自己的配置:
func myMiddleware(options ...) gin.HandlerFunc {
return func(c *gin.Context) {
// 请求前:可读请求、写 header、Set 值
c.Next()
// 请求后:可读状态码、收尾
}
}
注册位置有三档:引擎级 r.Use(mw)、组级 group.Use(mw) 或 r.Group("/x", mw)、路由级 r.GET("/x", mw, handler)。
记录耗时
func Timing() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
latency := time.Since(start)
log.Printf("[timing] %s %s -> %d (%s)",
c.Request.Method, c.FullPath(), c.Writer.Status(), latency)
}
}
时间戳取在 Next 之前,Next 返回后再算耗时,覆盖的就是“整条链”的时间;c.Writer.Status() 给出最终状态码。
注入请求 ID
func RequestID() gin.HandlerFunc {
return func(c *gin.Context) {
id := c.GetHeader("X-Request-Id") // 优先透传调用方带来的 ID
if id == "" {
id = randomHex(16) // crypto/rand 生成,示意函数见正文
}
c.Writer.Header().Set("X-Request-Id", id)
c.Set("request_id", id)
c.Next()
}
}
c.GetHeader 读请求头,c.Writer.Header().Set 在写响应前设置响应头;把 id 存入 context 后,handler 用 c.GetString("request_id") 即可取用。randomHex 可用 crypto/rand 读字节后转十六进制实现,网上与官方文档均有标准写法可参考。
自定义错误恢复
内置 Recovery 返回空 500 响应;想让客户端拿到统一 JSON 错误体,可自写:
func Recovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic: %v\n%s", r, debug.Stack())
c.AbortWithStatusJSON(500, gin.H{"error": "internal server error"})
}
}()
c.Next()
}
}
defer + recover 必须在 Next 之前注册好,链上任何 handler 抛出的 panic 都会被接住;debug.Stack() 来自 runtime/debug 包。
关键点
- 中间件要“可配置”就写成工厂函数,返回 gin.HandlerFunc。
- 顺序敏感:耗时类把计时放在 Next 前后,恢复类把 defer 放在 Next 之前。
- 写响应头尽量在 Next 返回后进行,避免过早定型响应影响后续处理。
- 自写恢复中间件只应处理业务 panic,堆栈与日志务必记录完整(debug.Stack)。
小结
三个示例覆盖中间件三大用法:观察(耗时)、注入(请求 ID)、兜底(恢复),套路一致——Next 前准备、Next 后收尾。下一章把原理用于真实横切需求:CORS 跨域。