mirror of https://github.com/gofiber/fiber.git
✨ feat: Add support for RebuildTree (#3074)
* feat: add rebuild tree method * docs: add newline at the end of app.md * docs: add an example of dynamic defined routes * docs: remove tabs from example code on app.md * Update docs/api/app.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update app.md * docs: add RebuildTree to what's new documentation * fix: markdown errors in documentation Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * refactor: add mutex lock to the addRoute function * refactor: remove mutex lock from addRoute * refactor: fix mutex deadlock in addRoute --------- Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>pull/3076/head
parent
7156af655a
commit
dfb1d2a6f3
|
@ -573,3 +573,31 @@ Hooks is a method to return [hooks](./hooks.md) property.
|
|||
```go title="Signature"
|
||||
func (app *App) Hooks() *Hooks
|
||||
```
|
||||
|
||||
## RebuildTree
|
||||
|
||||
The RebuildTree method is designed to rebuild the route tree and enable dynamic route registration. It returns a pointer to the App instance.
|
||||
|
||||
```go title="Signature"
|
||||
func (app *App) RebuildTree() *App
|
||||
```
|
||||
|
||||
**Note:** Use this method with caution. It is **not** thread-safe and calling it can be very performance-intensive, so it should be used sparingly and only in development mode. Avoid using it concurrently.
|
||||
|
||||
### Example Usage
|
||||
|
||||
Here’s an example of how to define and register routes dynamically:
|
||||
|
||||
```go
|
||||
app.Get("/define", func(c Ctx) error { // Define a new route dynamically
|
||||
app.Get("/dynamically-defined", func(c Ctx) error { // Adding a dynamically defined route
|
||||
return c.SendStatus(http.StatusOK)
|
||||
})
|
||||
|
||||
app.RebuildTree() // Rebuild the route tree to register the new route
|
||||
|
||||
return c.SendStatus(http.StatusOK)
|
||||
})
|
||||
```
|
||||
|
||||
In this example, a new route is defined and then `RebuildTree()` is called to make sure the new route is registered and available.
|
||||
|
|
|
@ -449,6 +449,31 @@ app.Route("/api").Route("/user/:id?")
|
|||
});
|
||||
```
|
||||
|
||||
### 🗺 RebuildTree
|
||||
|
||||
We have added a new method that allows the route tree stack to be rebuilt in runtime, with it, you can add a route while your application is running and rebuild the route tree stack to make it registered and available for calls.
|
||||
|
||||
You can find more reference on it in the [app](./api/app.md#rebuildtree):
|
||||
|
||||
#### Example Usage
|
||||
|
||||
```go
|
||||
app.Get("/define", func(c Ctx) error { // Define a new route dynamically
|
||||
app.Get("/dynamically-defined", func(c Ctx) error { // Adding a dynamically defined route
|
||||
return c.SendStatus(http.StatusOK)
|
||||
})
|
||||
|
||||
app.RebuildTree() // Rebuild the route tree to register the new route
|
||||
|
||||
return c.SendStatus(http.StatusOK)
|
||||
})
|
||||
```
|
||||
|
||||
In this example, a new route is defined and then `RebuildTree()` is called to make sure the new route is registered and available.
|
||||
|
||||
**Note:** Use this method with caution. It is **not** thread-safe and calling it can be very performance-intensive, so it should be used sparingly and only in
|
||||
development mode. Avoid using it concurrently.
|
||||
|
||||
### 🧠 Context
|
||||
|
||||
### 📎 Parser
|
||||
|
|
20
router.go
20
router.go
|
@ -375,6 +375,9 @@ func (app *App) register(methods []string, pathRaw string, group *Group, handler
|
|||
}
|
||||
|
||||
func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
// Check mounted routes
|
||||
var mounted bool
|
||||
if len(isMounted) > 0 {
|
||||
|
@ -400,15 +403,28 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
|
|||
|
||||
// Execute onRoute hooks & change latestRoute if not adding mounted route
|
||||
if !mounted {
|
||||
app.mutex.Lock()
|
||||
app.latestRoute = route
|
||||
if err := app.hooks.executeOnRouteHooks(*route); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
app.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// BuildTree rebuilds the prefix tree from the previously registered routes.
|
||||
// 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
|
||||
// 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
|
||||
// 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:
|
||||
// https://github.com/gofiber/fiber/issues/2769#issuecomment-2227385283
|
||||
func (app *App) RebuildTree() *App {
|
||||
app.mutex.Lock()
|
||||
defer app.mutex.Unlock()
|
||||
|
||||
return app.buildTree()
|
||||
}
|
||||
|
||||
// buildTree build the prefix tree from the previously registered routes
|
||||
func (app *App) buildTree() *App {
|
||||
if !app.routesRefreshed {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
@ -368,6 +369,33 @@ 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()))
|
||||
}
|
||||
|
||||
func Test_App_Rebuild_Tree(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
|
||||
app.Get("/test", func(c Ctx) error {
|
||||
app.Get("/dynamically-defined", func(c Ctx) error {
|
||||
return c.SendStatus(http.StatusOK)
|
||||
})
|
||||
|
||||
app.RebuildTree()
|
||||
|
||||
return c.SendStatus(http.StatusOK)
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/test", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
|
||||
|
||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
|
||||
require.NoError(t, err, "app.Test(req)")
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////
|
||||
///////////////// BENCHMARKS /////////////////
|
||||
//////////////////////////////////////////////
|
||||
|
|
Loading…
Reference in New Issue