想必很多人在使用 Gin 编写路由函数的时候遇到过以下问题:
panic: wildcard route ‘:articleID’ conflicts with existing children in path ‘/articles/:articleID’
如何解决这个问题呢?
原因
出现这个问题主要是因为 golang 很多框架使用 httprouter 作为他们的路由模块。httprouter 为了性能优化使用了显式匹配,这就导致了很多通配符与具体路由产生冲突。
如下列路由就会冲突:
GET /articles/info
GET /articles/:articleID
解决方法
1. 修改路由规则
对于路由规则不是很复杂的项目,可以使用规避传统路由规则的方法,如:
GET /articles/info
GET /article/:articleId
但是这种方法违背了我们常用的 RESTful API 规则,非常不舒服。在项目比较大比较负责的情况下就会非常混乱。
2. 特定路由参数分支
一种简单的解决方法就是在通配符外面包裹一层判断语句。
router.GET("/v1/users/:userID", func(c *gin.Context) {
if c.Param("userID") == "info" {
...
} else {
...
}
})
如果觉得这种写法不易阅读,还可以使用如下判断语句:
router.GET("/v1/users/:userID", func(c *gin.Context) {
if strings.HasPrefix(c.Request.RequestURI, "/v1/users/info") {
...
return
}
...
})
3. 多级路由分支
httprouter 中通配符接通配符是可以被正常识别的,所以在多级路由中可以使用以下方法:
router.GET("/v1/images/:path1", GetHandler) // /v1/images/detail
router.GET("/v1/images/:path1/:path2", GetHandler) // /v1/images/<id>/history
func GetHandler(c *gin.Context) {
path1 := c.Param("path1")
path2 := c.Param("path2")
if path1 == "detail" && path2 == "" {
Detail(c)
} else if path1 != "" && path2 == "history" {
imageId := path1
History(c, imageId)
} else {
HandleHttpError(c, NewHttpError(404, "Page not found"))
}
}
4. 使用 *
号进行通配
httprouter 提供 *
号匹配,匹配效果如下:
/users/*action
/users/ action = ""
/users/somefile.go action = "somefile.go"
/users/subdir/somefile.go action = "subdir/somefile.go"
总结
httprouter 在提供高性能的同时也确实带来了诸多不便,以上几种方法不能说完美,但应该可以满足大部分 RESTful API 的规范要求。
更好等方法其实是————不要用 wildcard。。。