WIP: export buildTree method

export-buildtree
Muhammed Efe Cetin 2024-06-29 01:15:42 +03:00
parent dbba6cfa69
commit ae61c32860
No known key found for this signature in database
GPG Key ID: 0AA4D45CBAA86F73
3 changed files with 74 additions and 3 deletions

2
app.go
View File

@ -86,7 +86,7 @@ type Error struct {
// App denotes the Fiber application.
type App struct {
mutex sync.Mutex
mutex sync.RWMutex
// Route stack divided by HTTP methods
stack [][]*Route
// Route stack divided by HTTP methods and route prefixes

View File

@ -146,12 +146,15 @@ func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint: unparam // boo
}
func (app *App) next(c *DefaultCtx) (bool, error) {
app.mutex.Lock()
// Get stack length
tree, ok := app.treeStack[c.methodINT][c.treePath]
if !ok {
tree = app.treeStack[c.methodINT][""]
}
lenTree := len(tree) - 1
app.mutex.Unlock()
// Loop over the route stack starting from previous index
for c.indexRoute < lenTree {
@ -375,6 +378,11 @@ 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()
fmt.Printf("addRoute: method: %s, route: %v, isMounted: %v\n", method, route, isMounted)
// Check mounted routes
var mounted bool
if len(isMounted) > 0 {
@ -400,12 +408,12 @@ 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.mutex.Lock()
app.latestRoute = route
if err := app.hooks.executeOnRouteHooks(*route); err != nil {
panic(err)
}
app.mutex.Unlock()
//app.mutex.Unlock()
}
}
@ -446,3 +454,13 @@ func (app *App) buildTree() *App {
return app
}
// 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 produtcion environments because rebuilding the tree is performance-intensive.
func (app *App) BuildTree() *App {
app.mutex.Lock()
defer app.mutex.Unlock()
return app.buildTree()
}

View File

@ -11,6 +11,9 @@ import (
"io"
"net/http/httptest"
"os"
"strconv"
"sync"
"sync/atomic"
"testing"
"github.com/gofiber/utils/v2"
@ -368,6 +371,56 @@ func Test_Router_NotFound_HTML_Inject(t *testing.T) {
require.Equal(t, "Cannot DELETE /does/not/exist&lt;script&gt;alert(&#39;foo&#39;);&lt;/script&gt;", string(c.Response.Body()))
}
func Test_App_BuildTree_Concurrent(t *testing.T) {
t.Parallel()
app := New()
var counter atomic.Uint64
var routerMu sync.Mutex
app.Get("/test", func(c Ctx) error {
routerMu.Lock()
cStr := strconv.FormatUint(counter.Load(), 10)
counter.Add(1)
app.Get("/test"+cStr, func(c Ctx) error {
return c.SendString("test" + cStr)
})
routerMu.Unlock()
app.BuildTree()
return c.SendStatus(StatusOK)
})
var wg sync.WaitGroup
wg.Add(4)
for i := 0; i < 4; i++ {
go func() {
defer wg.Done()
req := httptest.NewRequest(MethodGet, "/test", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, StatusOK, resp.StatusCode)
}()
}
wg.Wait()
for i := 0; i < 4; i++ {
req := httptest.NewRequest(MethodGet, "/test"+strconv.FormatInt(int64(i), 10), nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, "test"+strconv.Itoa(i), app.getString(body))
}
}
//////////////////////////////////////////////
///////////////// BENCHMARKS /////////////////
//////////////////////////////////////////////