- With issues: Inconsistent Middleware Application Based on Route Registration Order
Description
- Question 1: Method 1 and Method 2 only differ by the order of registration;Why do Method 1 and Method 2 produce different results? Are these methods legal? Or is this a bug?
- Question 2: Isn't Method 3 a bit redundant since the same route group is written twice?
How to reproduce
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type registerFunc func(*gin.RouterGroup) // Type for route registration functions
var registerFuncSlice []registerFunc // Slice of all route registration functions
func init() {
// Register routes
registerFuncSlice = append(registerFuncSlice, fooRoutes)
}
func fooHandler(c *gin.Context) {
key := c.GetString("testKey")
c.JSON(http.StatusOK, gin.H{"testValue": key})
c.Abort()
}
func testMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("testKey", "test")
c.Next()
}
}
func fooRoutes(v1 *gin.RouterGroup) {
// Method 1: If fooPrivateRoutes is placed before fooPublicRoutes, fooPublicRoutes
// will apply the testMiddleware (not expected)
// Accessing http://127.0.0.1:8888/v1/test/no-test-data returns {"testValue":"test"}
foo := v1.Group("/test")
fooPrivateRoutes(foo)
fooPublicRoutes(foo)
//// Method 2: If fooPrivateRoutes is placed after fooPublicRoutes, fooPublicRoutes
//// will not apply the testMiddleware (expected)
//// Accessing http://127.0.0.1:8888/v1/test/no-test-data returns {"testValue":""}
//foo := v1.Group("/test")
//fooPublicRoutes(foo)
//fooPrivateRoutes(foo)
//// Method 3: Separate the two, regardless of the order (expected)
//// Accessing http://127.0.0.1:8888/v1/test/no-test-data returns {"testValue":""}
//fooPrivate := v1.Group("/test")
//fooPrivateRoutes(fooPrivate)
//
//fooPublic := v1.Group("/test")
//fooPublicRoutes(fooPublic)
/*
Question 1: Method 1 and Method 2 only differ by the order of registration;
Why do Method 1 and Method 2 produce different results? Are these methods legal? Or is this a bug?
Question 2: Isn't Method 3 a bit redundant since the same route group is written twice?
*/
}
func fooPublicRoutes(group *gin.RouterGroup) {
group.GET("/no-test-data", fooHandler)
}
func fooPrivateRoutes(group *gin.RouterGroup) {
test := group.Use(testMiddleware())
test.GET("/has-test-data", fooHandler)
}
func setupRouter() *gin.Engine {
e := gin.Default()
v1 := e.Group("v1") // Register route group
// Dynamically register all routes
for _, regFunc := range registerFuncSlice {
regFunc(v1)
}
if err := e.Run("127.0.0.1:8888"); err != nil {
panic(err)
}
return e
}
func main() {
setupRouter()
}
Expectations
$ curl http://127.0.0.1:8888/v1/test/no-test-data
{"testValue":""}
Actual result
$ curl http://127.0.0.1:8888/v1/test/no-test-data
{"testValue":"test"}
Environment
- go version:go1.23.3
- gin version:v1.10.0
- operating system:windows 11
Comment From: simon-winter
related: https://github.com/gin-gonic/gin/issues/4109
Comment From: jiaopengzi
hello @manucorporat
- Why do I need to pay attention to the registration order within the same route group for Method 1 and Method 2? I find this unreasonable.
- Why does Method 3 work when written separately, even though it belongs to the same route group?
Comment From: Cristigeo
It looks like your code is adding 3 handlers on the same RouteGroup (/v1/test): one for the /has-test-data endpoint, one for the /no-test-data, and the middleware, which is basically a handler for ANY endpoint in the group (think of it like /*). When handling a request for the group, handlers are matched and executed in the order they were registered.
For Method 1, the handlers are registered in the order: /* (middleware) -> adds "test" /has-test-data -> returns response /no-test-data -> returns response so the "test" value will always be present in the responses for both /has-test-data and /no-test-data.
For Method 2, the handlers are registered in the order: /no-test-data -> returns response /* (middleware) -> adds "test" /has-test-data -> returns response so the "test" value will only appear in the /has-test-data response.
From a code perspective, the behaviour seems to be as expected, even if it looks counterintuitive.
Disclaimer: I'm very new to Gin; my explanation is based on the RouterGroup's implementation of the IRoutes interface, but maybe I misread something.