您的位置 首页 golang

Gin浅析


关键结构

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

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

常用方法

初始化

Gin浅析
Default.jpg
  • 初始化Engine

中间件

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

创建路由

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

启动服务

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

调用过程

Gin浅析
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是怎么做的。

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

Gin浅析
链式调用.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))        }    }

参考&致谢


文章来源:智云一二三科技

文章标题:Gin浅析

文章地址:https://www.zhihuclub.com/7005.shtml

关于作者: 智云科技

热门文章

评论已关闭

1条评论

网站地图