自定义中间件:耗时、请求 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 跨域。

笔记加载中…