I want to make my app to serve below things.
I made a code like below:
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")
}
When I run the code, it panics:
panic: path segment '/api/v1/disk' conflicts with existing wildcard '/*filepath' in path '/api/v1/disk'
I understand why it panics, but I have no idea how to fix.
Just two things comes up in my mind:
Thank you in advance.
This is an intended feature of Gin's underlying router implementation. Routes are matched on prefixes, so any path param or wildcard in the same position as another existing path segment will result in a conflict.
In this particular Q&A, the method RouterGroup.Static
adds a wildcard /*filepath
to the route you serve from. If that route is root /
, the wildcard will conflict with every other route you declare.
You must accept that there is no straightforward solution, because that's just how Gin's router implementation works. If you can't accept a workaround then you might have to change HTTP framework. The comments mention Echo as an alternative.
1. If you CAN change your route mappings
The best workaround is no workaround, and instead embracing Gin's design. Then you can simply add a unique prefix to the static files path: r.Static("/static", "/www")
. This doesn't mean you change your local directory structure, only what path prefix gets mapped to it. Request URLs have to change.
2. Wildcard conflict with one or few other route
Let's say that your router has only these two routes:
/*any
/api/foo
In this case you might get away with a an intermediary handler and manually check the path param:
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. Wildcard conflict with many other routes
Which one is best depends on your specific situation and directory structure.
3.1 using r.NoRoute
handler; this may work but is a bad hack.
r.NoRoute(gin.WrapH(http.FileServer(gin.Dir("static", false))))
r.GET("/api", apiHandler)
This will serve files from static
(or whatever other dir) BUT it will also attempt to serve assets for all non-existing routes under the /api
group. E.g. /api/xyz
will be handled by NoRoute
handler. This may be acceptable, until it isn't. E.g. if you just happen to have a folder named api
among your static assets.
3.2 using a middleware;
For example, you can also find gin-contrib/static
:
// declare before other routes
r.Use(static.Serve("/", static.LocalFile("www", false)))
This middleware is slightly more sophisticated, but it suffers from the same limitation. Namely:
Engine#RedirectTrailingSlash = false
)gin-contrib/static
does, as shown below) 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 using a Gin sub-engine; this may be an OK choice if you have a lot of potential conflicts, e.g. a wildcard on /
and complex API routes with groups and whatnot. Using a sub-engine will give you more control over this, but the implementation still feels hacky. An example based on Engine.HandleContext
:
func main() {
apiEngine := gin.New()
apiG := apiEngine.Group("/api")
{
apiG.GET("/foo", func(c *gin.Context) { c.JSON(200, gin.H{"foo": true})})
apiG.GET("/bar", func(c *gin.Context) { c.JSON(200, gin.H{"bar": true})})
}
r := gin.New()
r.GET("/*any", func(c *gin.Context) {
path := c.Param("any")
if strings.HasPrefix(path, "/api") {
apiEngine.HandleContext(c)
} else {
assetHandler(c)
}
})
r.Run(":9955")
}
If you can, redesign your routes. If you can't, this answer presents three possible workarounds of increasing complexity. As always, the limitations may or may not apply to your specific use case. YMMV.
If none of this works for you, maybe leave a comment to point out what is your use case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With