🌵hybrid routing with tree and fast router

pull/715/head
ReneWerner87 2020-08-10 10:51:38 +02:00
parent e802c41f41
commit df3c48a734
6 changed files with 81 additions and 52 deletions

1
app.go
View File

@ -634,6 +634,7 @@ func (app *App) init() *App {
app.server.IdleTimeout = app.Settings.IdleTimeout app.server.IdleTimeout = app.Settings.IdleTimeout
app.server.ReadBufferSize = app.Settings.ReadBufferSize app.server.ReadBufferSize = app.Settings.ReadBufferSize
app.server.WriteBufferSize = app.Settings.WriteBufferSize app.server.WriteBufferSize = app.Settings.WriteBufferSize
app.buildTree()
app.mutex.Unlock() app.mutex.Unlock()
return app return app
} }

6
ctx.go
View File

@ -38,7 +38,7 @@ type Ctx struct {
method string // HTTP method method string // HTTP method
methodINT int // HTTP method INT equivalent methodINT int // HTTP method INT equivalent
path string // Prettified HTTP path path string // Prettified HTTP path
treePart string // treePath string // Path for the search in the tree
pathOriginal string // Original HTTP path pathOriginal string // Original HTTP path
values []string // Route parameter values values []string // Route parameter values
err error // Contains error if passed to Next err error // Contains error if passed to Next
@ -1038,8 +1038,8 @@ func (ctx *Ctx) prettifyPath() {
ctx.path = utils.TrimRight(ctx.path, '/') ctx.path = utils.TrimRight(ctx.path, '/')
} }
ctx.treePart = ctx.treePart[0:0] ctx.treePath = ctx.treePath[0:0]
if len(ctx.path) >= 3 { if len(ctx.path) >= 3 {
ctx.treePart = ctx.path[:3] ctx.treePath = ctx.path[:3]
} }
} }

View File

@ -86,7 +86,7 @@ func (r *Route) match(path, original string) (match bool, values []string) {
func (app *App) next(ctx *Ctx) bool { func (app *App) next(ctx *Ctx) bool {
// Get stack length // Get stack length
tree, ok := app.treeStack[ctx.methodINT][ctx.treePart] tree, ok := app.treeStack[ctx.methodINT][ctx.treePath]
if !ok { if !ok {
tree = app.treeStack[ctx.methodINT][""] 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) { func (app *App) addRoute(method string, route *Route) {
// Get unique HTTP method indentifier // Get unique HTTP method indentifier
m := methodInt(method) 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 // prevent identically route registration
l := len(app.treeStack[m][treePart]) l := len(app.stack[m])
if l > 0 && app.treeStack[m][treePart][l-1].Path == route.Path && route.use == app.treeStack[m][treePart][l-1].use { if l > 0 && app.stack[m][l-1].Path == route.Path && route.use == app.stack[m][l-1].use {
preRoute := app.treeStack[m][treePart][l-1] preRoute := app.stack[m][l-1]
preRoute.Handlers = append(preRoute.Handlers, route.Handlers...) preRoute.Handlers = append(preRoute.Handlers, route.Handlers...)
} else { } else {
// Increment global route position // Increment global route position
@ -363,43 +353,36 @@ func (app *App) addRoute(method string, route *Route) {
route.Method = method route.Method = method
// Add route to the stack // Add route to the stack
app.stack[m] = append(app.stack[m], route) app.stack[m] = append(app.stack[m], route)
}
}
// TODO: outsource code // uniqueRouteStack
app.treeStack[m][treePart] = append(app.treeStack[m][treePart], route) func (app *App) buildTree() *App {
// loop all the methods and stacks and create the prefix tree
if treePart != "" { for m := range intMethod {
app.treeStack[m][treePart] = uniqueRouteStack(append(app.treeStack[m][treePart], app.treeStack[m][""]...)) app.treeStack[m] = make(map[string][]*Route)
} else { for _, route := range app.stack[m] {
for k, v := range app.treeStack[m] { treePath := ""
if k != treePart { if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 {
app.treeStack[m][k] = uniqueRouteStack(append(v, app.treeStack[m][""]...)) treePath = route.routeParser.segs[0].Const[:3]
sort.Slice(app.treeStack[m][k], func(i, j int) bool {
return app.treeStack[m][k][i].pos < app.treeStack[m][k][j].pos
})
}
} }
// 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
})
} }
} // loop the methods and tree stacks and add global stack and sort everything
for m := range intMethod {
func uniqueRouteStack(stack []*Route) []*Route { for treePart := range app.treeStack[m] {
var unique []*Route if treePart != "" {
m := make(map[*Route]int) // merge global tree routes in current tree stack
for _, v := range stack { app.treeStack[m][treePart] = uniqueRouteStack(append(app.treeStack[m][treePart], app.treeStack[m][""]...))
if i, ok := m[v]; ok { }
// Overwrite previous value per requirement in // sort tree slices with the positions
// question to keep last matching value. sort.Slice(app.treeStack[m][treePart], func(i, j int) bool {
unique[i] = v return app.treeStack[m][treePart][i].pos < app.treeStack[m][treePart][j].pos
} else { })
// Unique key found. Record position and collect
// in result.
m[v] = len(unique)
unique = append(unique, v)
} }
} }
return unique return app
} }

View File

@ -234,8 +234,7 @@ func Test_Router_Handler_SetETag(t *testing.T) {
}) })
c := &fasthttp.RequestCtx{} c := &fasthttp.RequestCtx{}
app.init().handler(c)
app.handler(c)
utils.AssertEqual(t, `"13-1831710635"`, string(c.Response.Header.Peek(HeaderETag))) 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") request.URI().SetPath("/user/keys/1337")
var res bool var res bool
app.init()
c := app.AcquireCtx(request) c := app.AcquireCtx(request)
defer app.ReleaseCtx(c) defer app.ReleaseCtx(c)
@ -518,6 +518,7 @@ func Benchmark_Router_Handler_StrictRouting(b *testing.B) {
func Benchmark_Router_Github_API(b *testing.B) { func Benchmark_Router_Github_API(b *testing.B) {
app := New() app := New()
registerDummyRoutes(app) registerDummyRoutes(app)
app.init()
c := &fasthttp.RequestCtx{} c := &fasthttp.RequestCtx{}
var match bool var match bool

View File

@ -51,7 +51,7 @@ func setMethodNotAllowed(ctx *Ctx) {
} }
// Reset stack index // Reset stack index
ctx.indexRoute = -1 ctx.indexRoute = -1
tree, ok := ctx.app.treeStack[i][ctx.treePart] tree, ok := ctx.app.treeStack[i][ctx.treePath]
if !ok { if !ok {
tree = ctx.app.treeStack[i][""] tree = ctx.app.treeStack[i][""]
} }
@ -294,6 +294,22 @@ var getBytesImmutable = func(s string) (b []byte) {
return []byte(s) 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 // HTTP methods and their unique INTs
func methodInt(s string) int { func methodInt(s string) int {
switch s { switch s {

View File

@ -211,6 +211,34 @@ func Test_Utils_TestConn_Deadline(t *testing.T) {
utils.AssertEqual(t, nil, conn.SetWriteDeadline(time.Time{})) 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) { func Test_Utils_IsNoCache(t *testing.T) {
testCases := []struct { testCases := []struct {
string string