Gin框架源码分析(1)—— 整体介绍

为什么需要Gin

既然go原生提供了http功能,为啥还需要gin等第三方框架呢?

主要还是原生的http不满足实际业务场景的需求,gin主要提供了以下额外的功能:

  1. 路径参数:go http支持路径完全匹配,和前缀匹配,但不支持路径参数匹配,gin基于redix tree实现路由功能,相比于普通前缀树来说树高度更小,占用内存更小,速度更快
  1. 中间件:传入的 HTTP 请求可以由一系列中间件和最终操作来处理。首先经过通用的中间件,例如Logger,Authorization,GZIP,最后执行实际业务逻辑。还可以在中间件中实现前置,后置处理逻辑
  1. 路由组:gin可以将路径分为不同的组,方便代码开发维护,可以给每个组配置不同的路径前缀。中间件,因而更好地组织路由
  1. 参数处理:gin 可以解析并验证请求的数据,取代手动处理

快速使用

下面是官方的qucikstart:

import (
   "net/http"

   "github.com/gin-gonic/gin"
)

func main() {
   r := gin.Default()
   r.GET( "/ping" , func(c *gin.Context) {
      c.JSON(http.StatusOK, gin.H{
         "message" : "pong" ,
      })
   })
   r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

需要做3件事:

  • new一个gin.Engine对象
  • 注册路由即处理函数
  • 启动服务

创建引擎

r := gin.Default()
func Default() *Engine {
   engine := New()
   engine.Use(Logger(), Recovery())
   return engine
}

继续看New():

func New() *Engine {
   debugPrintWARNINGNew()
   engine := &Engine{
      RouterGroup: RouterGroup{
         Handlers: nil,
         root:     true,
         trees:    make(methodTrees, 0, 9),
      },
      // ...
   }
   engine.RouterGroup.engine = engine
   engine.pool.New = func() any {
      return engine.allocateContext()
   }
   return engine
}

Engine有几个关键字段:

  • RouterGroup:管理中间件,框架默认会添加Logger(), Recovery()这两个中间件
  • trees:每一个 HTTP 方法会有一颗方法树,方法树记录了请求路径和该路径上的处理函数
  • pool:gin.Context的对象池,避免每次处理httq请求时都新生成一个对象

接着看engine.Use(Logger(), Recovery())

添加两个全局中间件:

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
   engine.RouterGroup.Use(middleware...)
   // ...
   return engine
}

添加到group.Handlers中

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
   group.Handlers = append(group.Handlers, middleware...)
   return group.returnObj()
}

其中HandlerFunc类型为:

type HandlerFunc func(*Context)

实际处理请求时没有区分中间件和业务处理函数,其函数签名都为HandlerFunc,

注册路由

入口为:

r.GET( "/ping" , func(c *gin.Context) {
      c.JSON(http.StatusOK, gin.H{
         "message" : "pong" ,
      })
   })

跟进去看:

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
   return group.handle(http.MethodGet, 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()
}

流程为:

  1. 将参数中的相对路径,拼接上group中的路径,组成绝对路径
  1. 将group中的中间件和参数中的handlers组成新的处理器链
  1. 将绝对路径和对应的处理器链,注册到路由树上

    1. 这里和go原生http服务不同,不是不是简单地将路径作为key,处理器函数作为value放到map中,而是用基数树的形式存放,因为需要支持路径参数的功能
    2. 本文不讨论其具体实现,可以将其当做黑盒,功能为路径注册

系统运行

入口为:

r.Run()
func (engine *Engine) Run(addr ...string) (err error) 
   
   address := resolveAddress(addr)
   err = http.ListenAndServe(address, engine.Handler())
   return
}
  • 解析地址:主要是没传时,默认用本地8080端口
  • 将engine作为handler,调用http.ListenAndServe

关于go原生的http.ListenAndServe可以看 Golang http请求源码解读

最终请求会进入到Engine的ServeHTTP方法:

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   c := engine.pool.Get().(*Context)
   c.writermem.reset(w)
   c.Request = req
   c.reset()

   engine.handleHTTPRequest(c)

   engine.pool.Put(c)
}
  • 从pool中获取Context
  • 重置Context
  • 执行业务逻辑
  • 归还Context到pool中

handleHTTPRequest:

func (engine *Engine) handleHTTPRequest(c *Context) {
   httpMethod := c.Request.Method
   rPath := c.Request.URL.Path
  
   // Find root of the tree for the given HTTP method
   t := engine.trees
   for i, tl := 0, len(t); i < tl; i++ {
      if t[i].method != httpMethod {
         continue
      }
      root := t[i].root
      // Find route in tree
      value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
      if value.params != nil {
         c.Params = *value.params
      }
      if value.handlers != nil {
         c.handlers = value.handlers
         c.fullPath = value.fullPath
         c.Next()
         c.writermem.WriteHeaderNow()
         return
      }
     
    }
}
  • 从engine.trees中找到当前请求方法的tree
  • 从tree中根据参数,路径获取处理器链
  • 调用c.Next(),执行处理器链

你可能感兴趣的:(gin,golang)