ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

go gin最左路由前缀树

2022-04-11 18:32:37  阅读:248  来源: 互联网

标签:结点 name user Rc go gin 路由 左路


gin也用了一段时间了,写个文总结一下路由部分吧,以免忘记。

关键名称:最左最短前缀树。

假设,最开始的路由route.GET("/R1R2R3R4R5..........Rn", func(c *gin.Context) {} ),这个时候树还是空的,直接调用n.insertChild(path, fullPath, handlers)函数。

对于/R1R2R3R4R5..........Rn,分几种情况:

一、没有通配符:和*

直接生成一个结点。

二、无*号,但至少有一个冒号":",第一个冒号所在位置Ri,有如下推论:

  • n >= i + 1 比如/user/: 就是非法,必须带有参数名。

  • 设集合A = { j | j​ (i+1, n] && Rj = : } ,且A从小大到排序;

    集合B = { k | k​ (i+1, n] && Rk = / } ,且B从小大到排序。

    A与B分四种情况

    A与B分四种情况

    1. A = ​,B = ​ 此时生成的路由树如下,W表示wildcard,虚线表示不挂载handlers。

     

    1. A = ​,B ​ ​ 取 k = Min(B) 此时生成的路由树如下

       

    1. A ​ ​,B = ​ 非法,不能在单个路径上(即用/分隔的路径)存在多个通配符,比如 /user:name:id。

    2. 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

到这里,单个路由树的规则已经有了。接下来是在已有路由树的基础上添加路由,导致的结点分裂、移动。

设已有的路由树如下:

要添加的路径为 S = S1S2S3...Sm,R与S的最长公共子串为C = C1C2C3...Ci,i​[1, Min(n, m)],i、m、n三者关系讨论:

只有 ① ② ③ ④ 有效,其他都是无效的组合。

  • 对于①,此时需要将R分裂,C(即 R1R2R3...Ri)作为父结点,Ri+1...Rn子结点

  • 对于 ③,相当于无效路由;
  • 对于 ④,按照下面的规则:

      1. 截取 P = Si+1...Sm

      2. 如果当前结点R是个param结点,Si+1 = /,且 R有子结点(有的话只会有一个子结点):

        S = P,R = children[0];

        回到起点;

      3. 如果在当前结点R的indices(param结点不会有indices)中能找到相同的Si+1值,说明在R的子结点中,有相同的前缀:

        找到该子结点Rc

        增加Rc的权值,并根据新的权值,重新排列R的子结点们;

        S = P,R = Rc

        回到起点;

      4. 如果Si+1 != :,且Si+1 != *:

        为当前结点R添加indices;

        在当前结点R后面添加添加子结点Si+1Si+2...Sm,添加方式与前面生成路由树的过程一样,会增加当前结点R的权重,重新排列;

        最后返回;

         

      5. 如果当前结点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

        回到起点;

         

 

 

至此完成!

 

 

 

 

 

 

标签:结点,name,user,Rc,go,gin,路由,左路
来源: https://www.cnblogs.com/cool-fire/p/16131363.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有