Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ feat: Add support for RebuildTree #3074

Merged
merged 11 commits into from
Jul 18, 2024
28 changes: 28 additions & 0 deletions docs/api/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -573,3 +573,31 @@ Hooks is a method to return [hooks](./hooks.md) property.
```go title="Signature"
func (app *App) Hooks() *Hooks
```

## RebuildTree
efectn marked this conversation as resolved.
Show resolved Hide resolved

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.
15 changes: 15 additions & 0 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,21 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
}
}

// 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()
}
luk3skyw4lker marked this conversation as resolved.
Show resolved Hide resolved

// buildTree build the prefix tree from the previously registered routes
func (app *App) buildTree() *App {
if !app.routesRefreshed {
Expand Down
28 changes: 28 additions & 0 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"testing"
Expand Down Expand Up @@ -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")
}
luk3skyw4lker marked this conversation as resolved.
Show resolved Hide resolved

//////////////////////////////////////////////
///////////////// BENCHMARKS /////////////////
//////////////////////////////////////////////
Expand Down
Loading