Gin浅析

关键结构

Context.png
  • Context贯穿整个HTTP业务当中,为相关业务提供上下文服务

    • WriteResponse是Gin自己定义HTTPResponseWriter接口,除了包含http.ResponseWriter外,额外增加了一些常用的接口。
    • handles包含了使用者自定义的业务和一些中间件(Gin默认会使用日志中间件和崩溃恢复中间件)
    • Cache包含着QueryCaCheFormCache,一个是读url的param参数,一个是读form表单参数
    • Param是使用者定义的url路径参数,例如 /user/:id
    • *Engine绑定Engine
Engine.png
  • EngineGin的引擎,把上下文和路由联合起来

    • RouterGroup实现了一系列给Engine添加路由的方法,方便使用
    • sync.Pool是复用池的概念,目的是减少GC带来的影响,用法是先创建再放入,不能理解为线程池或数据库连接池的概念,因为会随时回收,是编程人员不可控的。
    • treesGin实现快速路由匹配的利器,是基于Trie的算法思想进行路由匹配的,:param*的实现都是由它完成的,有兴趣的同学可以参考下我的上一篇文章字典树-Trie

常用方法

初始化

Default.jpg
  • 初始化Engine

中间件

Use.jpg
  • 将添加的中间件加入到Handles数组里面,形成调用链

创建路由

Get.jpg
  • 创建一个Get方法并放入trees中,用于运行中的匹配

启动服务

Run.jpg
  • 调用http库中的监听函数

调用过程

ServeHttp.jpg
  • Gin自己实现http.Handler接口,从trees中去到注册进去的业务,进行链式调用

中间件

这里我们看到tree中路由对应的是HandlersChain,实际就是[]HandlerFunc,所以一个路由,实际上会对应多个handlers。

首先我们已经把request和responseWriter封装在context里面了,多个handler只要处理好这个context就可以了,所以是可以一个路由拥有多个handler的。

其次这里的handler是怎么来的呢?

每个路由的handler有几个来源,第一个来源是在engine.GET的时候调用增加的。第二个来源是RouterGroup.GET的时候增加的,其实这两种方式都是调用

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("GET", relativePath, handlers)
}

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := group.calculateAbsolutePath(relativePath)
    handlers = group.combineHandlers(handlers)
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
    finalSize := len(group.Handlers) + len(handlers)
    if finalSize >= int(abortIndex) {
        panic("too many handlers")
    }
    mergedHandlers := make(HandlersChain, finalSize)
    copy(mergedHandlers, group.Handlers)
    copy(mergedHandlers[len(group.Handlers):], handlers)
    return mergedHandlers
}

从两个copy的顺序可以看出,group的handler高于自定义的handler。这里自定义的handler可以是多个,比如:

router.GET("/before", MiddleWare(), func(c *gin.Context) {
  request := c.MustGet("request").(string)
  c.JSON(http.StatusOK, gin.H{
    "middile_request": request,
  })
})

func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("before middleware")
        c.Set("request", "clinet_request")
        c.Next()
        fmt.Println("before middleware")
    }
}

这里的/before实际上是带了两个handler。

第三种方法是使用Use增加中间件的方式:

router.Use(MiddleWare())

这里的会把这个中间件(实际上也是一个handler)存放到routerRroup上。所以中间件是属于groupHandlers的。

在请求进来的时候是如何调用的呢?

答案还是在handleHTTPRequest中

func (engine *Engine) handleHTTPRequest(c *Context) {
    ...
        handlers, params, tsr := root.getValue(path, c.Params, unescape)
        if handlers != nil {
            c.handlers = handlers
            c.Params = params
            c.Next()
            c.writermem.WriteHeaderNow()
            return
        }
    ..
}

func (c *Context) Next() {
    c.index++
    for s := int8(len(c.handlers)); c.index < s; c.index++ {
        c.handlers[c.index](c)
    }
}

每个请求进来,匹配好路由之后,会获取这个路由最终combine的handlers,把它放在全局的context中,然后通过调用context.Next()来进行递归调用这个handlers。

想一想,就算是链式调用,GinLogger记录时间是如何做到的?在执行完自己的业务是如何终止计时的呢?

其实GoLang就有这种特性,想一想defer是怎么做的。

是的,就是利用栈的特性:先进后出

链式调用.png
func(c *lightGin.Context) {
        // Start timer
        start := time.Now()
        path := c.Request.URL.Path
        raw := c.Request.URL.RawQuery

        // Process request
        c.Next()

        // Log only when path is not being skipped
        if _, ok := skip[path]; !ok {
            param := LogFormatterParams{
                Request: c.Request,
                isTerm:  isTerm,
                Keys:    c.Keys,
            }

            // Stop timer
            param.TimeStamp = time.Now()
            param.Latency = param.TimeStamp.Sub(start)

            param.ClientIP = c.ClientIP()
            param.Method = c.Request.Method
            param.StatusCode = c.Writer.Status()
            param.ErrorMessage = c.Errors.ByType(lightGin.ErrorTypePrivate).String()

            param.BodySize = c.Writer.Size()

            if raw != "" {
                path = path + "?" + raw
            }

            param.Path = path

            fmt.Fprint(out, formatter(param))
        }
    }

参考&致谢


发表评论

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