diff --git a/context.go b/context.go index 370f6086..1e82ab7d 100644 --- a/context.go +++ b/context.go @@ -55,8 +55,9 @@ type Context struct { index int8 fullPath string - engine *Engine - params *Params + engine *Engine + params *Params + skippedNodes *[]skippedNode // This mutex protect Keys map mu sync.RWMutex @@ -99,6 +100,7 @@ func (c *Context) reset() { c.queryCache = nil c.formCache = nil *c.params = (*c.params)[:0] + *c.skippedNodes = (*c.skippedNodes)[:0] } // Copy returns a copy of the current context that can be safely used outside the request's scope. diff --git a/gin.go b/gin.go index 02a1062c..cf93bacd 100644 --- a/gin.go +++ b/gin.go @@ -147,6 +147,7 @@ type Engine struct { pool sync.Pool trees methodTrees maxParams uint16 + maxSections uint16 trustedCIDRs []*net.IPNet } @@ -202,7 +203,8 @@ func Default() *Engine { func (engine *Engine) allocateContext() *Context { v := make(Params, 0, engine.maxParams) - return &Context{engine: engine, params: &v} + skippedNodes := make([]skippedNode, 0, engine.maxSections) + return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes} } // Delims sets template left and right delims and returns a Engine instance. @@ -308,6 +310,10 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { if paramsCount := countParams(path); paramsCount > engine.maxParams { engine.maxParams = paramsCount } + + if sectionsCount := countSections(path); sectionsCount > engine.maxSections { + engine.maxSections = sectionsCount + } } // Routes returns a slice of registered routes, including some useful information, such as: @@ -529,7 +535,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { } root := t[i].root // Find route in tree - value := root.getValue(rPath, c.params, unescape) + value := root.getValue(rPath, c.params, c.skippedNodes, unescape) if value.params != nil { c.Params = *value.params } @@ -557,7 +563,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { if tree.method == httpMethod { continue } - if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil { + if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil { c.handlers = engine.allNoMethod serveError(c, http.StatusMethodNotAllowed, default405Body) return diff --git a/tree.go b/tree.go index 1a1aa93e..dfae0f40 100644 --- a/tree.go +++ b/tree.go @@ -410,8 +410,8 @@ type skippedNode struct { // If no handle can be found, a TSR (trailing slash redirect) recommendation is // made if a handle exists with an extra (without the) trailing slash for the // given path. -func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) { - skippedNodes := make([]skippedNode, 0, countSections(path)) // Caching the latest nodes +func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) { + //skippedNodes := make([]skippedNode, 0, countSections(path)) // Caching the latest nodes var globalParamsCount int16 walk: // Outer loop for walking the tree @@ -427,9 +427,9 @@ walk: // Outer loop for walking the tree if c == idxc { // strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild if n.wildChild { - index := len(skippedNodes) - skippedNodes = skippedNodes[:index+1] - skippedNodes[index] = skippedNode{ + index := len(*skippedNodes) + *skippedNodes = (*skippedNodes)[:index+1] + (*skippedNodes)[index] = skippedNode{ path: prefix + path, node: &node{ path: n.path, @@ -464,10 +464,9 @@ walk: // Outer loop for walking the tree */ if path != "/" && !n.wildChild { - for len(skippedNodes) > 0 { - l := len(skippedNodes) - skippedNode := skippedNodes[l-1] - skippedNodes = skippedNodes[:l-1] + for l := len(*skippedNodes); l > 0; { + skippedNode := (*skippedNodes)[l-1] + *skippedNodes = (*skippedNodes)[:l-1] if strings.HasSuffix(skippedNode.path, path) { path = skippedNode.path n = skippedNode.node @@ -593,10 +592,9 @@ walk: // Outer loop for walking the tree // If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node // the current node needs to be equal to the latest matching node if n.handlers == nil && path != "/" { - for len(skippedNodes) > 0 { - l := len(skippedNodes) - skippedNode := skippedNodes[l-1] - skippedNodes = skippedNodes[:l-1] + for l := len(*skippedNodes); l > 0; { + skippedNode := (*skippedNodes)[l-1] + *skippedNodes = (*skippedNodes)[:l-1] if strings.HasSuffix(skippedNode.path, path) { path = skippedNode.path n = skippedNode.node @@ -639,10 +637,9 @@ walk: // Outer loop for walking the tree } if path != "/" { - for len(skippedNodes) > 0 { - l := len(skippedNodes) - skippedNode := skippedNodes[l-1] - skippedNodes = skippedNodes[:l-1] + for l := len(*skippedNodes); l > 0; { + skippedNode := (*skippedNodes)[l-1] + *skippedNodes = (*skippedNodes)[:l-1] if strings.HasSuffix(skippedNode.path, path) { path = skippedNode.path n = skippedNode.node diff --git a/tree_test.go b/tree_test.go index 8ae5b7db..39668831 100644 --- a/tree_test.go +++ b/tree_test.go @@ -33,6 +33,11 @@ func getParams() *Params { return &ps } +func getSkippedNodes() *[]skippedNode { + ps := make([]skippedNode, 0, 20) + return &ps +} + func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) { unescape := false if len(unescapes) >= 1 { @@ -40,7 +45,7 @@ func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes .. } for _, request := range requests { - value := tree.getValue(request.path, getParams(), unescape) + value := tree.getValue(request.path, getParams(), getSkippedNodes(), unescape) if value.handlers == nil { if !request.nilHandler { @@ -605,7 +610,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/doc/", } for _, route := range tsrRoutes { - value := tree.getValue(route, nil, false) + value := tree.getValue(route, nil, getSkippedNodes(), false) if value.handlers != nil { t.Fatalf("non-nil handler for TSR route '%s", route) } else if !value.tsr { @@ -622,7 +627,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/api/world/abc", } for _, route := range noTsrRoutes { - value := tree.getValue(route, nil, false) + value := tree.getValue(route, nil, getSkippedNodes(), false) if value.handlers != nil { t.Fatalf("non-nil handler for No-TSR route '%s", route) } else if value.tsr { @@ -641,7 +646,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) { t.Fatalf("panic inserting test route: %v", recv) } - value := tree.getValue("/", nil, false) + value := tree.getValue("/", nil, getSkippedNodes(), false) if value.handlers != nil { t.Fatalf("non-nil handler") } else if value.tsr { @@ -821,7 +826,7 @@ func TestTreeInvalidNodeType(t *testing.T) { // normal lookup recv := catchPanic(func() { - tree.getValue("/test", nil, false) + tree.getValue("/test", nil, getSkippedNodes(), false) }) if rs, ok := recv.(string); !ok || rs != panicMsg { t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv) @@ -846,7 +851,7 @@ func TestTreeInvalidParamsType(t *testing.T) { params := make(Params, 0) // try to trigger slice bounds out of range with capacity 0 - tree.getValue("/test", ¶ms, false) + tree.getValue("/test", ¶ms, getSkippedNodes(), false) } func TestTreeWildcardConflictEx(t *testing.T) {