go gin最左路由前缀树
gin也用了一段时间了,写个文总结一下路由部分吧,以免忘记。
关键名称:最左最短前缀树。
1R2R3R4R5..........Rn
-
-
设集合A = { j | j (i+1, n] && Rj = : } ,且A从小大到排序;
集合B = { k | k (i+1, n] && Rk = / } ,且B从小大到排序。
-
A = ,B = 此时生成的路由树如下,W表示wildcard,虚线表示不挂载handlers。
-
A = ,B 取 k = Min(B) 此时生成的路由树如下
-
A ,B = 非法,不能在单个路径上(即用/分隔的路径)存在多个通配符,比如 /user:name:id。
-
A ,B 这种情况最复杂,需要考查 j、k的关系,取 j = Min(A),k = Min(B)
-
j < k 非法,比如/user/:name:
-
j > k 以k的分界线,先生成k之前的部分,之后再以k为起点,递归的从1开始生成子结点
以/user:name/uid:uid为例,生成的路由树如下:
-
-
三、无:号,至少有一个*,第一个*号所在位置Ri,有如下推论
-
有且仅有一个* ,gin规定路径中只能有一个*,比如 /user/*name/*id 是非法;
-
Ri-1 = /,gin规定在*之前,必须使用/开头,比如 /user*name 是非法;
-
*只能在路径的最后面,比如/user/*name/id 是非法;
注意,r.GET("*name", func(c *gin.Context){})
在内部gin会加上/,开成 /*name路径。
形成的路由树以及一个例子/user/name/*name,其中左边绿色小方框的是fullPath,右边橙色的是indices
号只能有一个,且*号只能在路径最后面,此时可以这样处理
*-
设集合A = { j | j [1, n] && Rj = : } ,且A从小大到排序,取 j = Max(A);
集合B = { k | k (k+1, n] && Rk = / } ,且B从小大到排序,取 k = Min(B);
k 即为最后一个:号后面的第一个/位置;
分成两个子串:T = /R1R2R3....Rk-1 和 Y = RkRk+1....Rn,T只有:号,Y只有*号。
对于T,按照前面一二递归处理;对于Y,按照三处理,即可。
下图为例子 /user/name/:name/id/:id/sex/*sex
到这里,单个路由树的规则已经有了。接下来是在已有路由树的基础上添加路由,导致的结点分裂、移动。
设已有的路由树如下:
1S2S3...Sm,R与S的最长公共子串为C = C1C2C3...Ci,i[1, Min(n, m)]
-
对于①,此时需要将R分裂,C(即 R1R2R3...Ri)作为父结点,Ri+1...Rn子结点
- 对于 ③,相当于无效路由;
-
对于 ④,按照下面的规则:
-
-
截取 P = Si+1...Sm;
-
如果当前结点R是个param结点,Si+1 = /,且 R有子结点(有的话只会有一个子结点):
S = P,R = children[0];
回到起点;
-
如果在当前结点R的indices(param结点不会有indices)中能找到相同的Si+1值,说明在R的子结点中,有相同的前缀:
找到该子结点Rc;
增加Rc的权值,并根据新的权值,重新排列R的子结点们;
S = P,R = Rc;
回到起点;
-
如果Si+1 != :,且Si+1 != *:
为当前结点R添加indices;
在当前结点R后面添加添加子结点Si+1Si+2...Sm,添加方式与前面生成路由树的过程一样,会增加当前结点R的权重,重新排列;
最后返回;
-
如果当前结点R的W值(R.wildcard) = true:
W值为true,只能是param和catchAll两种情况,且两种情况的模式是这样:
对于catchAll模式,直接panic退出。因为catchAll是通配后缀所有的字符串,gin规定不能在其后面添加路径;
对于param模式,取R的子结点Rc(唯一的子结点),即Rc = R.child,接着判断 P是否要比Rc长,且P是否以Rc打头,且 Rc打头后第一个字符是 /,即进行以下判断:
取 L = len(Rc),len(P) >= L && P[:L] == Rc && P[L] = /
如果条件不满足,则直接panic退出,因为gin不允许存在存在相同param模式的路径,比如(/:name 与 /:namekk)。
如果条件满足,S = P,R = Rc;
回到起点;
-
-
至此完成!
可以在gin里加上代码,打印出层级路由树:
func (engine *Engine) PrintTrees() {
for _, tree := range engine.trees {
// debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
fmt.Fprintf(DefaultWriter, "method: %s\n root: ", tree.method)
printNode(tree.root, 0)
fmt.Println()
}
}
func printNode(n *node, indent int) {
content := fmt.Sprintf("path(%s) fullPath(%s) prio(%d) indices(%s) wildChild(%t) handlers(%d) nType(%s)\n", n.path, n.fullPath, n.priority, n.indices, n.wildChild, len(n.handlers), getNtype(n.nType))
format := fmt.Sprintf("%%%ds", len(content) + indent)
fmt.Fprintf(DefaultWriter, format, content)
if indent == 0 {
indent += 10
} else {
indent += 2
}
for _, c := range n.children {
printNode(c, indent)
}
}