柠芒技术博客

gin的Next()方法使用

源代码/数据集已上传到 Github - posts

使用gin这个web框架开发http服务,少不了使用中间件。

在调用gin.Default()方法的时候就会自动使用LoggerRecovery()两个中间件。

仔细查看两个中间件的实现,你会发现,两个中间件都有c.Next()这个方法的调用。

很多人把Next放在中间的最后一行,然后发现加不加next似乎并没有什么区别。所以,它到底是怎么用的?

来自SF的引用文章:goalng框架Gin中间件的c.Next()有什么作用?

洋葱模型

上图可以说是Next()方法的一个形象化的解释

不使用 Next()

假设我们去编写这样的一段代码

1
2
3
4
route := gin.Default()
route.Use(middleware.RequestID)
route.Use(middleware.Log)
route.GET("/", app.Index)

middleware.RequestID的实现如下

1
2
3
4
5
func RequestID(ctx *gin.Context) {
log.Println("request start")
ctx.Set("request_id", Uid())
log.Println("request end")
}

middleware.Log的实现如下

1
2
3
4
func Log(ctx *gin.Context) {
log.Println("log start")
log.Println("log end")
}

app.Index的实现如下

1
2
3
4
func Index(ctx *gin.Context) {
log.Println(ctx.Get("request_id"))
ctx.JSON(http.StatusOK, "ok")
}

请求后可以在日志中信息中获取到

1
2
3
4
5
2020/02/09 23:53:45 request start
2020/02/09 23:53:45 request end
2020/02/09 23:53:45 log start
2020/02/09 23:53:45 log end
2020/02/09 23:53:45 18c6da3b-3e8c-4925-a0ec-76382eb15a10 true

日志显示,程序按照代码中定义的顺序request => log => index 执行了。

使用 Next()

那加入了Next()后怎么执行呢
如下为两个中间加入后的实际代码

1
2
3
4
5
6
func RequestID(ctx *gin.Context) {
log.Println("request start")
ctx.Set("request_id", Uid())
ctx.Next()
log.Println("request end")
}
1
2
3
4
5
func Log(ctx *gin.Context) {
log.Println("log start")
ctx.Next()
log.Println("log end")
}

请求后,从日志中可以得知如下信息

1
2
3
4
5
2020/02/09 23:58:45 request start
2020/02/09 23:58:45 log start
2020/02/09 23:58:45 ecffea3b-0e46-4ce0-b55f-f0bceef07e92 true
2020/02/09 23:58:45 log end
2020/02/09 23:58:45 request end

程序先执行到 request start,遇到Next,就去执行log start,又遇到Next。去执行app.Index,返回执行log end,再是request end

出现了一种先进后出的情况。而这个就是上面图片上画的洋葱模型。

分析Next()的实现

Next()方法的实现非常简单

1
2
route.Use(middleware.RequestID)
route.Use(middleware.Log)

代码入口加载中间件就是往c.handlers中append这个方法。

1
2
3
4
5
6
7
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}

而每一次调用Next都会调用当前index的下一个索引位的方法,开始自增循环,直到index已经比所有的中间件数量还要大了,就会开始执行业务代码。当业务代码执行完后,就会一层一层的往外执行每个中间件未执行完的代码。

洋葱模式在实际业务中如何使用?

  1. 计算业务程序实际耗时
    我们来写一个ExecTime的中间件
1
2
3
4
5
6
7
func ExecTime(ctx *gin.Context) {
log.Println("exec_time start")
startTime := time.Now()
ctx.Next()
log.Println(time.Now().Sub(startTime).Seconds())
log.Println("exec_time end")
}

放在离路由开始最近的位置

1
2
3
4
5
route := gin.Default()
route.Use(middleware.RequestID)
route.Use(middleware.Log)
route.Use(middleware.ExecTime)
route.GET("/", app.Index)

请求后,从日志中可以得知如下信息

1
2
3
4
5
6
7
8
2020/02/10 00:06:51 request start
2020/02/10 00:06:51 log start
2020/02/10 00:06:51 exec_time start
2020/02/10 00:06:51 7b943cb5-d2f7-4e0e-ab0f-1b70cd800a8f true
2020/02/10 00:06:51 0.000198358
2020/02/10 00:06:51 exec_time end
2020/02/10 00:06:51 log end
2020/02/10 00:06:51 request end

执行耗时在业务代码执行完后就输出了

  1. 程序异常的捕捉和报警
  2. 链路跟踪

edit this page last updated at 2024-04-22

Big Image