1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-21 19:53:19 +00:00

Merge branch 'hybrid_routing' into improve_route_behavior

This commit is contained in:
ReneWerner87 2020-08-10 10:14:31 +02:00
commit e802c41f41
5 changed files with 96 additions and 24 deletions

8
app.go
View File

@ -50,7 +50,10 @@ type Error struct {
type App struct {
mutex sync.Mutex
// Route stack divided by HTTP methods
stack [][]*Route
stack [][]*Route
treeStack []map[string][]*Route
// Amount of registered routes
routesCount int
// Amount of registered handlers
handlerCount int
// Ctx pool
@ -232,7 +235,8 @@ func New(settings ...*Settings) *App {
// Create a new app
app := &App{
// Create router stack
stack: make([][]*Route, len(intMethod)),
stack: make([][]*Route, len(intMethod)),
treeStack: make([]map[string][]*Route, len(intMethod)),
// Create Ctx pool
pool: sync.Pool{
New: func() interface{} {

8
ctx.go
View File

@ -38,6 +38,7 @@ type Ctx struct {
method string // HTTP method
methodINT int // HTTP method INT equivalent
path string // Prettified HTTP path
treePart string //
pathOriginal string // Original HTTP path
values []string // Route parameter values
err error // Contains error if passed to Next
@ -90,6 +91,8 @@ func (app *App) AcquireCtx(fctx *fasthttp.RequestCtx) *Ctx {
ctx.methodINT = methodInt(ctx.method)
// Attach *fasthttp.RequestCtx to ctx
ctx.Fasthttp = fctx
// Prettify path
ctx.prettifyPath()
return ctx
}
@ -1034,4 +1037,9 @@ func (ctx *Ctx) prettifyPath() {
if !ctx.app.Settings.StrictRouting && len(ctx.path) > 1 && ctx.path[len(ctx.path)-1] == '/' {
ctx.path = utils.TrimRight(ctx.path, '/')
}
ctx.treePart = ctx.treePart[0:0]
if len(ctx.path) >= 3 {
ctx.treePart = ctx.path[:3]
}
}

View File

@ -6,6 +6,7 @@ package fiber
import (
"fmt"
"sort"
"strings"
"time"
@ -37,6 +38,7 @@ type Router interface {
// Route is a struct that holds all metadata for each registered handler
type Route struct {
// Data for routing
pos int // Position in stack -> important for the sort of the matched routes
use bool // USE matches path prefixes
star bool // Path equals '*'
root bool // Path equals '/'
@ -84,13 +86,17 @@ func (r *Route) match(path, original string) (match bool, values []string) {
func (app *App) next(ctx *Ctx) bool {
// Get stack length
lenr := len(app.stack[ctx.methodINT]) - 1
tree, ok := app.treeStack[ctx.methodINT][ctx.treePart]
if !ok {
tree = app.treeStack[ctx.methodINT][""]
}
lenr := len(tree) - 1
// Loop over the route stack starting from previous index
for ctx.indexRoute < lenr {
// Increment route index
ctx.indexRoute++
// Get *Route
route := app.stack[ctx.methodINT][ctx.indexRoute]
route := tree[ctx.indexRoute]
// Check if it matches the request path
match, values := route.match(ctx.path, ctx.pathOriginal)
// No match, next route
@ -135,9 +141,6 @@ func (app *App) handler(rctx *fasthttp.RequestCtx) {
app.ReleaseCtx(ctx)
return
}
// Prettify path
ctx.prettifyPath()
// Find match in stack
match := app.next(ctx)
// Generate ETag if enabled
@ -335,15 +338,68 @@ 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.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]
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]
preRoute.Handlers = append(preRoute.Handlers, route.Handlers...)
} else {
// Increment global route position
app.mutex.Lock()
app.routesCount++
app.mutex.Unlock()
route.pos = app.routesCount
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
})
}
}
}
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
}

View File

@ -383,7 +383,7 @@ func Benchmark_Router_Next(b *testing.B) {
res = app.next(c)
}
utils.AssertEqual(b, true, res)
utils.AssertEqual(b, 31, c.indexRoute)
utils.AssertEqual(b, 4, c.indexRoute)
}
// go test -v ./... -run=^$ -bench=Benchmark_Route_Match -benchmem -count=4
@ -493,6 +493,7 @@ func Benchmark_Router_Handler_Unescape(b *testing.B) {
c.URI().SetPath("/cr%C3%A9er")
for n := 0; n < b.N; n++ {
c.URI().SetPath("/cr%C3%A9er")
app.handler(c)
}
}
@ -518,23 +519,22 @@ func Benchmark_Router_Github_API(b *testing.B) {
app := New()
registerDummyRoutes(app)
c := &fasthttp.RequestCtx{}
var match bool
var params []string
for n := 0; n < b.N; n++ {
for i := range routesFixture.TestRoutes {
for i := range routesFixture.TestRoutes {
c.Request.Header.SetMethod(routesFixture.TestRoutes[i].Method)
mINT := methodInt(routesFixture.TestRoutes[i].Method)
path := routesFixture.TestRoutes[i].Path
for i := range app.stack[mINT] {
match, params = app.stack[mINT][i].match(path, path)
}
for n := 0; n < b.N; n++ {
c.URI().SetPath(routesFixture.TestRoutes[i].Path)
ctx := app.AcquireCtx(c)
match = app.next(ctx)
app.ReleaseCtx(ctx)
}
utils.AssertEqual(b, true, match)
}
utils.AssertEqual(b, true, match)
utils.AssertEqual(b, true, params != nil)
}
type testRoute struct {

View File

@ -51,14 +51,18 @@ func setMethodNotAllowed(ctx *Ctx) {
}
// Reset stack index
ctx.indexRoute = -1
tree, ok := ctx.app.treeStack[i][ctx.treePart]
if !ok {
tree = ctx.app.treeStack[i][""]
}
// Get stack length
lenr := len(ctx.app.stack[i]) - 1
lenr := len(tree) - 1
//Loop over the route stack starting from previous index
for ctx.indexRoute < lenr {
// Increment route index
ctx.indexRoute++
// Get *Route
route := ctx.app.stack[i][ctx.indexRoute]
route := tree[ctx.indexRoute]
// Skip use routes
if route.use {
continue