Gin 路由器:路径段与现有通配符冲突

Posted

技术标签:

【中文标题】Gin 路由器:路径段与现有通配符冲突【英文标题】:Gin router: path segment conflicts with existing wildcard 【发布时间】:2016-07-21 08:19:02 【问题描述】:

我想让我的应用服务于以下事物。

a.com => 将 /www 提供给浏览器,以便浏览器可以查找 /www/index.html) a.com/js/mylib.js => 将 /www/js/mylib.js 提供给浏览器 a.com/api/v1/disk => 返回 JSON 的典型 REST API a.com/api/v1/memory => 另一个 API

我做了如下代码:

package main

import "github.com/gin-gonic/gin"

func main() 
    r := gin.Default()

    r.Static("/", "/www")

    apiv1 := r.Group("api/v1")
    
        apiv1.GET("/disk", diskSpaceHandler)
        apiv1.GET("/memory", memoryHandler)
        apiv1.GET("/cpu", cpuHandler)
    

    r.Run(":80")

当我运行代码时,它会发生恐慌:

panic: path segment '/api/v1/disk' conflicts with existing wildcard '/*filepath' in path '/api/v1/disk'

我明白它为什么会恐慌,但我不知道如何解决。

我想到了两件事:

    使用 NoRoute() 函数将处理除 /api/v1 组路径以外的其他方法(不知道我是如何实现的) 使用中间件。有静态中间位置https://github.com/gin-gonic/contrib,但代码在 Windows 上不起作用(https://github.com/gin-gonic/contrib/issues/91)

提前谢谢你。

【问题讨论】:

尝试将apiv1 := r.Group("api/v1")改为apiv1 := r.Group("/api/v1") 这是杜松子酒的限制。 github.com/gin-gonic/gin/issues/388 这不是 Gin 限制,而是 httprouter 的功能:github.com/julienschmidt/httprouter/issues/… 我刚刚用 Echo 框架尝试了同样的事情,它就可以工作。我很伤心,因为我真的很喜欢杜松子酒。 【参考方案1】:

这是 Gin 底层路由器实现的intended feature。路由在前缀上匹配,因此任何路径参数或通配符与另一个现有路径段位于相同位置都会导致冲突。

在这个特定的问答中,RouterGroup.Static 方法将通配符 /*filepath 添加到您服务的路线中。如果该路由是 root /通配符将与您声明的所有其他路由冲突

那该怎么办?

您必须接受没有直接的解决方案,因为这正是 Gin 的路由器实现的工作原理。如果您不能接受解决方法,那么您可能必须更改 HTTP 框架。 cmets 提到 Echo 作为替代方案。

1.如果您可以更改路线映射

最好的解决方法是没有解决方法,而是采用 Gin 的设计。然后,您可以简单地为静态文件路径添加一个唯一前缀:r.Static("/static", "/www")。这并不意味着您更改本地目录结构,只是将路径前缀映射到它。请求 URL 必须更改。

2。通配符与一个或几个其他路由冲突

假设你的路由器只有这两条路由:

/*any
/api/foo

在这种情况下,您可能会使用中间处理程序并手动检查路径参数:

r.GET("/*any", func(c *gin.Context) 
    path := c.Param("any")
    if strings.HasPrefix(path, "/api") 
        apiHandler(c) // your regular api handler
     else 
        // handle *any
    
)

3.通配符与许多其他路由冲突

哪个最好取决于你的具体情况和目录结构。

3.1 使用r.NoRoute 处理程序;这可能有效,但很糟糕。

r.NoRoute(gin.WrapH(http.FileServer(gin.Dir("static", false))))
r.GET("/api", apiHandler)

这将提供来自static(或任何其他目录)的文件但是它还将尝试为/api 组下所有不存在的路由提供资产。例如。 /api/xyz 将由 NoRoute 处理程序处理。这可能是可以接受的,直到不是。例如。如果您的静态资产中恰好有一个名为 api 的文件夹。

3.2 使用中间件;这也有一些陷阱。下面是一个人为的实现。您还可以找到gin-contrib/static,它稍微复杂一些,但也有同样的限制。即:

如果您在静态资产中有一个名为 API 路由的目录,它将导致无限重定向(可以通过 Engine#RedirectTrailingSlash = false 缓解) 即使没有无限重定向,中间件也会首先检查本地 FS,只有在没有发现任何内容时才会继续执行链中的下一个处理程序。这意味着您正在对每个请求进行系统调用以检查文件是否存在。 (或者至少这是gin-contrib/static 所做的)
    r := gin.New()
    r.Use(func(c *gin.Context) 
        fname := "static" + c.Request.URL.Path
        if _, err := os.Stat(fname); err == nil 
            c.File(fname)
            c.Abort() // file found, stop the handler chain
        
        // else move on to the next handler in chain
    )
    r.GET("/api", apiHandler)
    r.Run(":5555")

3.3 使用 Gin 子引擎;如果您有很多潜在的冲突,这可能是一个不错的选择,例如/ 上的通配符和带有组和诸如此类的复杂 API 路由。使用子引擎可以让你更好地控制它,但实现仍然感觉很笨拙。这里有一个综合示例:How to serve static files from all routes except one in Gin?

总结

如果可以,请重新设计路线。如果你不能,这个答案提供了三种可能的增加复杂性的解决方法。与往常一样,这些限制可能适用于您的特定用例,也可能不适用于您的特定用例。 YMMV。

如果这些都不适合你,也许可以发表评论指出你的用例是什么。

【讨论】:

【参考方案2】:

你应该使用静态中间件,看它的例子:

https://github.com/gin-contrib/static#canonical-example

r.Use(static.Serve("/", static.LocalFile("/tmp", false)))

【讨论】:

以上是关于Gin 路由器:路径段与现有通配符冲突的主要内容,如果未能解决你的问题,请参考以下文章

Express路由路径匹配规则以及第三方包模块cors

如何从通配符路由重定向到主页?

将新设备添加到现有通配符配置文件

Asyncio 和 aiohttp 将所有 url 路径路由到处理程序

ASP.NET MVC 路由:绕过路径的静态文件处理程序

Golang Gin/Ace/Iris/Echo RBAC 鉴权库