4.02 课后复习-Route
本节课工程结构:
(base) yanglei@yuanhong v4-rc % tree ./
./
├── context.go
├── handle_func.go
├── server.go
├── server_interface.go
└── server_test.go
0 directories, 5 filesPART1. 定义路由森林与节点
1.1 定义节点
新建文件node.go:
package v4_rc
// node 路由树中的节点
type node struct {
// pattern 路由路径
pattern string
// children 子节点 key为子节点的路由路径 value为路径对应子节点
children map[string]*node
// HandleFunc 路由对应的处理函数
HandleFunc
}1.2 定义路由森林
新建文件router.go:
1.3 定义添加路由的操作
router.go:
PART2. 组合路由森林与Server
server_interface.go:
server.go:
PART3. 静态路由注册
3.1 编写测试方法
3.1.1 构造路由树
router_test.go:
3.1.2 断言路由森林
若2个路由森林中的路由树数量不同,则不相等
如果目标router中没有对应HTTP动词的路由树(例:目标路由森林中有GET/POST/PUT这3棵路由树,而期望路由森林中有GET/POST/DELETE这3棵路由树),则不相等
router_test.go:
3.1.3 断言路由树
若2个节点中有一个为nil,则不相等
若2个节点的path不同,则不相等
若2个节点的子节点数量不同,则不相等
若2个节点的HandleFunc类型不同,则不相等
比对2个节点的子节点映射:
若源节点的子节点映射中,存在目标节点中没有的子节点,则不相同
注:因为上边已经比对过2个节点的子节点数量了,所以只要能通过这个步骤的比对,那么2个节点的子节点必然是一一对应的
两个path相同的节点递归比对
router_test.go:
3.2 添加测试用例
router_test.go:
3.3 实现addRoute方法
3.3.1 根据method查找路由树,不存在则创建
router.go:
3.3.2 对根节点做特殊处理
此处要特殊处理根节点的原因在于:后续按/分割path后,根节点就直接被分割没了(变成"")了.所以要特殊处理根节点
router.go:
3.3.3 从根节点开始逐层查找目标节点,找到目标节点后添加HandleFunc
node.go:
router.go:
此时运行测试用例即可顺利通过测试
3.4 测试用例
3.4.1 对根节点的测试用例
router_test.go:
3.4.2 前导/user节点的测试用例
router_test.go:
3.4.3 /order/detail节点的测试用例
为测试路由树中间有不存在的节点时,是否符合预期
router_test.go:
3.4.4 其他HTTP动词的测试用例
a. /login
router_test.go:
b. /order/create
router_test.go:
3.4.5 非法用例
a. path为空字符串
router.go:
router_test.go:
TODO:assert.Panicsf
b. path不是以/开头
router.go:
router_test.go:
c. path以/结尾
router.go:
router_test.go:
d. path中包含连续的/
router.go:
router_test.go:
e. 路由重复注册
e1. 根节点路由重复注册
router.go:
router_test.go:
e2. 普通节点路由重复注册
router.go:
router_test.go:
PART4. 静态路由查找
4.1 编写测试函数
router_test.go:
4.2 定义测试用例的类型
我们需要这个类型能够告知我们如下信息:
在给定HTTP动词和path的前提下,是否找到了节点?
在给定HTTP动词和path的前提下,找到的节点和预期的节点是否相同?
4.3 定义测试的过程
step1. 判断在路由树中是否找到了节点
step2. 判断找到的节点和预期的节点是否相同
route_test.go:
4.4 构造测试用例
HTTP动词对应的路由树不存在
完全命中
命中了path对应的节点,但HandleFunc为nil
根节点(特殊处理)
path对应的节点不存在
4.5 根据测试用例开发findRoute()
4.5.1 HTTP动词对应的路由树不存在
a. 实现
route.go:
b. 测试
route_test.go:
4.5.2 完全命中
a. 实现
step1. 按
/切割pathstep2. 从路由树的根节点开始,按"层次"查找节点
step3. 没找到则返回
nil, false即可
这里我一开始的实现是这样的:
route.go:
有2个问题:
没有对
target.children判空"在当前节点下查找子节点"是一个独立完整的功能,应该是
node结构体的方法
修改后的实现:
node.go:
route.go:
b. 测试
route_test.go:
4.5.3 命中了path对应的节点,但HandleFunc为nil
这个case不需要做特殊的边缘条件检测.因为在addRoute()时我们也没有检测HandleFunc是否为空.换言之就是:既然在注册路由时允许使用者传入空的HandleFunc,那么在查找时命中了一个HandleFunc为nil的节点就不算是错误
a. 测试
route_test.go:
4.5.4 根节点
a. 实现
route.go:
b. 测试
route_test.go:
4.5.5 path对应的节点不存在
a. 测试
route_test.go:
PART5. route集成至Server
5.1 实现
server.go:
5.2 测试
server_test.go:
PART6. 通配符路由的注册与查找
6.1 通配符路由的定义与设计
6.1.1 通配符路由的定义
通配符路由:用*表达匹配任何路径
6.1.2 通配符路由的设计
一个
*只能表达1段路由不做可回溯的路由匹配机制
6.2 通配符路由的注册
6.2.1 修改node的结构
node的结构node.go:
6.2.2 定义测试用例
route_test.go:
根据debug的结果可知:应该把*创建在wildcardChild字段上,现在则是创建在了children字段上
6.2.3 修改创建子节点的逻辑
正确的逻辑是:当路径为*时,将节点创建在wildcardChild字段上
node.go:
6.2.4 修改判断子节点相等的逻辑
由于新增了通配符子节点字段,所以对于2个节点,也要比对各自的通配符子节点是否相同
route_test.go:
此时再跑测试用例,就可以跑通了
6.2.5 添加其他测试用例
6.2.5.1 根节点的通配符子节点
route_test.go:
6.2.5.2 通配符子节点的通配符子节点
route_test.go:
6.2.5.3 通配符子节点的普通子节点
route_test.go:
6.2.5.4 通配符子节点的普通子节点的通配符子节点
route_test.go:
6.3 通配符路由的匹配
6.3.1 实现
思路比较简单:
若当前节点的children映射为空,则有可能匹配到通配符子节点
若在当前节点的children映射中没有找到path对应的子节点,则有可能匹配到通配符子节点
此处说"有可能匹配到",是因为还有一种可能是通配符子节点为空,这种情况就属于没匹配到了
node.go:
6.3.2 测试
a. 匹配普通节点的通配符子节点
route_test.go:
b. 匹配普通节点下普通子节点和通配符子节点共存
c. server组合route时的通配符匹配
server_test.go:
PART7. 参数路由的注册与查找
7.1 参数路由的定义与设计
7.1.1 参数路由的定义
参数路由:就是指在路由中带上参数,同时这些参数对应的值可以被业务取出来使用.在我们的设计中用:参数名的形式表示路由参数
例:/user/:id,如果输入路径/user/123,则会命中这个路由/user/:id,并且在业务函数中可以取到变量id = 123
7.1.2 参数路径的设计
是否允许同样的参数路由和通配符路由一起注册?
例如同时注册/user/:id和/user/*一起注册?
可以允许,但没必要,且用户也不该设计这种路由
7.2 实现参数路由节点的创建
7.2.1 修改node的结构
node.go
7.2.2 定义测试用例
router_test.go
7.2.3 修改创建子节点的逻辑
node.go:
7.2.4 修改判断子节点相等的逻辑
router_test.go:
7.3 参数路由的校验
7.3.1 实现校验逻辑
设计中是不准备支持同样的参数路由和通配符路由一起注册的(即/user/*和/user/:id).
所以从逻辑上来讲,只需要实现:注册参数路由时检测通配符路由是否存在;注册通配符路由时检测参数路由是否存在.若对方存在,则不允许注册即可.
node.go
7.3.2 测试
router_test.go:
7.4 参数路由的查找
7.4.1 编写测试用例
router_test.go
此时运行测试用例报错节点未找到,因为此时没有查找参数子节点的逻辑
7.4.2 实现参数路由的查找
node.go:
此时测试用例即可通过
7.5 参数路由的参数值
7.5.1 定义新类型
需要定义一个新的类型,该类型包含命中的节点,以及当该节点为参数路由节点时的参数名和参数值
match_node.go:
7.5.2 修改node.childOf()方法
node.childOf()方法node.childOf()方法需要告知其调用者(也就是router.findRoute()方法)找到的节点是否为参数子节点.因为查找节点时,拿到的是参数值,而参数名是放在查找到的节点的path字段上的.
node.go:
7.5.3 为matchNode结构体添加写入路由参数的方法
matchNode结构体添加写入路由参数的方法match_node.go:
7.5.4 修改router.findRoute()方法
router.findRoute()方法router.findRoute()方法则返回一个*matchNode类型的实例,其中包含了参数名和参数值(如果有的话).
router.go
7.5.5 修改Context结构体
Context结构体context.go
7.5.6 修改HTTPServer.serve()方法
HTTPServer.serve()方法server.go
7.5.7 测试HTTPServer
server_test.go:
Last updated