不会飞的章鱼

熟能生巧,勤能补拙;念念不忘,必有回响。

Go高级工程师_第8课_深⼊ web 框架原理与实现

middleware 实现

middleware/filter 实现

我们有这样⼀个 hello world 的业务逻辑:

如果想要统计每个请求花的时间:

随着业务的迭代,接⼝会逐渐增加:

公司发展壮⼤后,我们希望能把耗时可视化:

基本思路是,把功能性(业务代码)和⾮功能性(⾮业务代码)分离

  • 和 HandlerFunc 有相同签名的函数可以强制转换为 HandlerFunc
  • HandlerFunc 实现了 ServeHTTP ⽅法,所以 HandlerFunc 实现了 http.Handler 接⼝

多个中间件⼀般这么写就好了

不过这样写很丑,一般这样写:

实现也很简单:

⼀个 http 的请求处理过程:

![])(./go-senior-engineer-8-in-depth-web-framework-principle-and-realization/http.png)

常⻅ middleware

https://github.com/gin-gonic/contrib

router 实现

Router 本质

![])(./go-senior-engineer-8-in-depth-web-framework-principle-and-realization/rt.png)

字典树 trie

  • 单个节点代表⼀个字⺟
  • 如果需要对字符串进⾏匹配
  • 只要从根节点开始依次匹配即可

Radix Tree

In computer science, a radix tree (also radix trie or compact prefix tree) is a data structure that represents a space-optimized trie (prefix tree) in which each node that is the only child is merged with its parent. The result is that the number of children of every internal node is at most the radix r of the radix tree, where r is a positive integer and a power x of 2, having x ≥ 1. Unlike regular trees, edges can be labeled with sequences of elements as well as single elements. This makes radix trees much more efficient for small sets (especially if the strings are long) and for sets of strings that share long prefixes.

Radix tree

同⼀个 URI 在 HTTP 规范中会有多个⽅法

每⼀个 method 对应⼀棵 radix tree:

httprouter 实现细节

node

就是 httprouter 树中的节点。

nType

就是 node type,有⼏种枚举值:

1
2
3
4
• static // ⾮根节点的普通字符串节点
• root // 根节点
• param(wildcard) // 参数节点,例如 :id
• catchAll // 通配符节点,例如 *anyway

path


到达节点时,所经过的字符串路径

indices


⼦节点索引,当⼦节点为⾮参数类型,即本节点的 wildChild 为 false时,会将每个⼦节点的⾸字⺟放在该索引数组。说是数组,实际上是个string。


如果⼦节点为参数节点时,indices 应该是个空字符串。

wildChild

如果⼀个节点的⼦节点中有 param(wildcard) 节点,那么该节点的 wildChild 字段即为 true。

catchAll

以 * 结尾的路由,即为 catchAll。在静态⽂件服务上,catchAll ⽤的⽐较多。后⾯的部分⼀般⽤来描述⽂件路径。如:/software/downloads/monodraw-latest.dmg。

Radix Tree 的构造过程

第⼀条路由

插⼊ /marketplace_listing/plans/

第⼆条路由

插⼊ /marketplace_listing/plans/:id/acounts

第三条路由

插⼊ /search

路由冲突

conflict:
GET /user/info/:name
GET /user/:id

no conflict:
GET /user/info/:name
POST /user/:id

路由冲突细则

  • 在插⼊ wildcard 节点时,⽗节点的 children 数组⾮空且 wildChild 被设置为 false。
    例如:GET /user/getAll 和 GET /user/:id/getAddr,或者 GET /user/*aaa和 GET /user/:id。
  • 在插⼊ wildcard 节点时,⽗节点的 children 数组⾮空且 wildChild 被设置为 true,但该⽗节点的 wildcard ⼦节点要插⼊的 wildcard 名字不⼀样。
    例如: GET /user/:id/info 和 GET /user/:name/info。
  • 在插⼊ catchAll 节点时,⽗节点的 children ⾮空。
    例如: GET /src/abc和 GET /src/*filename,或者 GET /src/:id 和 GET /src/*filename。
  • 在插⼊ static 节点时,⽗节点的 wildChild 字段被设置为 true。 在插⼊static 节点时,⽗节点的 children ⾮空,且⼦节点 nType 为 catchAll。


validator 实现

为什么需要 validator

看看这坨代码:

使⽤ early return/guard clause 重构之后,代码还是太丑:

validator 改造:

validator 基本原理

深度优先遍历就可以了

request binder 实现

Gin 的 binding

HTTP header : Content-Type

简单工厂模式

不同的⼯⼚实现很简单,就是某种 codec 的 unmarshal

sql binder 实现

标准库的 API 难⽤

  • 标准库的 API 难⽤且容易犯错
  • 有⽆数的新⼿ Gopher 倒在 sql.Rows 忘记关闭的坑下

SQL binder 实现

实现很简单,只要能把特殊开头的单词提取出来就可以了,⽐如:开头,@开头,$开头。

go-micro 实现

插件化原理:

References

维基百科_基数树
《Go ⾼级编程》的 web 部分
Go 夜读 go-micro 分享

------ 本文结束------
如果本篇文章对你有帮助,可以给作者加个鸡腿~(*^__^*),感谢鼓励与支持!