想必很多人在使用 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。。。