From 3e3ed0183bdc3e6c71d048ce03bca9d1f12d53f4 Mon Sep 17 00:00:00 2001 From: Solaris-star <820622658@qq.com> Date: Tue, 21 Jul 2026 16:01:12 +0800 Subject: [PATCH] fix(group): register auto 404 routes only once on Use Group.Use always called RouteNotFound for "" and "/*" whenever middleware was present. A second Use() (or Group(..., mw) followed by Use) re-added the same routes and panicked when AllowOverwritingRoute is false. Only auto-register the catch-all 404 routes the first time middleware is attached to the group. Fixes #3047 --- group.go | 8 ++++++++ group_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/group.go b/group.go index 76382627d..aab7fcf13 100644 --- a/group.go +++ b/group.go @@ -29,6 +29,10 @@ type Group struct { // `/*` NotFound routes for itself. If this kind of behavior is not needed, then create an Echo instance with the ` noAutoRegisterRoutes ` // flag set to true. Example `echo.NewWithConfig(echo.Config{NoGroupAutoRegister404Routes: true})`. func (g *Group) Use(middleware ...MiddlewareFunc) { + // Track whether this is the first non-empty middleware attachment so we only + // auto-register 404 catch-alls once. Re-registering on every Use() panics when + // AllowOverwritingRoute is false (#3047). + hadMiddleware := len(g.middleware) > 0 g.middleware = append(g.middleware, middleware...) if len(g.middleware) == 0 { return @@ -36,6 +40,10 @@ func (g *Group) Use(middleware ...MiddlewareFunc) { if g.noAutoRegisterRoutes { return } + if hadMiddleware { + // 404 routes already registered by a previous Use()/Group(...middleware). + return + } // group level middlewares are different from Echo `Pre` and `Use` middlewares (those are global). Group level middlewares // are only executed if they are added to the Router with route. // So we register catch all route (404 is a safe way to emulate route match) for this group and now during routing the diff --git a/group_test.go b/group_test.go index 14535ea54..6373aa56c 100644 --- a/group_test.go +++ b/group_test.go @@ -866,3 +866,36 @@ func TestGroup_RouteNotFoundWithMiddleware(t *testing.T) { }) } } + + +func TestGroup_UseMultipleTimesDoesNotPanic(t *testing.T) { + // Repro for #3047: second Use() used to re-register 404 routes and panic when + // AllowOverwritingRoute is false. + e := NewWithConfig(Config{ + Router: NewRouter(RouterConfig{AllowOverwritingRoute: false}), + }) + + mw := func(next HandlerFunc) HandlerFunc { + return func(c *Context) error { return next(c) } + } + + // Case 1: sequential Use on empty group + g1 := e.Group("/api") + assert.NotPanics(t, func() { + g1.Use(mw) + g1.Use(mw) + }) + + // Case 2: Group created with middleware, then Use again + g2 := e.Group("/v2", mw) + assert.NotPanics(t, func() { + g2.Use(mw) + }) + + // Case 3: nested group inherits middleware then Use + api := e.Group("/nested", mw) + res := api.Group("/resource") + assert.NotPanics(t, func() { + res.Use(mw) + }) +}