0
点赞
收藏
分享

微信扫一扫

gin框架

陬者 2022-02-07 阅读 75

简单使用

import (
	"net/http"

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

func main() {

	r := gin.Default()

	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Hello world")
	})

	r.POST("/ping", func(c *gin.Context) {
		c.String(http.StatusOK, "pong")
	})

	r.Run("0.0.0:8910")
}

通过几行代码,我们就可以创建一个http server,主要有以下三步

1.初始化引擎

2.注册路由

3.启动http server

初始化引擎

1. engine := New()主要是构造engine对象,因为我们用的默认配置,所以如下

 有几个比较需要注意的字段

1).routerGroup,是管理路由的结构体,内嵌到engine中去了,我们注册路由和注册中间件都和它有关系,后面讲路由的时候再说

2).pool 然后是一个对象池的注册,context是从对象池里创建/复用的。

3)trees一个slice,存的就是path和handler的映射,gin为每一个方法(method,get,post),用一个基树来存储path和handlers

2.后面就是把2个中间件函数注册到router里

 从代码里可以看到 RouterGroup实现了IRoutes的方法。另外中间件也是个handlerFunc函数,注册的话,也是追加到RouterGroup的handlers中。

注册路由

注册路由最终调用的是handle函数,即 engine.Get -> engine.routergroup.Get -> routergroup.handle

handle里主要做的事情就是

1.根据basePath和relativePath组成绝对路径

2.把routerGroup的handlers合并(已经注册过全局的中间件的和这次新注册的)

3.将当前method和absolutePath, handlers 绑定,比如根据 post  /ping 找到handlers去执行。

 这步调用的是addRoute方法

 这里需要先介绍下路由和path映射的数据结构,基数树(Radix Tree)又称为PAT位树(Patricia Trie or crit bit tree),是一种更节省空间的前缀树(Trie Tree)。

还记得上面初始化过程中,trees那个字段吗?methodTree就是那个结构体,method就是方法,http中的get,post,delete等,root就是基树的根节点。所以说,gin为每一个http method创建一个基树来存储router。下面看一下node的结构

type node struct {
   path      string //
   indices   string
   wildChild bool
   nType     nodeType
   priority  uint32
   children  []*node // child nodes, at most 1 :param style node at the end of the array
   handlers  HandlersChain //
   fullPath  string
}

对应如下图

 也就是说当我们调用addRoute方法,就是往对应方法的基树中插入节点

Radix Tree可以被认为是一棵简洁版的前缀树。我们注册路由的过程就是构造前缀树的过程,具有公共前缀的节点也共享一个公共父节点。假设我们现在注册有以下路由信息:

r := gin.Default()

r.GET("/", func1)
r.GET("/search/", func2)
r.GET("/support/", func3)
r.GET("/blog/", func4)
r.GET("/blog/:post/", func5)
r.GET("/about-us/", func6)
r.GET("/about-us/team/", func7)
r.GET("/contact/", func8)

那么我们会得到一个GET方法对应的路由树,具体结构如下:

Priority   Path             Handle
9          \                *<1>
3          ├s               nil
2          |├earch\         *<2>
1          |└upport\        *<3>
2          ├blog\           *<4>
1          |    └:post      nil
1          |         └\     *<5>
2          ├about-us\       *<6>
1          |        └team\  *<7>
1          └contact\        *<8>

上面最右边那一列每个*<数字>表示Handle处理函数的内存地址(一个指针)。从根节点遍历到叶子节点我们就能得到完整的路由表。

为了获得更好的可伸缩性,每个树级别上的子节点都按Priority(优先级)排序,其中优先级(最左列)就是在子节点(子节点、子子节点等等)中注册的句柄的数量。这样做有两个好处:

  1. 首先优先匹配被大多数路由路径包含的节点。这样可以让尽可能多的路由快速被定位。

  2. 类似于成本补偿。最长的路径可以被优先匹配,补偿体现在最长的路径需要花费更长的时间来定位,如果最长路径的节点能被优先匹配(即每次拿子节点都命中),那么路由匹配所花的时间不一定比短路径的路由长。下面展示了节点(每个-可以看做一个节点)匹配的路径:从左到右,从上到下

addRoute的逻辑大致如下:

先找到有无对应方法的root,这个地方trees用的slice []methodTree 而不是map map[method]root,所以每次查找都需要遍历[]methodTree,再根据里面的method字段来判断是否等于当前的method(这个地方用slice,答案是在短长度的情况下,slice的速度会比map快很多)

如果没有那么新建树,并且当前方法和 “/”就是根节点

1.空树直接插入当前节点

2.找到最长公共前缀

3.

// 分裂边缘(此处分裂的是当前树节点)
		// 例如一开始path是search,新加入support,s是他们通用的最长前缀部分
		// 那么会将s拿出来作为parent节点,增加earch和upport作为child节点
		// 将新来的节点插入新的parent节点作为子节点

 4.insertChild函数是根据path本身进行分割,将/分开的部分分别作为节点保存,形成一棵树结构。参数匹配中的:*的区别是,前者是匹配一个字段而后者是匹配后面所有的路径。

启动server

简单逻辑就是主协程listen,然后一个for 死循环里面,accept,然后没有一个可读写fd,开启一个goroutine去执行。后面就是一些http参数 协议的解析和数据组装。

http server最终这里处理请求

 这个地方的context从之前提到过的pool里面获取,复用对象。做一些reset和初始化操作。然后处理请求, 用完之后在放到池子里去。

接下来走到handleHTTPRequest中去

这个地方就会涉及到路由匹配,然后执行对应的handlers。

先遍历trees,找到匹配的method。然后调用getValue找到匹配的path的节点。如果这个节点的handlers不为空,那么执行。执行hanlers,主要是Next方法,如下逻辑。

执行注册的handler回调函数,会传入context,这个执行顺序逻辑如下

假如handler如下,func1->func2->func3

func func1 () {

        c.Next() //会调用func2()

}

func func2() {

        c.Next() //会调用func3

}

func func3() {

        doSomething()...

}

c.Next执行完后,会回到func2, func1继续执行后面的逻辑,所以一般可以在func1里面算下整个业务处理时间。

 GitHub - gin-gonic/gin: Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.

https://www.liwenzhou.com/posts/Go/read_gin_sourcecode/

举报

相关推荐

0 条评论