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
22 changes: 22 additions & 0 deletions docs/api/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -573,3 +573,25 @@
```go title="Signature"
func (app *App) Hooks() *Hooks
```

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

RebuildTree is a method destined to rebuild the route tree stack and allow dynamically route registers. It returns an app pointer.

```go title="Signature"
func (app *App) RebuildTree() *App
```

**NOTE**: This method should be used in the most careful way possible, since it's not currently possible to make it thread-safe (it would add a big performance overhead to do so) and calling it is very performance-intensive, so it's recommended to be used only in development mode and never concurrently. Here's an example of defining routes dynamically:

```go
app.Get("/define", func(c Ctx) error {
app.Get("/dynamically-defined", func(c Ctx) error {
return c.SendStatus(http.StatusOK)

Check failure on line 590 in docs/api/app.md

View workflow job for this annotation

GitHub Actions / markdownlint

Hard tabs

docs/api/app.md:590:3 MD010/no-hard-tabs Hard tabs [Column: 3] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md010.md
})

app.RebuildTree()

return c.SendStatus(http.StatusOK)
})
```
gaby marked this conversation as resolved.
Show resolved Hide resolved
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