Gin
是go
语言的一款轻量级框架,风格简单朴素,支持中间件,动态路由等功能。 gin项目github地址
路由是web框架的核心功能。在没有读过 gin
的代码以前,在我眼里的路由实现是这样的:根据路由里的 /
把路由切分红多个字符串数组,而后按照相同的前子数组把路由构形成树的结构;寻址时,先把请求的 url
按照 /
切分,而后遍历树进行寻址。node
好比:定义了两个路由 /user/get
,/user/delete
,则会构造出拥有三个节点的路由树,根节点是 user
,两个子节点分别是 get
delete
。git
上述是一种实现路由树的方式,且比较直观,容易理解。对 url 进行切分、比较,时间复杂度是 O(2n)
。github
Gin的路由实现使用了相似前缀树的数据结构,只需遍历一遍字符串便可,时间复杂度为O(n)
。web
固然,对于一次 http 请求来讲,这点路由寻址优化能够忽略不计。数组
Gin
的 Engine
结构体内嵌了 RouterGroup
结构体,定义了 GET
,POST
等路由注册方法。数据结构
Engine
中的 trees
字段定义了路由逻辑。trees
是 methodTrees
类型(其实就是 []methodTree
),trees
是一个数组,不一样请求方法的路由在不一样的树(methodTree
)中。框架
最后,methodTree
中的 root
字段(*node
类型)是路由树的根节点。树的构造与寻址都是在 *node
的方法中完成的。函数
UML 结构图优化
trees
是个数组,数组里会有不一样请求方法的路由树。ui
node 结构体定义以下
type node struct { path string // 当前节点相对路径(与祖先节点的 path 拼接可获得完整路径) indices string // 因此孩子节点的path[0]组成的字符串 children []*node // 孩子节点 handlers HandlersChain // 当前节点的处理函数(包括中间件) priority uint32 // 当前节点及子孙节点的实际路由数量 nType nodeType // 节点类型 maxParams uint8 // 子孙节点的最大参数数量 wildChild bool // 孩子节点是否有通配符(wildcard) }
关于 path
和 indices
,实际上是使用了前缀树的逻辑。
举个栗子:
若是咱们有两个路由,分别是 /index
,/inter
,则根节点为 {path: "/in", indices: "dt"...}
,两个子节点为{path: "dex", indices: ""},{path: "ter", indices: ""}
handlers
里存储了该节点对应路由下的全部处理函数,处理业务逻辑时是这样的:
func (c *Context) Next() { c.index++ for s := int8(len(c.handlers)); c.index < s; c.index++ { c.handlers[c.index](c) } }
通常来讲,除了最后一个函数,前面的函数被称为中间件。
若是某个节点的 handlers
为空,则说明该节点对应的路由不存在。好比上面定义的根节点对应的路由 /in
是不存在的,它的 handlers
就是[]
。
Gin 中定义了四种节点类型:
const ( static nodeType = iota // 普通节点,默认 root // 根节点 param // 参数路由,好比 /user/:id catchAll // 匹配全部内容的路由,好比 /article/*key )
param
与 catchAll
使用的区别就是 :
与 *
的区别。*
会把路由后面的全部内容赋值给参数 key
;但 :
能够屡次使用。
好比:/user/:id/:no
是合法的,但 /user/*id/:no
是非法的,由于 *
后面全部内容会赋值给参数 id
。
若是孩子节点是通配符(*
或者:
),则该字段为 true
。
定义路由以下:
r.GET("/", func(context *gin.Context) {}) r.GET("/index", func(context *gin.Context) {}) r.GET("/inter", func(context *gin.Context) {}) r.GET("/go", func(context *gin.Context) {}) r.GET("/game/:id/:k", func(context *gin.Context) {})
获得的路由树结构图为:
附一篇前缀树的文章,前缀树和后缀树