Compare commits

...

2 Commits

Author SHA1 Message Date
goingforstudying-ctrl
2a93f82e57
Merge 30a1dadcb0ae02dbfe894ab8c78b8ec22b79b694 into d9307dbcbbe796a64d9e0ef23452da888dd7f904 2026-06-22 17:09:03 +00:00
goingforstudying-ctrl
30a1dadcb0 feat: add SkipMethodNotAllowedMiddleware option
Adds a new Engine option that, when enabled, prevents global middleware
registered via Use() from being 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.

Fixes gin-gonic/gin#4189
2026-06-22 13:08:57 -04:00
2 changed files with 70 additions and 0 deletions

11
gin.go
View File

@ -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)
}

View File

@ -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")
}