gin、httprouter 的路由冲突

在使用 gin 做 api 开发的时候,遇到了路由冲突的情况。这里记录下冲突的原因,以及解决方案。

场景

在开发一个查看文章的 api 时,设计了如下的路由:

1
2
3
r := gin.Default()
r.Get("/posts/hot",handler) //查看热门文章
r.Get("/posts/:id",handler) //查看某篇文章详情

结果程序会发生 painc,提示路由冲突。

原因

路由冲突的原因,主要是由于 gin 采用了 httprouter 作为路由,如果两个路由的 http method 相同,并且请求路径的前缀相同,如果出现了 /:id /hot 这样的 path,则会产生冲突。

在以前使用的 web 框架中,当出现上面的情况时,一般都会优先匹配 /hot ,然后在进行匹配 /:id,会有一个优先级的判断,但是在 gin 的路由中没有这种处理方案。

httprouter 使用 radix tree 结构来存储路由,保证查证路由时的性能。

/posts 作为其中的一个节点,当 :id 被注册到子节点之后,再次注册 hot 时,程序会判定与 /:id 产生冲突,不能注册。

解决方案

第一种

变化单复数形式,当使用 :id 时,将 /posts/:id 改为 /post/:id

1
2
3
r := gin.Default()
r.Get("/posts/hot",handler) //查看热门文章
r.Get("/post/:id",handler) //查看某篇文章详情

第二种

只保留一条路由,在 handler 中分情况处理。如下

1
2
3
4
5
6
7
8
9
r := gin.Default()
r.Get("/post/:id",handler) //查看某篇文章详情
func handler(c *gin.Context){
if c.Param("id")=="hot"{
hotHandler(c)
return
}
detailHandler(c)
}