Gin框架使用ServerTiming中间件

Server Timing是HTTP的新特性,可更加直观地在客户端显示服务器处理请求的耗时。在Chrome 65及以上版本中,其效果如下:
Server Timing in Chrome
go-server-timing是Go语言实现的HTTP中间件,可以方便地在HTTP服务中启用Server Timing,其官方示例在这里
官方示例是部署在Go语言自带HTTP服务器(net/http包)中,下面代码则是在Web框架Gin中使用该中间件。

需要注意的是,Gin中间件函数中的c.Next()之后就不能再修改HTTP Header了,所以需要先把servertiming.Header储存在gin.Context中,在Handler中读取并使用该对象,最后在输出前写入HTTP Header即可。

完整代码:

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
    "github.com/mitchellh/go-server-timing"
    "sync"
    "fmt"
    "time"
    "math/rand"
)

func main() {
    router := gin.Default()

    router.Use(func(c *gin.Context) {
        var serverTimer servertiming.Header
        req := c.Request.WithContext(servertiming.NewContext(c.Request.Context(), &serverTimer))
        c.Request = req
        c.Set("serverTimer", &serverTimer)
        c.Next()
    })

    router.GET("/timing", func(c *gin.Context) {
        timing := servertiming.FromContext(c.Request.Context())
        var wg sync.WaitGroup
        for i := 1; i <= 5; i++ {
            wg.Add(1)
            name := fmt.Sprintf("service-%d", i)
            go func(name string) {
                defer wg.Done()
                defer timing.NewMetric(name).Start().Stop()
                time.Sleep(random(25, 75))
            }(name)
        }

        m := timing.NewMetric("sql").WithDesc("SQL Query").Start()
        time.Sleep(random(20, 50))
        m.Stop()

        wg.Wait()

        if header, ok := c.Get("serverTimer"); ok {
            c.Header(servertiming.HeaderKey, header.(*servertiming.Header).String())
        }

        c.String(http.StatusOK, "Done. Check your browser inspector timing details.")
    })

    router.Run(":8080")
}

func random(min, max int) time.Duration {
    return (time.Duration(rand.Intn(max-min) + min)) * time.Millisecond
}

运行代码后在浏览器中打开页面,可以看到Response Headers中的Server-Timing字段:

Server-Timing: sql;desc="SQL Query";dur=45.5583,service-2;dur=65.1083,service-3;dur=31.5139,service-4;dur=25.5025,service-5;dur=69.6097,service-1;dur=37.0238