🌵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.ReadBufferSize = app.Settings.ReadBufferSize
app.server.WriteBufferSize = app.Settings.WriteBufferSize
app.buildTree()
app.mutex.Unlock()
return app
}

6
ctx.go
View File

@ -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]
}
}

View File

@ -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)
// 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)
}
}
// 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][""]...))
} 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
})
}
}
}
// 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
})
}
}
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)
}
}
return unique
return app
}

View File

@ -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

View File

@ -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 {

View File

@ -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