Gin的Next()方法使用

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

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

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

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

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

洋葱模型

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

不使用 Next()

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

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

middleware.RequestID的实现如下

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

middleware.Log的实现如下

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

app.Index的实现如下

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

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

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()后怎么执行呢
如下为两个中间加入后的实际代码

func RequestID(ctx *gin.Context) {
    log.Println("request start")
    ctx.Set("request_id", Uid())
    ctx.Next()
    log.Println("request end")
}
func Log(ctx *gin.Context) {
    log.Println("log start")
    ctx.Next()
    log.Println("log end")
}

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

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()方法的实现非常简单

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

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

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的中间件

    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")
    }

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

    route := gin.Default()
    route.Use(middleware.RequestID)
    route.Use(middleware.Log)
    route.Use(middleware.ExecTime)
    route.GET("/", app.Index)

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

    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

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

  2. 程序异常的捕捉和报警

  3. 链路跟踪

发表评论

电子邮件地址不会被公开。 必填项已用*标注

评论审核已启用。您的评论可能需要一段时间后才能被显示。