mirror of https://github.com/gofiber/fiber.git
Merge dd3430bbea
into e7681ee402
commit
3ca3eb92e5
|
@ -761,3 +761,56 @@ func main() {
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, a new route is defined and then `RebuildTree()` is called to ensure the new route is registered and available.
|
In this example, a new route is defined and then `RebuildTree()` is called to ensure the new route is registered and available.
|
||||||
|
|
||||||
|
## RemoveRoute
|
||||||
|
|
||||||
|
This method removes a route by path. You must call the `RebuildTree()` method after the remove in to ensure the route is removed.
|
||||||
|
|
||||||
|
```go title="Signature"
|
||||||
|
func (app *App) RemoveRoute(path string, methods ...string)
|
||||||
|
```
|
||||||
|
|
||||||
|
This method removes a route by name
|
||||||
|
|
||||||
|
```go title="Signature"
|
||||||
|
func (app *App) RemoveRouteByName(name string, methods ...string)
|
||||||
|
```
|
||||||
|
|
||||||
|
```go title="Example"
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Get("/api/feature-a", func(c *fiber.Ctx) error {
|
||||||
|
app.RemoveRoute("/api/feature", fiber.MethodGet)
|
||||||
|
app.RebuildTree()
|
||||||
|
// Redefine route
|
||||||
|
app.Get("/api/feature", func(c *fiber.Ctx) error {
|
||||||
|
return c.SendString("Testing feature-a")
|
||||||
|
})
|
||||||
|
|
||||||
|
app.RebuildTree()
|
||||||
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
})
|
||||||
|
app.Get("/api/feature-b", func(c *fiber.Ctx) error {
|
||||||
|
app.RemoveRoute("/api/feature", fiber.MethodGet)
|
||||||
|
app.RebuildTree()
|
||||||
|
// Redefine route
|
||||||
|
app.Get("/api/feature", func(c *fiber.Ctx) error {
|
||||||
|
return c.SendString("Testing feature-b")
|
||||||
|
})
|
||||||
|
|
||||||
|
app.RebuildTree()
|
||||||
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Fatal(app.Listen(":3000"))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -1175,6 +1175,14 @@ In this example, a new route is defined, and `RebuildTree()` is called to ensure
|
||||||
|
|
||||||
Note: Use this method with caution. It is **not** thread-safe and can be very performance-intensive. Therefore, it should be used sparingly and primarily in development mode. It should not be invoke concurrently.
|
Note: Use this method with caution. It is **not** thread-safe and can be very performance-intensive. Therefore, it should be used sparingly and primarily in development mode. It should not be invoke concurrently.
|
||||||
|
|
||||||
|
## RemoveRoute
|
||||||
|
|
||||||
|
- **RemoveRoute**: Removes route by path
|
||||||
|
|
||||||
|
- **RemoveRouteByName**: Removes route by name
|
||||||
|
|
||||||
|
For more details, refer to the [app documentation](./api/app.md#removeroute):
|
||||||
|
|
||||||
### 🧠 Context
|
### 🧠 Context
|
||||||
|
|
||||||
Fiber v3 introduces several new features and changes to the Ctx interface, enhancing its functionality and flexibility.
|
Fiber v3 introduces several new features and changes to the Ctx interface, enhancing its functionality and flexibility.
|
||||||
|
|
120
router.go
120
router.go
|
@ -337,6 +337,7 @@ func (app *App) register(methods []string, pathRaw string, group *Group, handler
|
||||||
if pathRaw[0] != '/' {
|
if pathRaw[0] != '/' {
|
||||||
pathRaw = "/" + pathRaw
|
pathRaw = "/" + pathRaw
|
||||||
}
|
}
|
||||||
|
|
||||||
pathPretty := pathRaw
|
pathPretty := pathRaw
|
||||||
if !app.config.CaseSensitive {
|
if !app.config.CaseSensitive {
|
||||||
pathPretty = utils.ToLower(pathPretty)
|
pathPretty = utils.ToLower(pathPretty)
|
||||||
|
@ -344,11 +345,10 @@ func (app *App) register(methods []string, pathRaw string, group *Group, handler
|
||||||
if !app.config.StrictRouting && len(pathPretty) > 1 {
|
if !app.config.StrictRouting && len(pathPretty) > 1 {
|
||||||
pathPretty = utils.TrimRight(pathPretty, '/')
|
pathPretty = utils.TrimRight(pathPretty, '/')
|
||||||
}
|
}
|
||||||
pathClean := RemoveEscapeChar(pathPretty)
|
|
||||||
|
|
||||||
|
pathClean := RemoveEscapeChar(pathPretty)
|
||||||
parsedRaw := parseRoute(pathRaw, app.customConstraints...)
|
parsedRaw := parseRoute(pathRaw, app.customConstraints...)
|
||||||
parsedPretty := parseRoute(pathPretty, app.customConstraints...)
|
parsedPretty := parseRoute(pathPretty, app.customConstraints...)
|
||||||
|
|
||||||
isMount := group != nil && group.app != app
|
isMount := group != nil && group.app != app
|
||||||
|
|
||||||
for _, method := range methods {
|
for _, method := range methods {
|
||||||
|
@ -395,11 +395,88 @@ func (app *App) register(methods []string, pathRaw string, group *Group, handler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *App) normalizePath(path string) string {
|
||||||
|
if path == "" {
|
||||||
|
path = "/"
|
||||||
|
}
|
||||||
|
if path[0] != '/' {
|
||||||
|
path = "/" + path
|
||||||
|
}
|
||||||
|
if !app.config.CaseSensitive {
|
||||||
|
path = utils.ToLower(path)
|
||||||
|
}
|
||||||
|
if !app.config.StrictRouting && len(path) > 1 {
|
||||||
|
path = utils.TrimRight(path, '/')
|
||||||
|
}
|
||||||
|
return RemoveEscapeChar(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.
|
||||||
|
// You should call RebuildTree after using this to ensure consistency of the tree.
|
||||||
|
func (app *App) RemoveRoute(path string, removeMiddlewares bool, methods ...string) {
|
||||||
|
// Normalize same as register uses
|
||||||
|
norm := app.normalizePath(path)
|
||||||
|
|
||||||
|
pathMatchFunc := func(r *Route) bool {
|
||||||
|
return r.path == norm // compare private normalized path
|
||||||
|
}
|
||||||
|
app.deleteRoute(methods, removeMiddlewares, pathMatchFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// You should call RebuildTree after using this to ensure consistency of the tree.
|
||||||
|
func (app *App) RemoveRouteByName(name string, removeMiddlewares bool, methods ...string) {
|
||||||
|
matchFunc := func(r *Route) bool { return r.Name == name }
|
||||||
|
app.deleteRoute(methods, removeMiddlewares, matchFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) deleteRoute(methods []string, removeMiddlewares bool, matchFunc func(r *Route) bool) {
|
||||||
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
for _, method := range methods {
|
||||||
|
// Uppercase HTTP methods
|
||||||
|
method = utils.ToUpper(method)
|
||||||
|
|
||||||
|
// Get unique HTTP method identifier
|
||||||
|
m := app.methodInt(method)
|
||||||
|
if m == -1 {
|
||||||
|
continue // Skip invalid HTTP methods
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, route := range app.stack[m] {
|
||||||
|
var removedUseHandler bool
|
||||||
|
// only remove middlewares when use is true and method is use, if not middleware just check path
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.routesRefreshed = true
|
||||||
|
}
|
||||||
|
|
||||||
func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
|
func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
|
||||||
app.mutex.Lock()
|
app.mutex.Lock()
|
||||||
defer app.mutex.Unlock()
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
// Check mounted routes
|
|
||||||
var mounted bool
|
var mounted bool
|
||||||
if len(isMounted) > 0 {
|
if len(isMounted) > 0 {
|
||||||
mounted = isMounted[0]
|
mounted = isMounted[0]
|
||||||
|
@ -408,19 +485,40 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
|
||||||
// Get unique HTTP method identifier
|
// Get unique HTTP method identifier
|
||||||
m := app.methodInt(method)
|
m := app.methodInt(method)
|
||||||
|
|
||||||
// prevent identically route registration
|
// Check for an existing route with the same normalized path,
|
||||||
l := len(app.stack[m])
|
// same "use" flag, mount flag, and method.
|
||||||
if l > 0 && app.stack[m][l-1].Path == route.Path && route.use == app.stack[m][l-1].use && !route.mount && !app.stack[m][l-1].mount {
|
// If found, replace the old route with the new one.
|
||||||
preRoute := app.stack[m][l-1]
|
for i, existing := range app.stack[m] {
|
||||||
preRoute.Handlers = append(preRoute.Handlers, route.Handlers...)
|
if existing.path == route.path &&
|
||||||
|
existing.use == route.use &&
|
||||||
|
existing.mount == route.mount &&
|
||||||
|
existing.Method == route.Method {
|
||||||
|
if route.use { // middleware: merge handlers instead of replacing
|
||||||
|
app.stack[m][i].Handlers = append(existing.Handlers, route.Handlers...) //nolint:gocritic // Not a concern
|
||||||
} else {
|
} else {
|
||||||
// Increment global route position
|
// For non-middleware routes, replace as before
|
||||||
|
atomic.AddUint32(&app.handlersCount, ^uint32(len(existing.Handlers)-1)) //nolint:gosec // Not a concern
|
||||||
|
route.pos = existing.pos
|
||||||
|
app.stack[m][i] = route
|
||||||
|
}
|
||||||
|
app.routesRefreshed = true
|
||||||
|
if !mounted {
|
||||||
|
app.latestRoute = route
|
||||||
|
if err := app.hooks.executeOnRouteHooks(*route); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No duplicate route exists; add the new route normally.
|
||||||
route.pos = atomic.AddUint32(&app.routesCount, 1)
|
route.pos = atomic.AddUint32(&app.routesCount, 1)
|
||||||
route.Method = method
|
route.Method = method
|
||||||
|
|
||||||
// Add route to the stack
|
// Add route to the stack
|
||||||
app.stack[m] = append(app.stack[m], route)
|
app.stack[m] = append(app.stack[m], route)
|
||||||
app.routesRefreshed = true
|
app.routesRefreshed = true
|
||||||
}
|
|
||||||
|
|
||||||
// Execute onRoute hooks & change latestRoute if not adding mounted route
|
// Execute onRoute hooks & change latestRoute if not adding mounted route
|
||||||
if !mounted {
|
if !mounted {
|
||||||
|
@ -435,7 +533,7 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
|
||||||
// This method is useful when you want to register routes dynamically after the app has started.
|
// This method is useful when you want to register routes dynamically after the app has started.
|
||||||
// It is not recommended to use this method on production environments because rebuilding
|
// It is not recommended to use this method on production environments because rebuilding
|
||||||
// the tree is performance-intensive and not thread-safe in runtime. Since building the tree
|
// the tree is performance-intensive and not thread-safe in runtime. Since building the tree
|
||||||
// is only done in the startupProcess of the app, this method does not makes sure that the
|
// is only done in the startupProcess of the app, this method does not make sure that the
|
||||||
// routeTree is being safely changed, as it would add a great deal of overhead in the request.
|
// routeTree is being safely changed, as it would add a great deal of overhead in the request.
|
||||||
// Latest benchmark results showed a degradation from 82.79 ns/op to 94.48 ns/op and can be found in:
|
// Latest benchmark results showed a degradation from 82.79 ns/op to 94.48 ns/op and can be found in:
|
||||||
// https://github.com/gofiber/fiber/issues/2769#issuecomment-2227385283
|
// https://github.com/gofiber/fiber/issues/2769#issuecomment-2227385283
|
||||||
|
|
455
router_test.go
455
router_test.go
|
@ -11,6 +11,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gofiber/utils/v2"
|
"github.com/gofiber/utils/v2"
|
||||||
|
@ -411,31 +415,452 @@ func Test_Router_NotFound_HTML_Inject(t *testing.T) {
|
||||||
require.Equal(t, "Cannot DELETE /does/not/exist<script>alert('foo');</script>", string(c.Response.Body()))
|
require.Equal(t, "Cannot DELETE /does/not/exist<script>alert('foo');</script>", string(c.Response.Body()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_App_Rebuild_Tree(t *testing.T) {
|
func registerTreeManipulationRoutes(app *App, middleware ...func(Ctx) error) {
|
||||||
t.Parallel()
|
|
||||||
app := New()
|
|
||||||
|
|
||||||
app.Get("/test", func(c Ctx) error {
|
app.Get("/test", func(c Ctx) error {
|
||||||
app.Get("/dynamically-defined", func(c Ctx) error {
|
app.Get("/dynamically-defined", func(c Ctx) error {
|
||||||
return c.SendStatus(http.StatusOK)
|
return c.SendStatus(StatusOK)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.RebuildTree()
|
app.RebuildTree()
|
||||||
|
|
||||||
return c.SendStatus(http.StatusOK)
|
return c.SendStatus(StatusOK)
|
||||||
|
}, middleware...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyRequest(tb testing.TB, app *App, path string, expectedStatus int) *http.Response {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
resp, err := app.Test(httptest.NewRequest(MethodGet, path, nil))
|
||||||
|
require.NoError(tb, err, "app.Test(req)")
|
||||||
|
require.Equal(tb, expectedStatus, resp.StatusCode, "Status code")
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyRouteHandlerCounts(tb testing.TB, app *App, expectedRoutesCount int) {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
// this is taken from listen.go's printRoutesMessage app method
|
||||||
|
var routes []RouteMessage
|
||||||
|
for _, routeStack := range app.stack {
|
||||||
|
for _, route := range routeStack {
|
||||||
|
routeMsg := RouteMessage{
|
||||||
|
name: route.Name,
|
||||||
|
method: route.Method,
|
||||||
|
path: route.Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, handler := range route.Handlers {
|
||||||
|
routeMsg.handlers += runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name() + " "
|
||||||
|
}
|
||||||
|
|
||||||
|
routes = append(routes, routeMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
require.Equal(tb, expectedRoutesCount, strings.Count(route.handlers, " "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyThereAreNoRoutes(tb testing.TB, app *App) {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
require.Equal(tb, uint32(0), app.handlersCount)
|
||||||
|
require.Equal(tb, uint32(0), app.routesCount)
|
||||||
|
verifyRouteHandlerCounts(tb, app, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_App_Rebuild_Tree(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
registerTreeManipulationRoutes(app)
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/dynamically-defined", StatusNotFound)
|
||||||
|
verifyRequest(t, app, "/test", StatusOK)
|
||||||
|
verifyRequest(t, app, "/dynamically-defined", StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_App_Remove_Route_A_B_Feature_Testing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
app.Get("/api/feature-a", func(c Ctx) error {
|
||||||
|
app.RemoveRoute("/api/feature", false, MethodGet)
|
||||||
|
app.RebuildTree()
|
||||||
|
// Redefine route
|
||||||
|
app.Get("/api/feature", func(c Ctx) error {
|
||||||
|
return c.SendString("Testing feature-a")
|
||||||
})
|
})
|
||||||
|
|
||||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
|
app.RebuildTree()
|
||||||
require.NoError(t, err, "app.Test(req)")
|
return c.SendStatus(StatusOK)
|
||||||
require.Equal(t, http.StatusNotFound, resp.StatusCode, "Status code")
|
})
|
||||||
|
app.Get("/api/feature-b", func(c Ctx) error {
|
||||||
|
app.RemoveRoute("/api/feature", false, MethodGet)
|
||||||
|
app.RebuildTree()
|
||||||
|
// Redefine route
|
||||||
|
app.Get("/api/feature", func(c Ctx) error {
|
||||||
|
return c.SendString("Testing feature-b")
|
||||||
|
})
|
||||||
|
|
||||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/test", nil))
|
app.RebuildTree()
|
||||||
require.NoError(t, err, "app.Test(req)")
|
return c.SendStatus(StatusOK)
|
||||||
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
|
})
|
||||||
|
|
||||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
|
verifyRequest(t, app, "/api/feature-a", StatusOK)
|
||||||
require.NoError(t, err, "app.Test(req)")
|
|
||||||
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
|
resp := verifyRequest(t, app, "/api/feature", StatusOK)
|
||||||
|
require.Equal(t, "Testing feature-a", resp, "Response Message")
|
||||||
|
|
||||||
|
resp = verifyRequest(t, app, "/api/feature-b", StatusOK)
|
||||||
|
require.Equal(t, "Testing feature-b", resp, "Response Message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_App_Remove_Route_By_Name(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
app.Get("/api/test", func(c Ctx) error {
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
}).Name("test")
|
||||||
|
|
||||||
|
app.RemoveRouteByName("test", false, MethodGet)
|
||||||
|
app.RebuildTree()
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/test", StatusNotFound)
|
||||||
|
verifyThereAreNoRoutes(t, app)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_App_Remove_Route_By_Name_Non_Existing_Route(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
app.RemoveRouteByName("test", false, MethodGet)
|
||||||
|
app.RebuildTree()
|
||||||
|
|
||||||
|
verifyThereAreNoRoutes(t, app)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_App_Remove_Route_Nested(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
api := app.Group("/api")
|
||||||
|
|
||||||
|
v1 := api.Group("/v1")
|
||||||
|
v1.Get("/test", func(c Ctx) error {
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/api/v1/test", StatusOK)
|
||||||
|
app.RemoveRoute("/api/v1/test", false, MethodGet)
|
||||||
|
|
||||||
|
verifyThereAreNoRoutes(t, app)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_App_Remove_Route_Parameterized(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
app.Get("/test/:id", func(c Ctx) error {
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
})
|
||||||
|
verifyRequest(t, app, "/test/:id", StatusOK)
|
||||||
|
app.RemoveRoute("/test/:id", false, MethodGet)
|
||||||
|
|
||||||
|
verifyThereAreNoRoutes(t, app)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_App_Remove_Route(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
app.Get("/test", func(c Ctx) error {
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.RemoveRoute("/test", false, MethodGet)
|
||||||
|
app.RebuildTree()
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/test", StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_App_Remove_Route_Non_Existing_Route(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
app.RemoveRoute("/test", false, MethodGet, MethodHead)
|
||||||
|
app.RebuildTree()
|
||||||
|
|
||||||
|
verifyThereAreNoRoutes(t, app)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_App_Remove_Route_Concurrent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
// Add test route
|
||||||
|
app.Get("/test", func(c Ctx) error {
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Concurrently remove and add routes
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
app.RemoveRoute("/test", false, MethodGet)
|
||||||
|
app.Get("/test", func(c Ctx) error {
|
||||||
|
return c.SendStatus(StatusOK)
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// Verify final state
|
||||||
|
app.RebuildTree()
|
||||||
|
verifyRequest(t, app, "/test", StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_App_Route_Registration_Prevent_Duplicate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
registerTreeManipulationRoutes(app)
|
||||||
|
registerTreeManipulationRoutes(app)
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/dynamically-defined", StatusNotFound)
|
||||||
|
require.Equal(t, uint32(1), app.handlersCount)
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/test", StatusOK)
|
||||||
|
require.Equal(t, uint32(2), app.handlersCount)
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/dynamically-defined", StatusOK)
|
||||||
|
require.Equal(t, uint32(2), app.handlersCount)
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/test", StatusOK)
|
||||||
|
require.Equal(t, uint32(2), app.handlersCount)
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/dynamically-defined", StatusOK)
|
||||||
|
require.Equal(t, uint32(2), app.handlersCount)
|
||||||
|
require.Equal(t, uint32(2), app.routesCount)
|
||||||
|
|
||||||
|
verifyRouteHandlerCounts(t, app, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Route_Registration_Prevent_Duplicate_With_Middleware(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
middleware := func(c Ctx) error {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
registerTreeManipulationRoutes(app, middleware)
|
||||||
|
registerTreeManipulationRoutes(app)
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/dynamically-defined", StatusNotFound)
|
||||||
|
require.Equal(t, uint32(2), app.handlersCount)
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/test", StatusOK)
|
||||||
|
require.Equal(t, uint32(3), app.handlersCount)
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/dynamically-defined", StatusOK)
|
||||||
|
require.Equal(t, uint32(3), app.handlersCount)
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/test", StatusOK)
|
||||||
|
require.Equal(t, uint32(3), app.handlersCount)
|
||||||
|
|
||||||
|
verifyRequest(t, app, "/dynamically-defined", StatusOK)
|
||||||
|
require.Equal(t, uint32(3), app.handlersCount)
|
||||||
|
require.Equal(t, uint32(2), app.routesCount)
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
|
|
Loading…
Reference in New Issue