mirror of https://github.com/gofiber/fiber.git
update
parent
f2817497aa
commit
961996050b
46
router.go
46
router.go
|
@ -9,7 +9,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"slices"
|
|
||||||
"sort"
|
"sort"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
@ -415,25 +414,25 @@ func (app *App) normalizePath(path string) string {
|
||||||
// RemoveRoute is used to remove a route from the stack by path.
|
// RemoveRoute is used to remove a route from the stack by path.
|
||||||
// This only needs to be called to remove a route, route registration prevents duplicate routes.
|
// This only needs to be called to remove a route, route registration prevents duplicate routes.
|
||||||
// You should call RebuildTree after using this to ensure consistency of the tree.
|
// You should call RebuildTree after using this to ensure consistency of the tree.
|
||||||
func (app *App) RemoveRoute(path string, methods ...string) {
|
func (app *App) RemoveRoute(path string, removeMiddlewares bool, methods ...string) {
|
||||||
// Normalize same as register uses
|
// Normalize same as register uses
|
||||||
norm := app.normalizePath(path)
|
norm := app.normalizePath(path)
|
||||||
|
|
||||||
pathMatchFunc := func(r *Route) bool {
|
pathMatchFunc := func(r *Route) bool {
|
||||||
return r.path == norm // compare private normalized path
|
return r.path == norm // compare private normalized path
|
||||||
}
|
}
|
||||||
app.deleteRoute(methods, pathMatchFunc)
|
app.deleteRoute(methods, removeMiddlewares, pathMatchFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveRouteByName is used to remove a route from the stack by name.
|
// RemoveRouteByName is used to remove a route from the stack by name.
|
||||||
// This only needs to be called to remove a route, route registration prevents duplicate routes.
|
// This only needs to be called to remove a route, route registration prevents duplicate routes.
|
||||||
// You should call RebuildTree after using this to ensure consistency of the tree.
|
// You should call RebuildTree after using this to ensure consistency of the tree.
|
||||||
func (app *App) RemoveRouteByName(name string, methods ...string) {
|
func (app *App) RemoveRouteByName(name string, removeMiddlewares bool, methods ...string) {
|
||||||
matchFunc := func(r *Route) bool { return r.Name == name }
|
matchFunc := func(r *Route) bool { return r.Name == name }
|
||||||
app.deleteRoute(methods, matchFunc)
|
app.deleteRoute(methods, removeMiddlewares, matchFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) deleteRoute(methods []string, matchFunc func(r *Route) bool) {
|
func (app *App) deleteRoute(methods []string, removeMiddlewares bool, matchFunc func(r *Route) bool) {
|
||||||
app.mutex.Lock()
|
app.mutex.Lock()
|
||||||
defer app.mutex.Unlock()
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
@ -447,21 +446,28 @@ func (app *App) deleteRoute(methods []string, matchFunc func(r *Route) bool) {
|
||||||
continue // Skip invalid HTTP methods
|
continue // Skip invalid HTTP methods
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the index of the route to remove
|
for i, route := range app.stack[m] {
|
||||||
index := slices.IndexFunc(app.stack[m], matchFunc)
|
var removedUseHandler bool
|
||||||
if index == -1 {
|
// only remove middlewares when use is true and method is use, if not middleware just check path
|
||||||
continue // Route not found
|
if (removeMiddlewares && route.use && matchFunc(route)) || (!route.use && matchFunc(route)) {
|
||||||
|
// Remove route from stack
|
||||||
|
if i+1 < len(app.stack[m]) {
|
||||||
|
app.stack[m] = append(app.stack[m][:i], app.stack[m][i+1:]...)
|
||||||
|
} else {
|
||||||
|
app.stack[m] = app.stack[m][:i]
|
||||||
|
}
|
||||||
|
app.routesRefreshed = true
|
||||||
|
|
||||||
|
// Decrement global handler count. In middleware routes, only decrement once
|
||||||
|
if (route.use && !removedUseHandler) || !route.use {
|
||||||
|
removedUseHandler = true
|
||||||
|
atomic.AddUint32(&app.handlersCount, ^uint32(len(route.Handlers)-1)) //nolint:gosec // Not a concern
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrement global route count
|
||||||
|
atomic.AddUint32(&app.routesCount, ^uint32(0)) //nolint:gosec // Not a concern
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
route := app.stack[m][index]
|
|
||||||
|
|
||||||
// Decrement global handler count
|
|
||||||
atomic.AddUint32(&app.handlersCount, ^uint32(len(route.Handlers)-1)) //nolint:gosec // Not a concern
|
|
||||||
// Decrement global route position
|
|
||||||
atomic.AddUint32(&app.routesCount, ^uint32(0))
|
|
||||||
|
|
||||||
// Remove route from tree stack
|
|
||||||
app.stack[m] = slices.Delete(app.stack[m], index, index+1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.routesRefreshed = true
|
app.routesRefreshed = true
|
||||||
|
|
200
router_test.go
200
router_test.go
|
@ -487,7 +487,7 @@ func Test_App_Remove_Route_A_B_Feature_Testing(t *testing.T) {
|
||||||
app := New()
|
app := New()
|
||||||
|
|
||||||
app.Get("/api/feature-a", func(c Ctx) error {
|
app.Get("/api/feature-a", func(c Ctx) error {
|
||||||
app.RemoveRoute("/api/feature", MethodGet)
|
app.RemoveRoute("/api/feature", false, MethodGet)
|
||||||
app.RebuildTree()
|
app.RebuildTree()
|
||||||
// Redefine route
|
// Redefine route
|
||||||
app.Get("/api/feature", func(c Ctx) error {
|
app.Get("/api/feature", func(c Ctx) error {
|
||||||
|
@ -498,7 +498,7 @@ func Test_App_Remove_Route_A_B_Feature_Testing(t *testing.T) {
|
||||||
return c.SendStatus(StatusOK)
|
return c.SendStatus(StatusOK)
|
||||||
})
|
})
|
||||||
app.Get("/api/feature-b", func(c Ctx) error {
|
app.Get("/api/feature-b", func(c Ctx) error {
|
||||||
app.RemoveRoute("/api/feature", MethodGet)
|
app.RemoveRoute("/api/feature", false, MethodGet)
|
||||||
app.RebuildTree()
|
app.RebuildTree()
|
||||||
// Redefine route
|
// Redefine route
|
||||||
app.Get("/api/feature", func(c Ctx) error {
|
app.Get("/api/feature", func(c Ctx) error {
|
||||||
|
@ -526,7 +526,7 @@ func Test_App_Remove_Route_By_Name(t *testing.T) {
|
||||||
return c.SendStatus(StatusOK)
|
return c.SendStatus(StatusOK)
|
||||||
}).Name("test")
|
}).Name("test")
|
||||||
|
|
||||||
app.RemoveRouteByName("test", MethodGet)
|
app.RemoveRouteByName("test", false, MethodGet)
|
||||||
app.RebuildTree()
|
app.RebuildTree()
|
||||||
|
|
||||||
verifyRequest(t, app, "/test", StatusNotFound)
|
verifyRequest(t, app, "/test", StatusNotFound)
|
||||||
|
@ -537,7 +537,7 @@ func Test_App_Remove_Route_By_Name_Non_Existing_Route(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New()
|
app := New()
|
||||||
|
|
||||||
app.RemoveRouteByName("test", MethodGet)
|
app.RemoveRouteByName("test", false, MethodGet)
|
||||||
app.RebuildTree()
|
app.RebuildTree()
|
||||||
|
|
||||||
verifyThereAreNoRoutes(t, app)
|
verifyThereAreNoRoutes(t, app)
|
||||||
|
@ -555,7 +555,7 @@ func Test_App_Remove_Route_Nested(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
verifyRequest(t, app, "/api/v1/test", StatusOK)
|
verifyRequest(t, app, "/api/v1/test", StatusOK)
|
||||||
app.RemoveRoute("/api/v1/test", MethodGet)
|
app.RemoveRoute("/api/v1/test", false, MethodGet)
|
||||||
|
|
||||||
verifyThereAreNoRoutes(t, app)
|
verifyThereAreNoRoutes(t, app)
|
||||||
}
|
}
|
||||||
|
@ -568,7 +568,7 @@ func Test_App_Remove_Route_Parameterized(t *testing.T) {
|
||||||
return c.SendStatus(StatusOK)
|
return c.SendStatus(StatusOK)
|
||||||
})
|
})
|
||||||
verifyRequest(t, app, "/test/:id", StatusOK)
|
verifyRequest(t, app, "/test/:id", StatusOK)
|
||||||
app.RemoveRoute("/test/:id", MethodGet)
|
app.RemoveRoute("/test/:id", false, MethodGet)
|
||||||
|
|
||||||
verifyThereAreNoRoutes(t, app)
|
verifyThereAreNoRoutes(t, app)
|
||||||
}
|
}
|
||||||
|
@ -581,7 +581,7 @@ func Test_App_Remove_Route(t *testing.T) {
|
||||||
return c.SendStatus(StatusOK)
|
return c.SendStatus(StatusOK)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.RemoveRoute("/test", MethodGet)
|
app.RemoveRoute("/test", false, MethodGet)
|
||||||
app.RebuildTree()
|
app.RebuildTree()
|
||||||
|
|
||||||
verifyRequest(t, app, "/test", StatusNotFound)
|
verifyRequest(t, app, "/test", StatusNotFound)
|
||||||
|
@ -591,7 +591,7 @@ func Test_App_Remove_Route_Non_Existing_Route(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New()
|
app := New()
|
||||||
|
|
||||||
app.RemoveRoute("/test", MethodGet, MethodHead)
|
app.RemoveRoute("/test", false, MethodGet, MethodHead)
|
||||||
app.RebuildTree()
|
app.RebuildTree()
|
||||||
|
|
||||||
verifyThereAreNoRoutes(t, app)
|
verifyThereAreNoRoutes(t, app)
|
||||||
|
@ -612,7 +612,7 @@ func Test_App_Remove_Route_Concurrent(t *testing.T) {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
app.RemoveRoute("/test", MethodGet)
|
app.RemoveRoute("/test", false, MethodGet)
|
||||||
app.Get("/test", func(c Ctx) error {
|
app.Get("/test", func(c Ctx) error {
|
||||||
return c.SendStatus(StatusOK)
|
return c.SendStatus(StatusOK)
|
||||||
})
|
})
|
||||||
|
@ -681,6 +681,188 @@ func Test_Route_Registration_Prevent_Duplicate_With_Middleware(t *testing.T) {
|
||||||
verifyRouteHandlerCounts(t, app, 1)
|
verifyRouteHandlerCounts(t, app, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizePath(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
caseSensitive bool
|
||||||
|
strictRouting bool
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Empty path",
|
||||||
|
path: "",
|
||||||
|
caseSensitive: true,
|
||||||
|
strictRouting: true,
|
||||||
|
expected: "/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No leading slash",
|
||||||
|
path: "users",
|
||||||
|
caseSensitive: true,
|
||||||
|
strictRouting: true,
|
||||||
|
expected: "/users",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "With trailing slash and strict routing",
|
||||||
|
path: "/users/",
|
||||||
|
caseSensitive: true,
|
||||||
|
strictRouting: true,
|
||||||
|
expected: "/users/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "With trailing slash and non-strict routing",
|
||||||
|
path: "/users/",
|
||||||
|
caseSensitive: true,
|
||||||
|
strictRouting: false,
|
||||||
|
expected: "/users",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Case sensitive",
|
||||||
|
path: "/Users",
|
||||||
|
caseSensitive: true,
|
||||||
|
strictRouting: true,
|
||||||
|
expected: "/Users",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Case insensitive",
|
||||||
|
path: "/Users",
|
||||||
|
caseSensitive: false,
|
||||||
|
strictRouting: true,
|
||||||
|
expected: "/users",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "With escape characters",
|
||||||
|
path: "/users\\/profile",
|
||||||
|
caseSensitive: true,
|
||||||
|
strictRouting: true,
|
||||||
|
expected: "/users/profile",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
app := &App{
|
||||||
|
config: Config{
|
||||||
|
CaseSensitive: tt.caseSensitive,
|
||||||
|
StrictRouting: tt.strictRouting,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result := app.normalizePath(tt.path)
|
||||||
|
require.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveRoute(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
|
||||||
|
app.Use(func(c Ctx) error {
|
||||||
|
buf.WriteString("1")
|
||||||
|
return c.Next()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Post("/", func(c Ctx) error {
|
||||||
|
buf.WriteString("2")
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Use("/test", func(c Ctx) error {
|
||||||
|
buf.WriteString("3")
|
||||||
|
return c.Next()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/test", func(c Ctx) error {
|
||||||
|
buf.WriteString("4")
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Post("/test", func(c Ctx) error {
|
||||||
|
buf.WriteString("5")
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Equal(t, uint32(5), app.handlersCount)
|
||||||
|
require.Equal(t, uint32(21), app.routesCount)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(MethodPost, "/", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err := app.Test(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 200, resp.StatusCode)
|
||||||
|
require.Equal(t, "12", buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
req, err = http.NewRequest(MethodGet, "/test", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err = app.Test(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 200, resp.StatusCode)
|
||||||
|
require.Equal(t, "134", buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
app.RemoveRoute("/test", false, MethodGet)
|
||||||
|
app.RebuildTree()
|
||||||
|
|
||||||
|
require.Equal(t, uint32(4), app.handlersCount)
|
||||||
|
require.Equal(t, uint32(20), app.routesCount)
|
||||||
|
|
||||||
|
app.RemoveRoute("/test", false, MethodPost)
|
||||||
|
app.RebuildTree()
|
||||||
|
|
||||||
|
require.Equal(t, uint32(3), app.handlersCount)
|
||||||
|
require.Equal(t, uint32(19), app.routesCount)
|
||||||
|
|
||||||
|
req, err = http.NewRequest(MethodPost, "/test", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err = app.Test(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 404, resp.StatusCode)
|
||||||
|
require.Equal(t, "13", buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
req, err = http.NewRequest(MethodGet, "/test", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err = app.Test(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 404, resp.StatusCode)
|
||||||
|
require.Equal(t, "13", buf.String())
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
app.RemoveRoute("/", false, MethodGet, MethodPost)
|
||||||
|
|
||||||
|
require.Equal(t, uint32(2), app.handlersCount)
|
||||||
|
require.Equal(t, uint32(18), app.routesCount)
|
||||||
|
|
||||||
|
req, err = http.NewRequest(MethodGet, "/", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err = app.Test(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 404, resp.StatusCode)
|
||||||
|
require.Equal(t, "1", buf.String())
|
||||||
|
|
||||||
|
app.RemoveRoute("/test", true, MethodGet, MethodPost)
|
||||||
|
|
||||||
|
require.Equal(t, uint32(2), app.handlersCount)
|
||||||
|
require.Equal(t, uint32(16), app.routesCount)
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
///////////////// BENCHMARKS /////////////////
|
///////////////// BENCHMARKS /////////////////
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
|
|
Loading…
Reference in New Issue