七叶笔记 » golang编程 » Golang Web框架Gin解析(四)

Golang Web框架Gin解析(四)

在上一篇结束的时候我们留下了两个问题,他们分别是:

1.关于Gin每次请求都是下标式请求,如何实现的?

2.关于路由注册时Gin是如何组装这些请求的并且最终实现下标式请求?

今天我们将针对这两个问题进行深入学习:

首先我们先思考一下如果实现上面的方式我们需要如下步骤:

1.当我们在注册路由的时候,针对于我们的路由地址,我们需要一个handlefunc数组

2.当我们的服务再接收到HTTP请求的时候,把我们请求的地址解析,并对应到我们设置的路由中

3.并得到handlefunc的数组,依次执行数组中的方法

当这里我们有一个初步的概念了,那么接下来我们看一下Gin是怎么实现的。

通过RouterGroup 结构体 的方法我们可以找到其中有一个addRoute的方法,依次寻找原点我们可以快速定位在 Gin中存在一个tree行结构并且树的结构体下有个addRoute的方法,用于填充树,这个方法也是我们今天的重点学习目标!

首先我们先简单看一下这个树的结构中包含了哪些属性:

type node struct {
 path string //用于存放设置的路由,当然这里是经过解析后的路由,并不是和设置路由完全对应
 wildChild bool //当为param时为true,意可以任意搭配
 nType nodeType 
 maxParams uint8
 indices string //用来存放每个子节点第一个字符拼接成的字符串。
 children []*node
 handlers HandlersChain //用来存储每个节点所需要执行func
 priority uint32
}
 

现在我们我们开始攻破这个树复杂的解析方法

node.addRoute方法解析

先贴上代码

func (n *node) addRoute(path string, handlers HandlersChain) {
 fullPath := path
 n.priority++
 numParams := countParams(path)
 // non-empty tree
 if len(n.path) > 0 || len(n.children) > 0 {
 walk:
 for {
 // Update maxParams of the current node
 if numParams > n.maxParams {
 n.maxParams = numParams
 }
 // Find the longest common prefix.
 // This also implies that the common prefix contains no ':' or '*'
 // since the existing key can't contain those chars.
 i := 0
 max := min(len(path), len(n.path))
 for i < max && path[i] == n.path[i] {
 i++
 }
 // Split edge
 if i < len(n.path) {
 child := node{
 path: n.path[i:],
 wildChild: n.wildChild,
 indices: n.indices,
 children: n.children,
 handlers: n.handlers,
 priority: n.priority - 1,
 }
 // Update maxParams (max of all children)
 for i := range child.children {
 if child.children[i].maxParams > child.maxParams {
 child.maxParams = child.children[i].maxParams
 }
 }
 n.children = []*node{&child}
 // [] byte  for proper unicode char conversion, see #65
 n.indices = string([]byte{n.path[i]})
 n.path = path[:i]
 n.handlers =  nil 
 n.wildChild = false
 }
 // Make new node a child of this node
 if i < len(path) {
 path = path[i:]
 if n.wildChild {
 n = n.children[0]
 n.priority++
 // Update maxParams of the child node
 if numParams > n.maxParams {
 n.maxParams = numParams
 }
 numParams--
 // Check if the wildcard matches
 if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
 // check for longer wildcard, e.g. :name and :names
 if len(n.path) >= len(path) || path[len(n.path)] == '/' {
 continue walk
 }
 }
 panic("path segment '" + path +
 "' conflicts with existing wildcard '" + n.path +
 "' in path '" + fullPath + "'")
 }
 c := path[0]
 // slash after param
 if n.nType == param && c == '/' && len(n.children) == 1 {
 n = n.children[0]
 n.priority++
 continue walk
 }
 // Check if a child with the next path byte exists
 for i := 0; i < len(n.indices); i++ {
 if c == n.indices[i] {
 i = n.incrementChildPrio(i)
 n = n.children[i]
 continue walk
 }
 }
 // Otherwise insert it
 if c != ':' && c != '*' {
 // []byte for proper unicode char conversion, see #65
 n.indices += string([]byte{c})
 child := &node{
 maxParams: numParams,
 }
 n.children =  append (n.children, child)
 n.incrementChildPrio(len(n.indices) - 1)
 n = child
 }
 n.insertChild(numParams, path, fullPath, handlers)
 return
 } else if i == len(path) { // Make node a (in-path) leaf
 if n.handlers != nil {
 panic("handlers are already registered for path ''" + fullPath + "'")
 }
 n.handlers = handlers
 }
 return
 }
 } else { // Empty tree
 n.insertChild(numParams, path, fullPath, handlers)
 n.nType = root
 }
}
 

关于AddRoute方法的学习主要为大家整理几条思路。因为这中间有很多细节可能需要大家自己去学习。我梳理的思路如下:

1.此时methodsTree中不存在对应的method时,会直接进入insertChild方法保存第一个子节点

2.当对应的methodTree存在时,会进入一个walk循环,首先会进行相同部分判断并用相同作为子节点。也就是我们设置的路由在树结构中会被父子节点

下面我们来看一个简单的图例,相信大家在看到这个图例的时候就会大概明白中间的道理了。

图1为:路由注册

图1

图2是图1按照顺序添加后的变化过程

图2

总结

今天我们为大家分析了gin在注册完路由后是如何保存在自己的树结构中的,这里建议大家还是详细的进入Gin的源码中仔细的阅读一下,个人觉得这个树结构的写法是封装好的值得学习一下。

方法入口:进入gin源码文件,打开tree.go,搜索AddRoute方法

相关文章