mirror of https://github.com/gofiber/fiber.git
🌵hybrid routing with tree and fast router
parent
e802c41f41
commit
df3c48a734
1
app.go
1
app.go
|
@ -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
6
ctx.go
|
@ -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]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
75
router.go
75
router.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
18
utils.go
18
utils.go
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue