mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-29 08:12:21 +00:00
fix desc for restful
This commit is contained in:
parent
efc0e93e9c
commit
47ac7a8d78
@ -2,9 +2,25 @@
|
|||||||
|
|
||||||
在常见的 web 框架中,router 是必备的组件。golang 圈子里 router 也时常被称为 http 的 multiplexer。在上一节中我们通过对 Burrow 代码的简单学习,已经知道如何用 http 标准库中内置的 mux 来完成简单的路由功能了。如果开发 web 系统对路径中带参数没什么兴趣的话,用 http 标准库中的 mux 就可以。
|
在常见的 web 框架中,router 是必备的组件。golang 圈子里 router 也时常被称为 http 的 multiplexer。在上一节中我们通过对 Burrow 代码的简单学习,已经知道如何用 http 标准库中内置的 mux 来完成简单的路由功能了。如果开发 web 系统对路径中带参数没什么兴趣的话,用 http 标准库中的 mux 就可以。
|
||||||
|
|
||||||
restful 是几年前刮起的 API 设计风潮,在 restful 中使用了 http 标准库还没有支持的一些语义。来看看 restful 中常见的请求路径:
|
restful 是几年前刮起的 API 设计风潮,在 restful 中除了 GET 和 POST 之外,还使用了 http 协议定义的几种其它的标准化语义。具体包括:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
const (
|
||||||
|
MethodGet = "GET"
|
||||||
|
MethodHead = "HEAD"
|
||||||
|
MethodPost = "POST"
|
||||||
|
MethodPut = "PUT"
|
||||||
|
MethodPatch = "PATCH" // RFC 5789
|
||||||
|
MethodDelete = "DELETE"
|
||||||
|
MethodConnect = "CONNECT"
|
||||||
|
MethodOptions = "OPTIONS"
|
||||||
|
MethodTrace = "TRACE"
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
来看看 restful 中常见的请求路径:
|
||||||
|
|
||||||
|
```shell
|
||||||
GET /repos/:owner/:repo/comments/:id/reactions
|
GET /repos/:owner/:repo/comments/:id/reactions
|
||||||
|
|
||||||
POST /projects/:project_id/columns
|
POST /projects/:project_id/columns
|
||||||
@ -19,6 +35,7 @@ DELETE /user/starred/:owner/:repo
|
|||||||
如果我们的系统也想要这样的 URI 设计,使用标准库的 mux 显然就力不从心了。
|
如果我们的系统也想要这样的 URI 设计,使用标准库的 mux 显然就力不从心了。
|
||||||
|
|
||||||
## httprouter
|
## httprouter
|
||||||
|
|
||||||
较流行的开源 golang web 框架大多使用 httprouter,或是基于 httprouter 的变种对路由进行支持。前面提到的 github 的参数式路由在 httprouter 中都是可以支持的。
|
较流行的开源 golang web 框架大多使用 httprouter,或是基于 httprouter 的变种对路由进行支持。前面提到的 github 的参数式路由在 httprouter 中都是可以支持的。
|
||||||
|
|
||||||
因为 httprouter 中使用的是显式匹配,所以在设计路由的时候需要规避一些会导致路由冲突的情况,例如:
|
因为 httprouter 中使用的是显式匹配,所以在设计路由的时候需要规避一些会导致路由冲突的情况,例如:
|
||||||
@ -40,15 +57,15 @@ panic: wildcard route ':id' conflicts with existing children in path '/user/:id'
|
|||||||
|
|
||||||
goroutine 1 [running]:
|
goroutine 1 [running]:
|
||||||
github.com/cch123/httprouter.(*node).insertChild(0xc4200801e0, 0xc42004fc01, 0x126b177, 0x3, 0x126b171, 0x9, 0x127b668)
|
github.com/cch123/httprouter.(*node).insertChild(0xc4200801e0, 0xc42004fc01, 0x126b177, 0x3, 0x126b171, 0x9, 0x127b668)
|
||||||
/Users/caochunhui/go_work/src/github.com/cch123/httprouter/tree.go:256 +0x841
|
/Users/caochunhui/go_work/src/github.com/cch123/httprouter/tree.go:256 +0x841
|
||||||
github.com/cch123/httprouter.(*node).addRoute(0xc4200801e0, 0x126b171, 0x9, 0x127b668)
|
github.com/cch123/httprouter.(*node).addRoute(0xc4200801e0, 0x126b171, 0x9, 0x127b668)
|
||||||
/Users/caochunhui/go_work/src/github.com/cch123/httprouter/tree.go:221 +0x22a
|
/Users/caochunhui/go_work/src/github.com/cch123/httprouter/tree.go:221 +0x22a
|
||||||
github.com/cch123/httprouter.(*Router).Handle(0xc42004ff38, 0x126a39b, 0x3, 0x126b171, 0x9, 0x127b668)
|
github.com/cch123/httprouter.(*Router).Handle(0xc42004ff38, 0x126a39b, 0x3, 0x126b171, 0x9, 0x127b668)
|
||||||
/Users/caochunhui/go_work/src/github.com/cch123/httprouter/router.go:262 +0xc3
|
/Users/caochunhui/go_work/src/github.com/cch123/httprouter/router.go:262 +0xc3
|
||||||
github.com/cch123/httprouter.(*Router).GET(0xc42004ff38, 0x126b171, 0x9, 0x127b668)
|
github.com/cch123/httprouter.(*Router).GET(0xc42004ff38, 0x126b171, 0x9, 0x127b668)
|
||||||
/Users/caochunhui/go_work/src/github.com/cch123/httprouter/router.go:193 +0x5e
|
/Users/caochunhui/go_work/src/github.com/cch123/httprouter/router.go:193 +0x5e
|
||||||
main.main()
|
main.main()
|
||||||
/Users/caochunhui/test/go_web/httprouter_learn2.go:18 +0xaf
|
/Users/caochunhui/test/go_web/httprouter_learn2.go:18 +0xaf
|
||||||
exit status 2
|
exit status 2
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -56,7 +73,7 @@ exit status 2
|
|||||||
|
|
||||||
除支持路径中的 wildcard 参数之外,httprouter 还可以支持 `*` 号来进行通配,不过 `*` 号开头的参数只能放在路由的结尾,例如下面这样:
|
除支持路径中的 wildcard 参数之外,httprouter 还可以支持 `*` 号来进行通配,不过 `*` 号开头的参数只能放在路由的结尾,例如下面这样:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
Pattern: /src/*filepath
|
Pattern: /src/*filepath
|
||||||
|
|
||||||
/src/ filepath = ""
|
/src/ filepath = ""
|
||||||
@ -78,15 +95,16 @@ r.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|||||||
或者内部 panic 的时候:
|
或者内部 panic 的时候:
|
||||||
```go
|
```go
|
||||||
r.PanicHandler = func(w http.ResponseWriter, r *http.Request, c interface{}) {
|
r.PanicHandler = func(w http.ResponseWriter, r *http.Request, c interface{}) {
|
||||||
log.Printf("Recovering from panic, Reason: %#v", c.(error))
|
log.Printf("Recovering from panic, Reason: %#v", c.(error))
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte(c.(error).Error()))
|
w.Write([]byte(c.(error).Error()))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
目前开源界最为流行(star 数最多)的 web 框架 [gin](https://github.com/gin-gonic/gin) 使用的就是 httprouter 的变种。
|
目前开源界最为流行(star 数最多)的 web 框架 [gin](https://github.com/gin-gonic/gin) 使用的就是 httprouter 的变种。
|
||||||
|
|
||||||
## 原理
|
## 原理
|
||||||
|
|
||||||
httprouter 和众多衍生 router 使用的数据结构被称为 radix tree,压缩字典树。读者可能没有接触过压缩字典树,但对字典树 trie tree 应该有所耳闻。下图是一个典型的字典树结构:
|
httprouter 和众多衍生 router 使用的数据结构被称为 radix tree,压缩字典树。读者可能没有接触过压缩字典树,但对字典树 trie tree 应该有所耳闻。下图是一个典型的字典树结构:
|
||||||
|
|
||||||

|

|
||||||
@ -100,6 +118,7 @@ httprouter 和众多衍生 router 使用的数据结构被称为 radix tree,
|
|||||||
每个节点上不只存储一个字母了,这也是压缩字典树中“压缩”的主要含义。使用压缩字典树可以减少树的层数,同时因为每个节点上数据存储也比通常的字典树要多,所以程序的局部性较好(一个节点的 path 加载到 cache 即可进行多个字符的对比),从而对 CPU 缓存友好。
|
每个节点上不只存储一个字母了,这也是压缩字典树中“压缩”的主要含义。使用压缩字典树可以减少树的层数,同时因为每个节点上数据存储也比通常的字典树要多,所以程序的局部性较好(一个节点的 path 加载到 cache 即可进行多个字符的对比),从而对 CPU 缓存友好。
|
||||||
|
|
||||||
## 压缩字典树创建过程
|
## 压缩字典树创建过程
|
||||||
|
|
||||||
我们来跟踪一下 httprouter 中,一个典型的压缩字典树的创建过程,路由设定如下:
|
我们来跟踪一下 httprouter 中,一个典型的压缩字典树的创建过程,路由设定如下:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -114,22 +133,25 @@ GET /support
|
|||||||
补充路由:
|
补充路由:
|
||||||
GET /marketplace_listing/plans/ohyes
|
GET /marketplace_listing/plans/ohyes
|
||||||
```
|
```
|
||||||
|
|
||||||
最后一条补充路由是我们臆想的,除此之外所有 API 路由均来自于 api.github.com。
|
最后一条补充路由是我们臆想的,除此之外所有 API 路由均来自于 api.github.com。
|
||||||
|
|
||||||
### root 节点创建
|
### root 节点创建
|
||||||
|
|
||||||
httprouter 的 Router struct 中存储压缩字典树使用的是下述数据结构:
|
httprouter 的 Router struct 中存储压缩字典树使用的是下述数据结构:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// 略去了其它部分的 Router struct
|
// 略去了其它部分的 Router struct
|
||||||
type Router struct {
|
type Router struct {
|
||||||
// ...
|
// ...
|
||||||
trees map[string]*node
|
trees map[string]*node
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
trees 中的 key 即为 http 1.1 的 RFC 中定义的各种 method,具体有:
|
trees 中的 key 即为 http 1.1 的 RFC 中定义的各种 method,具体有:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
GET
|
GET
|
||||||
HEAD
|
HEAD
|
||||||
OPTIONS
|
OPTIONS
|
||||||
@ -159,17 +181,19 @@ path: 当前节点对应的路径中的字符串
|
|||||||
wildChild: 子节点是否为参数节点,即 wildcard node,或者说 :id 这种类型的节点
|
wildChild: 子节点是否为参数节点,即 wildcard node,或者说 :id 这种类型的节点
|
||||||
|
|
||||||
nType: 当前节点类型,有四个枚举值: 分别为 static/root/param/catchAll。
|
nType: 当前节点类型,有四个枚举值: 分别为 static/root/param/catchAll。
|
||||||
static // 非根节点的普通字符串节点
|
static // 非根节点的普通字符串节点
|
||||||
root // 根节点
|
root // 根节点
|
||||||
param // 参数节点,例如 :id
|
param // 参数节点,例如 :id
|
||||||
catchAll // 通配符节点,例如 *anyway
|
catchAll // 通配符节点,例如 *anyway
|
||||||
|
|
||||||
indices: 子节点索引,当子节点为非参数类型,即本节点的 wildChild 为 false 时,会将每个子节点的首字母放在该索引数组。说是数组,实际上是个 string。
|
indices: 子节点索引,当子节点为非参数类型,即本节点的 wildChild 为 false 时,会将每个子节点的首字母放在该索引数组。说是数组,实际上是个 string。
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
当然,PUT 路由只有唯一的一条路径。接下来,我们以后续的多条 GET 路径为例,讲解子节点的插入过程。
|
当然,PUT 路由只有唯一的一条路径。接下来,我们以后续的多条 GET 路径为例,讲解子节点的插入过程。
|
||||||
|
|
||||||
### 子节点插入
|
### 子节点插入
|
||||||
|
|
||||||
当插入 `GET /marketplace_listing/plans` 时,类似前面 PUT 的过程,GET 树的结构如图所示:
|
当插入 `GET /marketplace_listing/plans` 时,类似前面 PUT 的过程,GET 树的结构如图所示:
|
||||||

|

|
||||||
|
|
||||||
@ -184,6 +208,7 @@ indices: 子节点索引,当子节点为非参数类型,即本节点的 wild
|
|||||||
上面这种情况比较简单,新的路由可以直接作为原路由的子节点进行插入。实际情况不会这么美好。
|
上面这种情况比较简单,新的路由可以直接作为原路由的子节点进行插入。实际情况不会这么美好。
|
||||||
|
|
||||||
### 边分裂
|
### 边分裂
|
||||||
|
|
||||||
接下来我们插入 `GET /search`,这时会导致树的边分裂。
|
接下来我们插入 `GET /search`,这时会导致树的边分裂。
|
||||||
|
|
||||||

|

|
||||||
|
Loading…
x
Reference in New Issue
Block a user