为你自己学Go
  • README
  • PART01.Web框架概览
    • 1.01 Web框架概览:学习路线
    • 1.02 Web框架概览-Beego框架分析
    • 1.03 Web框架概览-GIN框架分析
    • 1.04 Web框架概览-Iris框架分析
    • 1.05 Web框架概览-Echo框架分析
  • PART02.Server
    • 2.01 Server详解与面试要点
  • PART03.路由树
    • 3.01 路由树-Beego&GIN&Echo实现与设计总结
    • 3.02 路由树-全静态匹配
    • 3.03 路由树-TDD起步
    • 3.04 路由树-静态匹配测试用例
    • 3.05 路由树-静态匹配之路由查找
    • 3.06 路由树-静态匹配之集成Server
    • 3.07 路由树-通配符匹配之路由注册
    • 3.08 路由树-通配符匹配之路由查找与测试
    • 3.09 路由树-参数路径之基本注册和查找
    • 3.10 路由树-参数路径之校验
    • 3.11 路由树-参数路径之参数值
    • 3.12 路由树-总结与面试要点
  • PART04.课后复习
    • 4.01 课后复习-Server
    • 4.02 课后复习-Route
  • PART05.Context
    • 5.01 Context-简介
    • 5.02 Context-Beego Context设计分析
    • 5.03 Context-Gin Context设计分析
    • 5.04 Context-Echo和Iris的Context设计分析
    • 5.05 Context-处理输入输出总结
    • 5.06 Context-处理输入之Body输入
    • 5.07 Context-处理输入之表单输入
    • 5.08 Context-处理输入之查询参数、路径参数和StringValue
    • 5.09 Context-处理输出
    • 5.10 Context-总结与面试要点
  • PART06.AOP
    • 6.01 AOP简介与不同框架设计概览
    • 6.02 AOP设计方案-Middleware
  • PART07.Middleware
    • 7.01 Middleware-AccessLog
    • 7.02 Middleware-Trace简介和OpenTelemetry
    • 7.03 Middleware-OpenTelemetry测试
    • 7.04 Middleware-OpenTelemetry总结
    • 7.05 Prometheus详解
    • 7.06 Middleware-Prometheus
    • 7.07 Middleware-错误页面
    • 7.08 Middleware-从panic中恢复
    • 7.09 Middleware总结和面试
  • PART08.Review
    • 8.01 课后复习-AOP
    • 8.02 课后复习-Context
    • 8.03 课后复习-Middleware-AccessLog
  • PART09.Appendix
    • 附录1.责任链模式
    • 附录2.生成器模式
    • 附录3.函数选项模式
  • xiaochengxu
    • 01.原力去水印
    • 02.KeePass密码管理:安全轻松的管理您的密码
Powered by GitBook
On this page
  • PART1. 注册路由的注意事项
  • PART2. 为什么在注册路由用panic?
  • 2.1 可不可以返回error?
  • 2.2 应不应该返回error?
  • PART3. 路由树是线程安全的吗?
  • PART4. 面试要点(一)
  • 4.1 路由树算法的实现?
  • 4.2 路由匹配的优先级?
  • 4.3 路由查找会回溯吗?
  • 4.4 Web框架是怎么组织路由树的?
  • PART5. 面试要点(二)
  • 5.1 路由查找的性能受什么影响? 或者说怎么评估路由查找的性能?
  • 5.2 路由树是线程安全的吗?
  • 5.3 具体匹配方式的实现原理?
  1. PART03.路由树

3.12 路由树-总结与面试要点

本节课工程结构如下:

(base) yanglei@yuanhong 17-summary % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
├── matchNode.go
├── node.go
├── router.go
├── router_test.go
└── serverInterface.go

0 directories, 9 files

PART1. 注册路由的注意事项

  • 已经注册了的路由,无法被覆盖.例如/user/home注册两次,会冲突

  • path必须以/开始并且结尾不能有/,中间也不允许有连续的/

  • 不能在同一个位置注册不同的参数路由.例如/user/:id和/user/:name冲突

  • 不能在同一个位置同时注册通配符路由和参数路由.例如/user/:id和/user/*冲突

  • 同名路径参数,在路由匹配的时候,值会被覆盖.例如/user/:id/abc/:id,那么/user/123/abc/456,最终id = 456

    • TODO:这里是可以实现把2个id都记录下来的,只需要将matchNode.pathParams的类型修改为map[string][]string即可.下去自己试一下

    • TODO:这种情况也可以考虑panic掉

把以上内容作为注释放在addRoute()方法上,那么但凡用户发现panic的时候看一眼这个注释,都知道出了什么问题

router.go:

// AddRoute 注册路由到路由森林中的路由树上
// 其中path为路由的路径.该路径:
// 1. 不得为空字符串
// 2. 必须以"/"开头
// 3. 不能以"/"结尾
// 4. 不能包含连续的"/"
// - 已经注册了的路由,无法被覆盖.例如`/user/home`注册两次,会冲突
// - `path`必须以`/`开始并且结尾不能有`/`,中间也不允许有连续的`/`
// - 不能在同一个位置注册不同的参数路由.例如`/user/:id`和`/user/:name`冲突
// - 不能在同一个位置同时注册通配符路由和参数路由.例如`/user/:id`和`/user/*`冲突
// - 同名路径参数,在路由匹配的时候,值会被覆盖.例如`/user/:id/abc/:id`,那么`/user/123/abc/456`,最终`id = 456`
func (r *router) addRoute(method string, path string, handleFunc HandleFunc)

PART2. 为什么在注册路由用panic?

可以看到在addRoute()方法中有大量的panic出现.

那么问题来了:

  • 可不可以返回error?

  • 应不应该返回error?

2.1 可不可以返回error?

可以返回,但没必要.因为框架的使用者根本不会去处理的.注册路由都失败了,谁还有空去处理注册路由的报错信息呢?

2.2 应不应该返回error?

其实不应该.从框架使用者的角度上来看,用户必须完成注册路由的步骤,才能启动HTTPServer.那么在注册路由时,采用panic就是没问题的,因为这时应用还没运行.

注册路由时panic总好过应用启动了再panic.

PART3. 路由树是线程安全的吗?

显然不是线程安全的.我们要求用户必须要注册完路由才能启动HTTPServer.而正常的用法都是在启动之前依次注册路由,不存在并发场景.

换言之,这是一个顺序写并发读的场景.并不会出问题.并发读写才会引入线程安全问题.

至于运行期间动态注册路由,没必要支持.这是典型的为了解决1%的问题,引入99%的代码.

之所以不将路由树做成线程安全的,也是为了性能,因为一旦涉及到加锁/原子操作的情况,性能就下来了.

PART4. 面试要点(一)

4.1 路由树算法的实现?

核心就是前缀树.前缀的意思就是,两个节点共同的前缀,将会被抽取出来作为父节点.在我们的实现中是按照/来切割,每一段作为一个节点.

4.2 路由匹配的优先级?

本质上这是和Web框架相关的.在我们的设计里面是静态匹配 > 路径参数 > 通配符匹配.但实际上这个规则意义不大,因为路径参数和通配符匹配是互斥的

4.3 路由查找会回溯吗?

这也是和Web框架相关的,我们在课程上是不支持的.在这里可以简单描述可回溯和不可回溯之间的区别,可以是用课程中的例子/user/123/home和/user/*/*.课程中不支持是因为这个特性非常鸡肋

4.4 Web框架是怎么组织路由树的?

一个HTTP动词就是一颗路由树.也可以考虑所有HTTP动词共用一颗路由树,每个节点标记自己支持的HTTP方法.前者是比较主流的

TODO:第二种可以尝试去写

PART5. 面试要点(二)

5.1 路由查找的性能受什么影响? 或者说怎么评估路由查找的性能?

核心是看路由树的高度,次要因素是路由树的宽度(想想我们的node.children字段).

但是我们实现的时候node.children字段用的是map来实现的,所以宽度对我们的实现影响不大.但是如果使用slice实现,宽度影响就比较大了.

GIN的最长前缀树,则树会更高;我们的实现中,树会更宽.

如果你不是为了装逼,不要写GIN的那种路由树,因为代码很复杂,且很难读懂.

5.2 路由树是线程安全的吗?

严格来说也是跟Web框架相关的.大多数都不是线程安全的,这是为了性能.所以才要求大家一定要先注册路由,后启动Web服务器.如果你有运行期间动态添加路由的需求,只需要利用装饰器模式,就可以将一个线程不安全的封装为线程安全的路由树.

5.3 具体匹配方式的实现原理?

课程上我们实现了静态匹配、通配符匹配和路径匹配,作业里面要求大家照着实现一个正则匹配.其实核心就是划定优先级,然后一种种匹配方式挨个匹配过去.

Last updated 9 months ago