diff --git a/gin.go b/gin.go index 2e033bf3..0ffa3e91 100644 --- a/gin.go +++ b/gin.go @@ -122,6 +122,13 @@ type Engine struct { // handler. HandleMethodNotAllowed bool + // SkipMethodNotAllowedMiddleware if enabled, global middleware registered via Use() + // will not be executed for 405 Method Not Allowed responses. This allows + // NoMethod handlers to run without triggering middleware that may reject + // the request (e.g., authentication, checksum validation) before the 405 + // response can be sent. + SkipMethodNotAllowedMiddleware bool + // ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that // match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was // fetched, it falls back to the IP obtained from @@ -358,6 +365,10 @@ func (engine *Engine) rebuild404Handlers() { } func (engine *Engine) rebuild405Handlers() { + if engine.SkipMethodNotAllowedMiddleware { + engine.allNoMethod = engine.noMethod + return + } engine.allNoMethod = engine.combineHandlers(engine.noMethod) } diff --git a/gin_test.go b/gin_test.go index a9cf1755..dfb7ac6e 100644 --- a/gin_test.go +++ b/gin_test.go @@ -1156,3 +1156,62 @@ func TestUpdateRouteTreesCalledOnce(t *testing.T) { assert.Equal(t, "ok", w.Body.String()) } } + +// Test the fix for https://github.com/gin-gonic/gin/issues/4189 +func TestSkipMethodNotAllowedMiddleware(t *testing.T) { + g := New() + g.HandleMethodNotAllowed = true + g.SkipMethodNotAllowedMiddleware = true + + var middlewareCalled bool + middleware := func(c *Context) { + middlewareCalled = true + c.Next() + } + noMethodHandler := func(c *Context) { + c.String(http.StatusMethodNotAllowed, "method not allowed") + } + + g.Use(middleware) + g.NoMethod(noMethodHandler) + g.POST("/test", func(c *Context) { + c.String(http.StatusOK, "ok") + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/test", nil) + g.ServeHTTP(w, req) + + assert.Equal(t, http.StatusMethodNotAllowed, w.Code) + assert.Equal(t, "method not allowed", w.Body.String()) + assert.False(t, middlewareCalled, "middleware should not be called when SkipMethodNotAllowedMiddleware is true") +} + +func TestSkipMethodNotAllowedMiddlewareDisabled(t *testing.T) { + g := New() + g.HandleMethodNotAllowed = true + g.SkipMethodNotAllowedMiddleware = false + + var middlewareCalled bool + middleware := func(c *Context) { + middlewareCalled = true + c.Next() + } + noMethodHandler := func(c *Context) { + c.String(http.StatusMethodNotAllowed, "method not allowed") + } + + g.Use(middleware) + g.NoMethod(noMethodHandler) + g.POST("/test", func(c *Context) { + c.String(http.StatusOK, "ok") + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/test", nil) + g.ServeHTTP(w, req) + + assert.Equal(t, http.StatusMethodNotAllowed, w.Code) + assert.Equal(t, "method not allowed", w.Body.String()) + assert.True(t, middlewareCalled, "middleware should be called when SkipMethodNotAllowedMiddleware is false") +} diff --git a/go.mod b/go.mod index df181253..c67c5fed 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.59.0 + github.com/quic-go/quic-go v0.59.1 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 go.mongodb.org/mongo-driver/v2 v2.5.0 diff --git a/go.sum b/go.sum index f7f9e27b..e69bfeee 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= -github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic= +github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=