在上一节内容中,我们讨论了如何定义一个简单的WebServer,我们参考了Gin框架,来搭建了属于我们的Server。
可以看到Gin中的 Engine 的关键在于RouterGroup,这节内容,我们来学习一下,如何完善我们的路由树。
一、路由树内容
路由树能做什么,这就不得不再次去参考Gin了,现在 好用的Web框架层出不穷,我们学习这些的主要目的就是为了更加深入的理解,方便后续的应用。
可以看到,在RouterGroup 里面也组合了 engine,还有一个 Handlers 我们可以猜测,这个大概率是用来存放我们的路由代码,一个 path 作为路径
底下会发现,RouterGroup 是实现了 IRouter 的接口,所以,我们跳去 IRoute 看看这个接口主要做了什么
可以看到,IRoutes 就是包装了 我们要向http发送请求的构造的路由树,这么看来,Gin 的路由树 应该是 每一个method,也就是一个请求方法作为一棵树,然后路径向下延生。
二、实现简单路由树
路由树的实现,不可避免就要考虑到一个问题,那就是匹配问题,例如用户输入: " / user / a ",我们需要准确的识别出,根节点为 "/",子节点为 user,子节点的子节点为 a。
如此这般一层一层,像树一样延生下去
所以,首先我们需要,在 自己定义的 router 下 定义一颗树,树的组成这里我们选用map,因为Gin里面是一个http请求方法,一颗树,而http请求方法是 string 类型,因此,我们还得定义一个专门用于管理节点的结构体
话不多说,看看代码
package serv
import (
"fmt"
"github.com/gin-gonic/gin"
"strings"
)
type router struct {
trees map[string]*node
}
type node struct {
path string
children map[string]*node
handler HandleFunc
}
func newRouter() *router {
return &router{
trees: map[string]*node{},
}
}
func (r *node) childOrCreate(seg string) *node {
if r.children == nil {
// 如果children为nil的话,下面的逻辑将会崩溃,所以需要建立一个新的node
r.children = map[string]*node{}
}
res, ok := r.children[seg]
if !ok {
res = &node{
path: seg,
}
r.children[seg] = res
}
return res
}
func (r *router) addRoute(method string, path string, handler HandleFunc) {
if path == "" {
panic("路径不能为空")
}
gin.Default()
if path[0] != '/' {
panic("路径只能以 / 开头")
}
if path != "/" && path[len(path)-1] == '/' {
panic("路径不能以 / 结尾")
}
// 简单校验完后,直接查找对应的 请求方法有没有树,
// 没有我们就创建一个根路径作为起始
root, ok := r.trees[method]
if !ok {
// 不ok说明没有根节点,所以我们新建一个
root = &node{
path: "/",
}
r.trees[method] = root
}
if path == "/" {
// 根节点重复注册
if root.handler != nil {
panic(fmt.Sprintf("路径 [%s] 重复注册", path))
}
root.handler = handler
return
}
// 因为我们的path是 “/xxx/xxx”,这样第一个/会切割出一个空字符串
// 所以我们需要刨除第一个 /
path = path[1:]
segs := strings.Split(path, "/")
for _, seg := range segs {
// 防止中间出现多个 /
if seg == "" {
panic("路径不能出现连续多个 / ")
}
child := root.childOrCreate(seg)
root = child
}
// 普通路径重复注册,主要判断依据就是,这个路径下是否依旧存在handlerFunc了
if root.handler != nil {
panic(fmt.Sprintf("路径 [%s] 重复注册", path))
}
root.handler = handler
}
看得出来是比较简单的,关键在于,我们切割了 / 后,用循环(也可以考虑递归)将子节点的内容不断创建出来。
其次就是一些校验,防止用户输入错误的路径导致崩溃等等
最后,这只是最基础的静态匹配,后续还有更多匹配方法更新,不断完善这个路由树。
关于校验部分,是否要做的详尽一切,关键就在于 权衡 ,你是否愿意花费大量成本去进行测试,编写和思考用户的行为,这里我就简单粗暴的校验一些。
欢迎关注