golang中的路由分组

router 路由请求
API RESTful 风格


const (
MethodGet = "GET"
MethodHead = "HEAD"
MethodPost = "POST"
MethodPut = "PUT"
MethodPatch = "PATCH" // RFC 5789
MethodDelete = "DELETE"
MethodConnect = "CONNECT"
MethodOptions = "OPTIONS"
MethodTrace = "TRACE"
)


常见的请求路径

GET /repos/:owner/:repo/comments/:id/reactions

POST /projects/:project_id/columns

PUT /book/starred/:owner/:repo

DELETE /book/starred/:owner/:repo


因为httprouter中使用的是显式匹配,所以在设计路由的时候需要规避一些会导致路由冲突的情况,例如:


案例1:
a. GET /book/info/:name
b. GET /book/:id

如果两个路由拥有一致的http方法(GET)和请求路径前缀,
且在某个位置出现了b路由是(:id这种形式)参数,a路由则是普通字符串,那么就会发生路由冲突。
路由冲突会在初始化阶段直接panic:

案例2:
GET /book/info/:name
POST /book/:id


因为httprouter考虑到字典树的深度,在初始化时会对参数的数量进行限制,所以在路由中的参数数目不能超过255,否则会导致httprouter无法识别后续的参数。

httprouter还可以支持*号来进行通配,不过*号开头的参数只能放在路由的结尾,例如下面这样:

/src/ filepath = ""
/src/somefile.go filepath = "somefile.go"
/src/subdir/somefile.go filepath = "subdir/somefile.go"

除了正常情况下的路由支持,httprouter也支持对一些特殊情况下的回调函数进行定制,例如404的时候:

r := httprouter.New()
r.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("oh no, not found"))
})
或者内部panic的时候:

r.PanicHandler = func(w http.ResponseWriter, r *http.Request, c interface{}) {
log.Printf("Recovering from panic, Reason: %#v", c.(error))
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(c.(error).Error()))
}


httprouter和众多衍生router使用的数据结构被称为压缩字典树(Radix Tree)。


字典树常用来进行字符串检索,例如用给定的字符串序列建立字典树。
对于目标字符串,只要从根节点开始深度优先搜索,即可判断出该字符串是否曾经出现过,时间复杂度为O(n),n可以认为是目标字符串的长度。
字符串本身不像数值类型可以进行数值比较,两个字符串对比的时间复杂度取决于字符串长度。如果不用字典树来完成上述功能,要对历史字符串进行排序,再利用二分查找之类的算法去搜索,时间复杂度只高不低。可认为字典树是一种空间换时间的典型做法。

普通的字典树有一个比较明显的缺点,就是每个字母都需要建立一个孩子节点,这样会导致字典树的层数比较深,压缩字典树相对好地平衡了字典树的优点和缺点。

 

每个节点上不只存储一个字母了,这也是压缩字典树中“压缩”的主要含义。使用压缩字典树可以减少树的层数,同时因为每个节点上数据存储也比通常的字典树要多,所以程序的局部性较好(一个节点的path加载到cache即可进行多个字符的对比),从而对CPU缓存友好。

httprouter的Router结构体中存储压缩字典树使用的是下述数据结构:

type Router struct {
// ...
trees map[string]*node
// ...
}


trees中的key即为HTTP 1.1的RFC中定义的各种方法,具体有:

GET
HEAD
OPTIONS
POST
PUT
PATCH
DELETE


彼此之间不共享数据.
PUT和GET是两棵树而非一棵。


r.PUT("/user/installations/:installation_id/repositories/:reposit", Hello)


radix的节点类型为*httprouter.node,为了说明方便,我们留下了目前关心的几个字段:

path: 当前节点对应的路径中的字符串

wildChild: 子节点是否为参数节点,即 wildcard node,或者说 :id 这种类型的节点

nType: 当前节点类型,有四个枚举值: 分别为 static/root/param/catchAll。
static // 非根节点的普通字符串节点
root // 根节点
param // 参数节点,例如 :id
catchAll // 通配符节点,例如 *anyway

indices:子节点索引,当子节点为非参数类型,即本节点的wildChild为false时,会将每个子节点的首字母放在该索引数组。说是数组,实际上是个string。
当然,PUT路由只有唯一的一条路径


在路由本身只有字符串的情况下,不会发生任何冲突。只有当路由中含有wildcard(类似 :id)或者catchAll的情况下才可能冲突。这一点在前面已经提到了。

子节点的冲突处理很简单,分几种情况:

在插入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。
只要发生冲突,都会在初始化的时候panic。例如,在插入我们臆想的路由GET /marketplace_listing/plans/ohyes时,出现第4种冲突情况:它的父节点marketplace_listing/plans/的wildChild字段为true。

原文地址:https://www.cnblogs.com/webkaifazhe/p/14205167.html