

web框架中路由实现原理解析
source link: https://studygolang.com/articles/26324
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

路由功能是web框架中一个很重要的功能,它将不同的请求转发给不同的函数(handler)处理,很容易能想到,我们可以用一个字典保存它们之间的对应关系,字典的key存放path,value存放handler。当一个请求过来后,使用 routers.get(path, None) 就可以找到对应的handler。
利用字典实现路由可以参考我的这篇文章: 动手实现web框架 。
使用字典有一个问题,不支持动态路由。如果路由像这样呢?
/hello/:name/profile
name前面是通配符 : ,表示这是个动态的值。一个解决办法是使用前缀树trie。
前缀树
leetcode中有这个算法, 点这里 查看。
前缀树前缀树,首先是一棵树。不同的是树中一个节点的所有子孙都有相同的前缀。前缀树将单词中的每个字母依次插入树中,插入前首先确认该单词是否存在,不存在才创建新节点,如果一个单词已经全部插入,则将末尾单词设置为标志位。
type Node struct { isWord bool // 是否是单词结尾 next map[string]*Node // 子节点 } type Trie struct { root *Node }
以单词leetcode,leetd和code为例,首先一次插入leetcode中的每个单词,然后插入leetd的时候,leet在树中已经存在,跳过往下,现在要插入字母d,不存在,所以新建节点插入树中,并将该节点的isWord置位true,表明到了单词末尾。
最终插入结果为:
func (this *Trie) Insert(word string) { cur := this.root for _, w := range []rune(word) { c := string(w) if cur.next[c] == nil { cur.next[c] = &Node{next: make(map[string]*Node)} } cur = cur.next[c] } cur.isWord = true }
那么,当我们要搜索单词leetd的时候,从根节点开始查找,如果找到某条路径是leetd,并且末尾的d是单词标志位,则表示搜索成功。
func (this *Trie) Search(word string) bool { cur := this.root for _, w := range []rune(word) { c := string(w) if cur.next[c] == nil { return false } cur = cur.next[c] } return cur.isWord }
明白了前缀树的原理,我们来看看路由匹配是如何利用前缀树来实现的。
路由前缀树
go语言中gin框架的路由实现就是利用前缀树,可以看看它的源代码是如何实现的。
考虑一下,路由或者说路径的特点,是以 / 分隔的单词组成的,那我们将 / 的每一部分挂靠在前缀树上就可以了。如下图所示:
还有一点需要考虑,我们在用web框架定义路由的时候,常见的做法是根据不同的HTTP方法来定义。比如:
// 以go语言gin框架为例 g := gin.New() g.GET("/hello", Hello) g.POST("/form", Form)
对于同一个路径,可能有多个方法支持。所以我们要以不同HTTP方法为树根创建前缀树。当一个GET请求过来的时候,就从GET树上搜索,POST请求就从POST树上搜索。
除了为不同的HTTP方法定义树之外,还要给那些是通配符的节点增加一个标志位。所以,我们的路由前缀树结构看起来像这样:
type node struct { path string // 路由路径 part string // 路由中由'/'分隔的部分 children map[string]*node // 子节点 isWild bool // 是否是通配符节点 } type router struct { root map[string]*node // 路由树根节点 route map[string]HandlerFunc // 路由处理handler }
依照上面的前缀树算法的实现,照葫芦画瓢,我们可以写出插入路由和搜索路由的方法:
// addRoute 绑定路由到handler func (r *router) addRoute(method, path string, handler HandlerFunc) { parts := parsePath(path) if _, ok := r.root[method]; !ok { r.root[method] = &node{children: make(map[string]*node)} } root := r.root[method] key := method + "-" + path // 将parts插入到路由树 for _, part := range parts { if root.children[part] == nil { root.children[part] = &node{ part: part, children: make(map[string]*node), isWild: part[0] == ':' || part[0] == '*'} } root = root.children[part] } root.path = path // 绑定路由和handler r.route[key] = handler } // getRoute 获取路由树节点以及路由变量 func (r *router) getRoute(method, path string) (node *node, params map[string]string) { params = map[string]string{} searchParts := parsePath(path) // get method trie var ok bool if node, ok = r.root[method]; !ok { return nil, nil } // 在该方法的路由树上查找该路径 for i, part := range searchParts { var temp string // 查找child是否等于part for _, child := range node.children { if child.part == part || child.isWild { // 添加参数 if child.part[0] == '*' { params[child.part[1:]] = strings.Join(searchParts[i:], "/") } if child.part[0] == ':' { params[child.part[1:]] = part } temp = child.part } } // 遇到通配符*,直接返回 if temp[0] == '*' { return node.children[temp], params } node = node.children[temp] } return }
上面的代码是我自己实现的一个web框架 gaga 中路由前缀树相关的代码,有需要的可以去看看源代码。另外,欢迎 star 呀。
其中的 addRoute 用来将路由插入到对应method的路由树中,如果节点是通配符,将其设置为 isWild , 同时绑定路由和handler方法。
getRoute方法首先查找路由方法对应的路由前缀树,然后在树中查找是否存在该路径。
总结
前缀树trie算法不光可以用在路由的实现上,搜索引擎中自动补全的实现,拼写检查等等都是用trie实现的。trie树查找的时间和空间复杂度都是线性的,效率很高,很适合路由这种场景使用。
路由的实现上,go语言中 httpRouter 这个库除了使用前缀树之外,还加入了优先级,有兴趣的可以看看它的源码了解下。
我的blog: https://blog.shiniao.fun
Recommend
-
60
什么是koa框架? koa是一个基于node实现的一个新的web框架,它是由express框架的原班人马打造的。它的特点是优雅、简洁、表达力强、自由度高。它更express相比,它是一个更轻量的node框架,因为它所有功能都通过插件实现,这种插拔式的架构设计模
-
40
原文链接:github.com/whinc/blog/… 在单页应用如此流行的今天,曾经令人惊叹的前端路由已经成为各大框架的基础标配,每个框架都提供了强大的路由功能,导致路由实现变的复杂。想要搞懂路由内部实现还是有些困难的,但是如果只想了解路由实现基本原理
-
19
何为前端路由?路由(Router)这个概念最先是后端出现的,是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不同的资源,请求不同的页面是路由的其中一种功能。前端随着 ajax 的流行,数据请求可以在不刷新浏览...
-
13
VirtualAPP框架原理解析58同城 Android工程师为了方便大家理解整个VirutlAPP框架实现的原理及双开APP的Activity是如何启动起来的,文本会着重讲解关键点,对于细节会在后续的文章...
-
8
Android图片加载框架最全解析(七),实现带进度的Glide图片加载功能 ...
-
10
阅读目录: 1.开篇介绍 2.ASP.NET Routing 路由对象模型的位置 3.ASP.NET Routing 路由对象模型的入口 4.ASP.NET Routing 路由对象模型的内部结构 4.1UrlRoutingModule 对象内部结构 4.2RouteBase、Ro...
-
16
一、前缀树和基数树(radix tree)的区别:trie又叫前缀树,是一个多叉树,广泛应用于字符串搜索,每个树节点存储一个字符,从根节点到任意一个叶子结点串起来就是一个字符串;radix tree是优化之后的前缀树,对空间进一步压缩。下图左侧是字符...
-
11
Activiti集成LDAP简介企业在LDAP系统中保存了用户和群组信息,Activiti提供了一种解决方案,通过简单的配置就可以让activit连接LDAP要想在项目中集成LDAP,需要在pom.xml中添加activiti-ldap依赖:<dependency&...
-
12
想了解更多关于开源的内容,请访问:...
-
5
1. SPA路由实现的基本原理 前端单页应用实现路由的策略有两种,分别是 基于hash 和 基于 History API 基于hash 通过将一个 URL path 用
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK