惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
宝玉的分享
宝玉的分享
小众软件
小众软件
有赞技术团队
有赞技术团队
月光博客
月光博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
MyScale Blog
MyScale Blog
Engineering at Meta
Engineering at Meta
Stack Overflow Blog
Stack Overflow Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
D
Docker
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MongoDB | Blog
MongoDB | Blog
WordPress大学
WordPress大学
J
Java Code Geeks
罗磊的独立博客
V
Visual Studio Blog
雷峰网
雷峰网
H
Help Net Security
T
The Blog of Author Tim Ferriss
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
大猫的无限游戏
大猫的无限游戏
F
Fortinet All Blogs

Mohuishou

如何实现支持多集群的 Kubernetes Operator? 第三方应用如何调用我们 kubebuilder 生成的自定义资源? Kubernetes 简明教程 k8s job 为何迟迟不能结束? Go 工程化(十一) 如何优雅的写出 repo 层代码 Go 工程化(十) 如何在整洁架构中使用事务? 给博客添加章节目录 使用 Notion Database 管理静态博客文章 一个普通 Go 开发的三年 4. localhost 就一定是 localhost 么? Go可用性(七) 总结: 一张图串联可用性知识点 Go可用性(六) 熔断 10. 总结 9. kubebuilder 进阶: 源码分析 8. kubebuilder 进阶: webhook 7. kubebuilder 进阶: 测试 6. kubebuilder 实战: status & event 5. kubebuilder 实战: CRUD 4. kustomize 简明教程 3. KubeBuilder 简明教程 2. Kind: 如何快速搭建本地 K8s 开发环境? 1. Operator概述: 如何对 Kubernetes 进行扩展 Go可用性(五) 自适应限流 Go可用性(四) 漏桶算法 Go可用性(三) 令牌桶的实现 rate/limt Go可用性(二) 令牌桶原理及使用 Go可用性(一) 隔离设计 Go并发编程(十二) Singleflight Go工程化(九) 项目重构实践 Go工程化(八) 单元测试
Httprouter介绍及源码阅读
Mohuishou · 2018-08-02 · via Mohuishou

注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用

在上一篇文章当中阅读了 Go 语言的一个高性能的 Web 框架 Gin,Web 框架当中最重要的功能之一是路由,Gin 的路由就是由 httprouter 这个包实现的

地址

特性

  • 基于基数树实现的高性能路由框架
  • 仅支持精确匹配
  • 不必关心 URL 结尾的斜线
  • 路径自动校正,例如在 url 路径当中有../,//的时候
  • 可以在 URL 当中设置参数,例如/user/:id
  • 零内存分配
  • 不存在服务器崩溃,可以通过设置panic handler使服务器从 panic 当中恢复
  • 适合 API 构建

源码

两个问题

解决两个问题,就基本明白了这个路由框架

  • 路由是是如何注册?如何保存的?
  • 当请求到来之后,路由是如何匹配,如何查找的?

一个 Demo

还是从一个Hello World讲起

1
2
3
4
5
6
7
func main()  {
r := httprouter.New()
r.GET("/:name", func(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
fmt.Fprintf(writer, "hello, %s!\n", params.ByName("name"))
})
http.ListenAndServe(":8080",r)
}

httprouter.New()初始化了一个 Router,下面直接看一下 Router 的结构

Router

在 Router 的源码当中有十分详尽的注释,这里按照我个人的理解注释一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

// Router实现了Http.Handler接口,用于注册分发路由
type Router struct {
// trees 是一个基数树集合,每一个HTTP方法对应一棵单独的路由树
// node是基数树的根节点
trees map[string]*node

// 用于开启上文提到的自动处理URL尾部斜杆的特性
// 这个值为true时,如果/foo/没有被匹配到,会尝试匹配/foo
RedirectTrailingSlash bool

// 用于开启上文提到的路由校正的特性
// 这个值为true时,会对../和//这种路径进行校正
RedirectFixedPath bool

// 这个值为true时,如果当前方法的路由没有被匹配到,会尝试匹配其他方法的路由,
// 如果匹配到了则返回405,如果没有,就交给NotFound Handler处理
HandleMethodNotAllowed bool

// 这个值为true时,将开启OPTIONS自动匹配,注意: 手动匹配优先级更高
HandleOPTIONS bool

// 没有匹配到相应路由的时候会调用这个方法
// 如果没有注册这个方法会返回 NotFound
NotFound http.Handler

// 没有匹配到相应路由并且HandleMethodNotAllowed为true时会调用这个方法
MethodNotAllowed http.Handler

// 用于从panic当中恢复
// 需要返回500错误,并且渲染相应的错误页面
PanicHandler func(http.ResponseWriter, *http.Request, interface{})
}

初始化 Router 之后看看路由是如何保存并且注册的

路由是如何保存的?

这里以官方 Readme 当中的例子说明:
如果注册了以下路由

1
2
3
4
5
6
7
8
r.GET("/", f1)
r.GET("/search/", f2)
r.GET("/support/", f3)
r.GET("/blog/", f4)
r.GET("/blog/:post/", f5)
r.GET("/about_us/", f6)
r.GET("/about_us/team/", f7)
r.GET("/contact/", f8)

那么这些路由会如下方所示,以一颗树的形式保存,并且这些路由的公共前缀会被抽离并且变为上一层节点
Priority 表示加上自身一共有多少个节点
Path 表示路径
Handle 表示路由注册的方法

1
2
3
4
5
6
7
8
9
10
11
Priority   Path             Handle
9 \ *<1>
3 ├s nil
2 |├earch\ *<2>
1 |└upport\ *<3>
2 ├blog\ *<4>
1 | └:post nil
1 | └\ *<5>
2 ├about-us\ *<6>
1 | └team\ *<7>
1 └contact\ *<8>

r.Handle

r.Get, r.Post等方法实质都是通过调用 r.Handle 实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func (r *Router) Handle(method, path string, handle Handle) {
// 路径注册必须从/开始,否则直接报错
if path[0] != '/' {
panic("path must begin with '/' in path '" + path + "'")
}

// 路由树map不存在需要新建
if r.trees == nil {
r.trees = make(map[string]*node)
}

// 获取当前方法所对应树的根节点,不存在则新建一个
root := r.trees[method]
if root == nil {
root = new(node)
r.trees[method] = root
}

// 向路由树当中添加一条一条路由
root.addRoute(path, handle)
}

node

路由是注册到一颗路由树当中的,先看看节点的源码,再来分析,是如何添加路由的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
type node struct {
// 当前节点的路径
path string

// 是否为参数节点,参数节点用:name表示
wildChild bool

// 当前节点类型, 一共有4种
// static: 静态节点,默认类型
// root: 根节点
// param: 其他节点
// catchAll: 带有*的节点,这里*的作用和正则当中的*一样
nType nodeType

// 当前路径上最大参数的个数,不能超过255
maxParams uint8

// 代表分支的首字母
// 上面的例子,当前节点为s
// 那么indices = eu
// ├s nil
// |├earch\ *<2>
// |└upport\ *<3>
indices string

// 孩子节点
children []*node

// 注册的路由
handle Handle

// 权重,表示当前节点加上所有子节点的数目
priority uint32
}

路由树是如何生成的?

未完待续

关注我获取更新

猜你喜欢