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.ReadBufferSize = app.Settings.ReadBufferSize
|
||||
app.server.WriteBufferSize = app.Settings.WriteBufferSize
|
||||
app.buildTree()
|
||||
app.mutex.Unlock()
|
||||
return app
|
||||
}
|
||||
|
|
6
ctx.go
6
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]
|
||||
}
|
||||
}
|
||||
|
|
69
router.go
69
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)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
18
utils.go
18
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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue