Skip to content

Commit

Permalink
Improve performance
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio Andres Virviescas Santana committed Sep 3, 2020
1 parent f947c3f commit b0e5c87
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 27 deletions.
3 changes: 2 additions & 1 deletion group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ func TestGroup_shortcutsAndHandle(t *testing.T) {
fn("/bar", func(_ *fasthttp.RequestCtx) {})
}

for _, method := range httpMethods {
methods := httpMethods[:len(httpMethods)-1] // Avoid customs methods
for _, method := range methods {
h, _ := r.Lookup(method, "/v1/bar", nil)
if h == nil {
t.Errorf("Bad shorcurt")
Expand Down
2 changes: 0 additions & 2 deletions radix/conts.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package radix

const stackBufSize = 128

const (
root nodeType = iota
static
Expand Down
86 changes: 70 additions & 16 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ var (
// Path auto-correction, including trailing slashes, is enabled by default.
func New() *Router {
return &Router{
trees: make(map[string]*radix.Tree),
trees: make([]*radix.Tree, 10),
customMethodsIndex: make(map[string]int),
registeredPaths: make(map[string][]string),
RedirectTrailingSlash: true,
RedirectFixedPath: true,
Expand All @@ -51,6 +52,37 @@ func (r *Router) saveMatchedRoutePath(path string, handler fasthttp.RequestHandl
}
}

func (r *Router) methodIndexOf(method string) int {
switch method {
case fasthttp.MethodGet:
return 0
case fasthttp.MethodHead:
return 1
case fasthttp.MethodPost:
return 2
case fasthttp.MethodPut:
return 3
case fasthttp.MethodPatch:
return 4
case fasthttp.MethodDelete:
return 5
case fasthttp.MethodConnect:
return 6
case fasthttp.MethodOptions:
return 7
case fasthttp.MethodTrace:
return 8
case MethodWild:
return 9
}

if i, ok := r.customMethodsIndex[method]; ok {
return i
}

return -1
}

// Mutable allows updating the route handler
//
// It's disabled by default
Expand All @@ -59,8 +91,12 @@ func (r *Router) saveMatchedRoutePath(path string, handler fasthttp.RequestHandl
func (r *Router) Mutable(v bool) {
r.treeMutable = v

for method := range r.trees {
r.trees[method].Mutable = v
for i := range r.trees {
tree := r.trees[i]

if tree != nil {
tree.Mutable = v
}
}
}

Expand Down Expand Up @@ -175,12 +211,22 @@ func (r *Router) Handle(method, path string, handler fasthttp.RequestHandler) {

r.registeredPaths[method] = append(r.registeredPaths[method], path)

tree := r.trees[method]
methodIndex := r.methodIndexOf(method)
if methodIndex == -1 {
tree := radix.New()
tree.Mutable = r.treeMutable

r.trees = append(r.trees, tree)
methodIndex = len(r.trees) - 1
r.customMethodsIndex[method] = methodIndex
}

tree := r.trees[methodIndex]
if tree == nil {
tree = radix.New()
tree.Mutable = r.treeMutable

r.trees[method] = tree
r.trees[methodIndex] = tree
r.globalAllowed = r.allowed("*", "")
}

Expand All @@ -206,14 +252,19 @@ func (r *Router) Handle(method, path string, handler fasthttp.RequestHandler) {
// values. Otherwise the third return value indicates whether a redirection to
// the same path with an extra / without the trailing slash should be performed.
func (r *Router) Lookup(method, path string, ctx *fasthttp.RequestCtx) (fasthttp.RequestHandler, bool) {
if tree := r.trees[method]; tree != nil {
methodIndex := r.methodIndexOf(method)
if methodIndex == -1 {
return nil, false
}

if tree := r.trees[methodIndex]; tree != nil {
handler, tsr := tree.Get(path, ctx)
if handler != nil || tsr {
return handler, tsr
}
}

if tree := r.trees[MethodWild]; tree != nil {
if tree := r.trees[r.methodIndexOf(MethodWild)]; tree != nil {
return tree.Get(path, ctx)
}

Expand Down Expand Up @@ -243,13 +294,13 @@ func (r *Router) allowed(path, reqMethod string) (allow string) {
return r.globalAllowed
}
} else { // specific path
for method := range r.trees {
for method := range r.registeredPaths {
// Skip the requested method - we already tried this one
if method == reqMethod || method == fasthttp.MethodOptions {
continue
}

handle, _ := r.trees[method].Get(path, nil)
handle, _ := r.trees[r.methodIndexOf(method)].Get(path, nil)
if handle != nil {
// Add request method to list of allowed methods
allowed = append(allowed, method)
Expand Down Expand Up @@ -342,20 +393,23 @@ func (r *Router) Handler(ctx *fasthttp.RequestCtx) {

path := gotils.B2S(ctx.Request.URI().Path())
method := gotils.B2S(ctx.Request.Header.Method())
methodIndex := r.methodIndexOf(method)

if tree := r.trees[method]; tree != nil {
if handler, tsr := tree.Get(path, ctx); handler != nil {
handler(ctx)
return
} else if method != fasthttp.MethodConnect && path != "/" {
if ok := r.tryRedirect(ctx, tree, tsr, method, path); ok {
if methodIndex > -1 {
if tree := r.trees[methodIndex]; tree != nil {
if handler, tsr := tree.Get(path, ctx); handler != nil {
handler(ctx)
return
} else if method != fasthttp.MethodConnect && path != "/" {
if ok := r.tryRedirect(ctx, tree, tsr, method, path); ok {
return
}
}
}
}

// Try to search in the wild method tree
if tree := r.trees[MethodWild]; tree != nil {
if tree := r.trees[r.methodIndexOf(MethodWild)]; tree != nil {
if handler, tsr := tree.Get(path, ctx); handler != nil {
handler(ctx)
return
Expand Down
32 changes: 27 additions & 5 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var httpMethods = []string{
fasthttp.MethodOptions,
fasthttp.MethodTrace,
MethodWild,
"CUSTOM",
}

func randomHTTPMethod() string {
Expand Down Expand Up @@ -156,10 +157,10 @@ func TestRouterAPI(t *testing.T) {
router.GET("/GET", func(ctx *fasthttp.RequestCtx) {
get = true
})
router.HEAD("/GET", func(ctx *fasthttp.RequestCtx) {
router.HEAD("/HEAD", func(ctx *fasthttp.RequestCtx) {
head = true
})
router.OPTIONS("/GET", func(ctx *fasthttp.RequestCtx) {
router.OPTIONS("/OPTIONS", func(ctx *fasthttp.RequestCtx) {
options = true
})
router.POST("/POST", func(ctx *fasthttp.RequestCtx) {
Expand Down Expand Up @@ -192,12 +193,12 @@ func TestRouterAPI(t *testing.T) {
t.Error("routing GET failed")
}

request(fasthttp.MethodHead, "/GET")
request(fasthttp.MethodHead, "/HEAD")
if !head {
t.Error("routing HEAD failed")
}

request(fasthttp.MethodOptions, "/GET")
request(fasthttp.MethodOptions, "/OPTIONS")
if !options {
t.Error("routing OPTIONS failed")
}
Expand Down Expand Up @@ -335,7 +336,7 @@ func TestRouterMutable(t *testing.T) {

for method := range router.trees {
if !router.trees[method].Mutable {
t.Errorf("Method %s - Mutable == %v, want %v", method, router.trees[method].Mutable, true)
t.Errorf("Method %d - Mutable == %v, want %v", method, router.trees[method].Mutable, true)
}
}

Expand Down Expand Up @@ -1072,6 +1073,27 @@ func BenchmarkRouterANY(b *testing.B) {
}
}

func BenchmarkRouterGet_ANY(b *testing.B) {
resp := []byte("Bench GET")
respANY := []byte("Bench GET (ANY)")

r := New()
r.GET("/", func(ctx *fasthttp.RequestCtx) {
ctx.Success("text/plain", resp)
})
r.ANY("/", func(ctx *fasthttp.RequestCtx) {
ctx.Success("text/plain", respANY)
})

ctx := new(fasthttp.RequestCtx)
ctx.Request.Header.SetMethod("UNICORN")
ctx.Request.SetRequestURI("/")

for i := 0; i < b.N; i++ {
r.Handler(ctx)
}
}

func BenchmarkRouterNotFound(b *testing.B) {
r := New()
r.GET("/bench", func(ctx *fasthttp.RequestCtx) {})
Expand Down
7 changes: 4 additions & 3 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import (
// Router is a fasthttp.RequestHandler which can be used to dispatch requests to different
// handler functions via configurable routes
type Router struct {
trees map[string]*radix.Tree
treeMutable bool
registeredPaths map[string][]string
trees []*radix.Tree
treeMutable bool
customMethodsIndex map[string]int
registeredPaths map[string][]string

// If enabled, adds the matched route path onto the ctx.UserValue context
// before invoking the handler.
Expand Down

0 comments on commit b0e5c87

Please sign in to comment.