httprouter学习

1. 什么是httprouter

较为流行的go web框架大多使用httprouter,或者是基于httprouter的变种对路由进行支持。

httprouter使用的是显式匹配,所以在路由设计的时候会存在一些路由冲突的问题:

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

 

上面这两个路由就会产生冲突,冲突的规则很简单,当两个路由是一样的HTTP方法和路由请求前缀,但是在某个位置出现了一个路由是wildcard(:id),另一个路由是普通字符串(info),就会发生路由冲突。

 

除了支持路径中的wildcard之外,httprouter还可以支持 * 通配符,但是 * 开头的参数只能放在路由的结尾:

Pattern: /src/*filepath

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

 

其实稍微思考一下上面这两个限制,可以看出这是httprouter数据结构上所带来的问题,使用字典树进行匹配时会产生这样的冲突或者通配限制

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

r := httprouter.New()
r.NotFound = http.HandlerFunc( func (w http.ResponseWriter, r *http.Request){
   w.Write([]byte( "oh no, not found" ))     
})

  

2. httprouter原理

httprouter使用的数据结构就是压缩字典树。

为什么使用压缩字典树?——普通的字典树有个比较明显的缺点,就是每个字母都需要建立一个孩子节点,这样会导致字典树的层数比较深,压缩字典树相对好的平衡了字典树的优点和缺点,可以更好的用于前缀查询,典型的就是路由结构

典型的压缩字典树结构如下:

 

可以看到压缩字典树每个节点不只是一个字母,使用压缩字典树可以有效地减少树的层数,所以程序的局部性较好,从而对CPU友好

 

2.1 压缩字典树的创建过程

我们现在想要设置这样的一组路由:

PUT /user/installations/:installation_id/repositories/:repository_id

GET /marketplace_listing/plans/
GET /marketplace_listing/plans/:id/accounts
GET /search
GET /status
GET /support

补充路由:
GET /marketplace_listing/plans/ohyes

 

在httprouter中,每一种方法都对应着一个压缩字典树,这些方法与压缩字典树以map的方式关联起来:

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

 

我们的第一个路由是:PUT /user/installations/:installation_id/repositories/:repository_id

当对一个http方法创建第一个路由的时候,会创建一个新的字典树,那么这个PUT请求所对应的字典树就是这样的:

 

其中一些字段的释义为:

  • path:当前节点对应的路径中的字符串
  • wildChild:子节点是否为参数节点,类似于:id
  • nType:当前节点类型:static--非根节点的普通字符串节点,root--根节点,param--参数节点,catchAll:通配符节点,例如*anyway
  • indices:子节点索引,当子节点为非参数类型,即本节点的wildChild为false时,会将每个子节点的首字母放在该索引数组

 

下面是第二个路由:GET /marketplace_listing/plans/:id/accounts

是一个新的GET请求,所以会新创建一个字典树:

 

 

当我们插入第三个路由:GET /search

因为之前存在一颗GET字典树了,而根节点是/market...,这和我们的新的路由丝毫无关,所以要抽离出一个新的根节点,使用二者重叠的前缀即可,目前只有 / 

 

 

同样地,我们继续插入后面两个路由:GET /status 和 GET /support,可以观察一下字典树的插入过程

 

 

2.2 探究路由冲突问题

当路由本身全都是字符串时,不会产生冲突,只有当出现了wildcard才可能会有冲突

让我们探究一下是什么造成了之前说的同一个http方法下的路由冲突问题:

  • 当插入wildcard节点时,父节点的children数组非空并且wildChild=false,那么自然这个wildcard节点就会产生冲突
  • 当插入wildcard节点时,父节点的children数组非空且wildChild=true,但是该父节点的wildcard子节点要插入的wildcard名字不一样:GET /user/:id/info GET /user/:name/info
  • 在插入catchAll节点时,父节点的children数组非空
  • 在插入static节点时,父节点的wildChild=true
  • 在插入static节点时,父节点的children非空,且子节点的nType=catchAll

 

posted @ 2022-03-26 15:59  aganippe  阅读(613)  评论(0编辑  收藏  举报