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

推荐订阅源

B
Blog RSS Feed
Jina AI
Jina AI
雷峰网
雷峰网
Blog — PlanetScale
Blog — PlanetScale
Hugging Face - Blog
Hugging Face - Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
博客园 - 司徒正美
罗磊的独立博客
J
Java Code Geeks
Engineering at Meta
Engineering at Meta
WordPress大学
WordPress大学
Vercel News
Vercel News
A
About on SuperTechFans
I
InfoQ
D
DataBreaches.Net
爱范儿
爱范儿
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
aimingoo的专栏
aimingoo的专栏
宝玉的分享
宝玉的分享
P
Proofpoint News Feed
Microsoft Azure Blog
Microsoft Azure Blog
美团技术团队

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
}

路由树是如何生成的?

未完待续

关注我获取更新

猜你喜欢