7

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

  • 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'

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

我脑子里只有两件事:

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

先感谢您。

4

2 回答 2

0

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

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

那该怎么办?

您必须接受没有直接的解决方案,因为这正是 Gin 的路由器实现的工作原理。如果您不能接受解决方法,那么您可能必须更改 HTTP 框架。评论提到 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 路由与组等等。使用子引擎可以让你更好地控制它,但实现仍然感觉很笨拙。这里有一个综合示例:如何从除 Gin 中的一个以外的所有路由提供静态文件?

包起来

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

如果这些都不适合您,请发表评论以指出您的用例。

于 2021-08-01T19:41:17.393 回答
-3

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

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

r.Use(static.Serve("/", static.LocalFile("/tmp", false)))
于 2019-10-03T10:24:07.657 回答