这是 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。
如果这些都不适合您,请发表评论以指出您的用例。