mirror of
https://github.com/gin-gonic/gin.git
synced 2025-12-13 13:12:17 +08:00
fix: call updateRouteTrees in ServeHTTP using sync.Once to support literal colon routes in all usage scenarios (#4413)
This commit is contained in:
parent
2e22e50859
commit
1092232f44
9
gin.go
9
gin.go
@ -94,6 +94,11 @@ const (
|
|||||||
type Engine struct {
|
type Engine struct {
|
||||||
RouterGroup
|
RouterGroup
|
||||||
|
|
||||||
|
// routeTreesUpdated ensures that the initialization or update of the route trees
|
||||||
|
// (used for routing HTTP requests) happens only once, even if called multiple times
|
||||||
|
// concurrently. It helps prevent race conditions and redundant setup operations.
|
||||||
|
routeTreesUpdated sync.Once
|
||||||
|
|
||||||
// RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a
|
// RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a
|
||||||
// handler for the path with (without) the trailing slash exists.
|
// handler for the path with (without) the trailing slash exists.
|
||||||
// For example if /foo/ is requested but a route only exists for /foo, the
|
// For example if /foo/ is requested but a route only exists for /foo, the
|
||||||
@ -635,6 +640,10 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) {
|
|||||||
|
|
||||||
// ServeHTTP conforms to the http.Handler interface.
|
// ServeHTTP conforms to the http.Handler interface.
|
||||||
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
engine.routeTreesUpdated.Do(func() {
|
||||||
|
engine.updateRouteTrees()
|
||||||
|
})
|
||||||
|
|
||||||
c := engine.pool.Get().(*Context)
|
c := engine.pool.Get().(*Context)
|
||||||
c.writermem.reset(w)
|
c.writermem.reset(w)
|
||||||
c.Request = req
|
c.Request = req
|
||||||
|
|||||||
111
literal_colon_test.go
Normal file
111
literal_colon_test.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLiteralColonWithRun(t *testing.T) {
|
||||||
|
SetMode(TestMode)
|
||||||
|
router := New()
|
||||||
|
|
||||||
|
router.GET(`/test\:action`, func(c *Context) {
|
||||||
|
c.JSON(http.StatusOK, H{"path": "literal_colon"})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.updateRouteTrees()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/test:action", nil)
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "literal_colon")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLiteralColonWithDirectServeHTTP(t *testing.T) {
|
||||||
|
SetMode(TestMode)
|
||||||
|
router := New()
|
||||||
|
|
||||||
|
router.GET(`/test\:action`, func(c *Context) {
|
||||||
|
c.JSON(http.StatusOK, H{"path": "literal_colon"})
|
||||||
|
})
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/test:action", nil)
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "literal_colon")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLiteralColonWithHandler(t *testing.T) {
|
||||||
|
|
||||||
|
SetMode(TestMode)
|
||||||
|
router := New()
|
||||||
|
|
||||||
|
router.GET(`/test\:action`, func(c *Context) {
|
||||||
|
c.JSON(http.StatusOK, H{"path": "literal_colon"})
|
||||||
|
})
|
||||||
|
|
||||||
|
handler := router.Handler()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/test:action", nil)
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "literal_colon")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLiteralColonWithHTTPServer(t *testing.T) {
|
||||||
|
SetMode(TestMode)
|
||||||
|
router := New()
|
||||||
|
|
||||||
|
router.GET(`/test\:action`, func(c *Context) {
|
||||||
|
c.JSON(http.StatusOK, H{"path": "literal_colon"})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.GET("/test/:param", func(c *Context) {
|
||||||
|
c.JSON(http.StatusOK, H{"param": c.Param("param")})
|
||||||
|
})
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/test:action", nil)
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "literal_colon")
|
||||||
|
|
||||||
|
w2 := httptest.NewRecorder()
|
||||||
|
req2, _ := http.NewRequest("GET", "/test/foo", nil)
|
||||||
|
router.ServeHTTP(w2, req2)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w2.Code)
|
||||||
|
assert.Contains(t, w2.Body.String(), "foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that updateRouteTrees is called only once
|
||||||
|
func TestUpdateRouteTreesCalledOnce(t *testing.T) {
|
||||||
|
SetMode(TestMode)
|
||||||
|
router := New()
|
||||||
|
|
||||||
|
callCount := 0
|
||||||
|
originalUpdate := router.updateRouteTrees
|
||||||
|
|
||||||
|
router.GET(`/test\:action`, func(c *Context) {
|
||||||
|
c.JSON(http.StatusOK, H{"call": callCount})
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/test:action", nil)
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = originalUpdate
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user