diff --git a/app.go b/app.go index 9a1eab77..73e7499a 100644 --- a/app.go +++ b/app.go @@ -634,6 +634,7 @@ func (app *App) init() *App { app.server.IdleTimeout = app.Settings.IdleTimeout app.server.ReadBufferSize = app.Settings.ReadBufferSize app.server.WriteBufferSize = app.Settings.WriteBufferSize + app.buildTree() app.mutex.Unlock() return app } diff --git a/ctx.go b/ctx.go index aa23e17f..7f737715 100644 --- a/ctx.go +++ b/ctx.go @@ -38,7 +38,7 @@ type Ctx struct { method string // HTTP method methodINT int // HTTP method INT equivalent path string // Prettified HTTP path - treePart string // + treePath string // Path for the search in the tree pathOriginal string // Original HTTP path values []string // Route parameter values err error // Contains error if passed to Next @@ -1038,8 +1038,8 @@ func (ctx *Ctx) prettifyPath() { ctx.path = utils.TrimRight(ctx.path, '/') } - ctx.treePart = ctx.treePart[0:0] + ctx.treePath = ctx.treePath[0:0] if len(ctx.path) >= 3 { - ctx.treePart = ctx.path[:3] + ctx.treePath = ctx.path[:3] } } diff --git a/router.go b/router.go index b769b877..d56b3e89 100644 --- a/router.go +++ b/router.go @@ -86,7 +86,7 @@ func (r *Route) match(path, original string) (match bool, values []string) { func (app *App) next(ctx *Ctx) bool { // Get stack length - tree, ok := app.treeStack[ctx.methodINT][ctx.treePart] + tree, ok := app.treeStack[ctx.methodINT][ctx.treePath] if !ok { tree = app.treeStack[ctx.methodINT][""] } @@ -338,21 +338,11 @@ func (app *App) registerStatic(prefix, root string, config ...Static) Route { func (app *App) addRoute(method string, route *Route) { // Get unique HTTP method indentifier m := methodInt(method) - // TODO: improve code - if app.treeStack[m] == nil { - app.treeStack[m] = make(map[string][]*Route) - } - - treePart := "" - if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 2 { - // TODO: change it for the new route logic - treePart = "/" + route.routeParser.segs[0].Const[:2] - } // prevent identically route registration - l := len(app.treeStack[m][treePart]) - if l > 0 && app.treeStack[m][treePart][l-1].Path == route.Path && route.use == app.treeStack[m][treePart][l-1].use { - preRoute := app.treeStack[m][treePart][l-1] + l := len(app.stack[m]) + if l > 0 && app.stack[m][l-1].Path == route.Path && route.use == app.stack[m][l-1].use { + preRoute := app.stack[m][l-1] preRoute.Handlers = append(preRoute.Handlers, route.Handlers...) } else { // Increment global route position @@ -363,43 +353,36 @@ func (app *App) addRoute(method string, route *Route) { route.Method = method // Add route to the stack app.stack[m] = append(app.stack[m], route) + } +} - // TODO: outsource code - app.treeStack[m][treePart] = append(app.treeStack[m][treePart], route) - - if treePart != "" { - app.treeStack[m][treePart] = uniqueRouteStack(append(app.treeStack[m][treePart], app.treeStack[m][""]...)) - } else { - for k, v := range app.treeStack[m] { - if k != treePart { - app.treeStack[m][k] = uniqueRouteStack(append(v, app.treeStack[m][""]...)) - sort.Slice(app.treeStack[m][k], func(i, j int) bool { - return app.treeStack[m][k][i].pos < app.treeStack[m][k][j].pos - }) - } +// uniqueRouteStack +func (app *App) buildTree() *App { + // loop all the methods and stacks and create the prefix tree + for m := range intMethod { + app.treeStack[m] = make(map[string][]*Route) + for _, route := range app.stack[m] { + treePath := "" + if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 { + treePath = route.routeParser.segs[0].Const[:3] } + // create tree stack + app.treeStack[m][treePath] = append(app.treeStack[m][treePath], route) } - sort.Slice(app.treeStack[m][treePart], func(i, j int) bool { - return app.treeStack[m][treePart][i].pos < app.treeStack[m][treePart][j].pos - }) } -} - -func uniqueRouteStack(stack []*Route) []*Route { - var unique []*Route - m := make(map[*Route]int) - for _, v := range stack { - if i, ok := m[v]; ok { - // Overwrite previous value per requirement in - // question to keep last matching value. - unique[i] = v - } else { - // Unique key found. Record position and collect - // in result. - m[v] = len(unique) - unique = append(unique, v) + // loop the methods and tree stacks and add global stack and sort everything + for m := range intMethod { + for treePart := range app.treeStack[m] { + if treePart != "" { + // merge global tree routes in current tree stack + app.treeStack[m][treePart] = uniqueRouteStack(append(app.treeStack[m][treePart], app.treeStack[m][""]...)) + } + // sort tree slices with the positions + sort.Slice(app.treeStack[m][treePart], func(i, j int) bool { + return app.treeStack[m][treePart][i].pos < app.treeStack[m][treePart][j].pos + }) } } - return unique + return app } diff --git a/router_test.go b/router_test.go index 3e667d50..efe507f7 100644 --- a/router_test.go +++ b/router_test.go @@ -234,8 +234,7 @@ func Test_Router_Handler_SetETag(t *testing.T) { }) c := &fasthttp.RequestCtx{} - - app.handler(c) + app.init().handler(c) utils.AssertEqual(t, `"13-1831710635"`, string(c.Response.Header.Peek(HeaderETag))) } @@ -375,6 +374,7 @@ func Benchmark_Router_Next(b *testing.B) { request.URI().SetPath("/user/keys/1337") var res bool + app.init() c := app.AcquireCtx(request) defer app.ReleaseCtx(c) @@ -518,6 +518,7 @@ func Benchmark_Router_Handler_StrictRouting(b *testing.B) { func Benchmark_Router_Github_API(b *testing.B) { app := New() registerDummyRoutes(app) + app.init() c := &fasthttp.RequestCtx{} var match bool diff --git a/utils.go b/utils.go index 11ff3ab7..b8d0c9e1 100644 --- a/utils.go +++ b/utils.go @@ -51,7 +51,7 @@ func setMethodNotAllowed(ctx *Ctx) { } // Reset stack index ctx.indexRoute = -1 - tree, ok := ctx.app.treeStack[i][ctx.treePart] + tree, ok := ctx.app.treeStack[i][ctx.treePath] if !ok { tree = ctx.app.treeStack[i][""] } @@ -294,6 +294,22 @@ var getBytesImmutable = func(s string) (b []byte) { return []byte(s) } +// uniqueRouteStack drop all not unique routes from the slice +func uniqueRouteStack(stack []*Route) []*Route { + var unique []*Route + m := make(map[*Route]int) + for _, v := range stack { + if _, ok := m[v]; !ok { + // Unique key found. Record position and collect + // in result. + m[v] = len(unique) + unique = append(unique, v) + } + } + + return unique +} + // HTTP methods and their unique INTs func methodInt(s string) int { switch s { diff --git a/utils_test.go b/utils_test.go index 9d9ff9b9..c224727d 100644 --- a/utils_test.go +++ b/utils_test.go @@ -211,6 +211,34 @@ func Test_Utils_TestConn_Deadline(t *testing.T) { utils.AssertEqual(t, nil, conn.SetWriteDeadline(time.Time{})) } +func Test_Utils_UniqueRouteStack(t *testing.T) { + route1 := &Route{} + route2 := &Route{} + route3 := &Route{} + utils.AssertEqual( + t, + []*Route{ + route1, + route2, + route3, + }, + uniqueRouteStack([]*Route{ + route1, + route1, + route1, + route2, + route2, + route2, + route3, + route3, + route3, + route1, + route2, + route3, + }), + ) +} + func Test_Utils_IsNoCache(t *testing.T) { testCases := []struct { string