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 files

PART1. 定义路由森林与节点

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. 按/切割path

  • step2. 从路由树的根节点开始,按"层次"查找节点

  • step3. 没找到则返回nil, false即可

这里我一开始的实现是这样的:

route.go:

有2个问题:

  1. 没有对target.children判空

  2. "在当前节点下查找子节点"是一个独立完整的功能,应该是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. 一个*只能表达1段路由

  2. 不做可回溯的路由匹配机制

6.2 通配符路由的注册

6.2.1 修改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()方法需要告知其调用者(也就是router.findRoute()方法)找到的节点是否为参数子节点.因为查找节点时,拿到的是参数值,而参数名是放在查找到的节点的path字段上的.

node.go:

7.5.3 为matchNode结构体添加写入路由参数的方法

match_node.go:

7.5.4 修改router.findRoute()方法

router.findRoute()方法则返回一个*matchNode类型的实例,其中包含了参数名和参数值(如果有的话).

router.go

7.5.5 修改Context结构体

context.go

7.5.6 修改HTTPServer.serve()方法

server.go

7.5.7 测试HTTPServer

server_test.go:

Last updated