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:
commit
e802c41f41
8
app.go
8
app.go
@ -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
8
ctx.go
@ -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]
|
||||
}
|
||||
}
|
||||
|
72
router.go
72
router.go
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
8
utils.go
8
utils.go
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user