From 07fdeff4156971a053d919c6bf5f7c1bea16cb9f Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 7 Jun 2014 09:39:45 +0200 Subject: [PATCH] tree: optimize lookups --- tree.go | 320 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 166 insertions(+), 154 deletions(-) diff --git a/tree.go b/tree.go index 2c4631b6..b5bcd079 100644 --- a/tree.go +++ b/tree.go @@ -26,10 +26,10 @@ const ( type node struct { path string - indices []byte - children []*node wildChild bool nType nodeType + indices []byte + children []*node handle map[string]Handle priority uint32 } @@ -165,7 +165,7 @@ func (n *node) insertChild(method, path string, handle Handle) { panic("wildcard route conflicts with existing children") } - // find wildcard end (either '/'' or path end) + // find wildcard end (either '/' or path end) k := i + 1 for k < j && path[k] != '/' { k++ @@ -258,116 +258,122 @@ func (n *node) insertChild(method, path string, handle Handle) { // made if a handle exists with an extra (without the) trailing slash for the // given path. func (n *node) getValue(method, path string) (handle Handle, vars map[string]string, tsr bool) { -WALK: // Outer loop for walking the tree nodes - for len(path) >= len(n.path) && path[:len(n.path)] == n.path { - path = path[len(n.path):] - - if len(path) == 0 { - // Check if this node has a handle registered for the given node - if handle = n.handle[method]; handle != nil { - return - } +walk: // Outer loop for walking the tree + for { + if len(path) > len(n.path) { + if path[:len(n.path)] == n.path { + path = path[len(n.path):] + // If this node does not have a wildcard (param or catchAll) + // child, we can just look up the next child node and continue + // to walk down the tree + if !n.wildChild { + c := path[0] + for i, index := range n.indices { + if c == index { + n = n.children[i] + continue walk + } + } - // No handle found. Check if a handle for this path + a - // trailing slash exists for trailing slash recommendation - for i, index := range n.indices { - if index == '/' { - n = n.children[i] - tsr = (n.path == "/" && n.handle[method] != nil) || - (n.nType == catchAll && n.children[0].handle[method] != nil) + // Nothing found. + // We can recommend to redirect to the same URL without a + // trailing slash if a leaf exists for that path. + tsr = (path == "/" && n.handle[method] != nil) return - } - } - // TODO: handle HTTP Error 405 - Method Not Allowed - // Return available methods + } - return + // handle wildcard child + n = n.children[0] + switch n.nType { + case param: + // find param end (either '/' or path end) + k := 0 + for k < len(path) && path[k] != '/' { + k++ + } - } else if n.wildChild { - n = n.children[0] + // save param value + if vars == nil { + vars = map[string]string{ + n.path[1:]: path[:k], + } + } else { + vars[n.path[1:]] = path[:k] + } - switch n.nType { - case param: - // find param end (either '/'' or path end) - k := 0 - for k < len(path) && path[k] != '/' { - k++ - } + // we need to go deeper! + if k < len(path) { + if len(n.children) > 0 { + path = path[k:] + n = n.children[0] + continue walk + } - // save param value - if vars == nil { - vars = map[string]string{ - n.path[1:]: path[:k], + // ... but we can't + tsr = (len(path) == k+1) + return } - } else { - vars[n.path[1:]] = path[:k] - } - // we need to go deeper! - if k < len(path) { - if len(n.children) > 0 { - path = path[k:] - n = n.children[0] - continue - } else { // ... but we can't - tsr = (len(path) == k+1) + if handle = n.handle[method]; handle != nil { return + } else if len(n.children) == 1 { + // No handle found. Check if a handle for this path + a + // trailing slash exists for TSR recommendation + n = n.children[0] + tsr = (n.path == "/" && n.handle[method] != nil) } - } - if handle = n.handle[method]; handle != nil { + // TODO: handle HTTP Error 405 - Method Not Allowed + // Return available methods return - } else if len(n.children) == 1 { - // No handle found. Check if a handle for this path + a - // trailing slash exists for TSR recommendation - n = n.children[0] - tsr = (n.path == "/" && n.handle[method] != nil) - } - // TODO: handle HTTP Error 405 - Method Not Allowed - // Return available methods + case catchAll: + // save catchAll value + if vars == nil { + vars = map[string]string{ + n.path[2:]: path, + } + } else { + vars[n.path[2:]] = path + } - return + handle = n.handle[method] + return - case catchAll: - // save catchAll value - if vars == nil { - vars = map[string]string{ - n.path[2:]: path, - } - } else { - vars[n.path[2:]] = path + default: + panic("Unknown node type") } - - handle = n.handle[method] + } + } else if path == n.path { + // We should have reached the node containing the handle. + // Check if this node has a handle registered for the given method. + if handle = n.handle[method]; handle != nil { return - - default: - panic("Unknown node type") } - } else { - c := path[0] - + // No handle found. Check if a handle for this path + a + // trailing slash exists for trailing slash recommendation for i, index := range n.indices { - if c == index { + if index == '/' { n = n.children[i] - continue WALK + tsr = (n.path == "/" && n.handle[method] != nil) || + (n.nType == catchAll && n.children[0].handle[method] != nil) + return } } - // Nothing found. We can recommend to redirect to the same URL - // without a trailing slash if a leaf exists for that path - tsr = (path == "/" && n.handle[method] != nil) + // TODO: handle HTTP Error 405 - Method Not Allowed + // Return available methods return } - } - // Nothing found. We can recommend to redirect to the same URL with an extra - // trailing slash if a leaf exists for that path - tsr = (len(path)+1 == len(n.path) && n.path[len(path)] == '/' && n.handle[method] != nil) || (path == "/") - return + // Nothing found. We can recommend to redirect to the same URL with an + // extra trailing slash if a leaf exists for that path + tsr = (path == "/") || + (n.path[len(path)] == '/' && path == n.path[:len(n.path)-1] && n.handle[method] != nil) + return + } } // Makes a case-insensitive lookup of the given path and tries to find a handler @@ -377,96 +383,101 @@ WALK: // Outer loop for walking the tree nodes func (n *node) findCaseInsensitivePath(method, path string, fixTrailingSlash bool) (ciPath []byte, found bool) { ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory - // Outer loop for walking the tree nodes + // Outer loop for walking the tree for len(path) >= len(n.path) && strings.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) { path = path[len(n.path):] ciPath = append(ciPath, n.path...) - if len(path) == 0 { - // Check if this node has a handle registered for the given node - if n.handle[method] != nil { - return ciPath, true - } - - // No handle found. - // Try to fix the path by adding a trailing slash - if fixTrailingSlash { + if len(path) > 0 { + // If this node does not have a wildcard (param or catchAll) child, + // we can just look up the next child node and continue to walk down + // the tree + if !n.wildChild { + r := unicode.ToLower(rune(path[0])) for i, index := range n.indices { - if index == '/' { - n = n.children[i] - if (n.path == "/" && n.handle[method] != nil) || - (n.nType == catchAll && n.children[0].handle[method] != nil) { - return append(ciPath, '/'), true + // must use recursive approach since both index and + // ToLower(index) could exist. We must check both. + if r == unicode.ToLower(rune(index)) { + out, found := n.children[i].findCaseInsensitivePath(method, path, fixTrailingSlash) + if found { + return append(ciPath, out...), true } - return } } - } - return - } else if n.wildChild { - n = n.children[0] + // Nothing found. We can recommend to redirect to the same URL + // without a trailing slash if a leaf exists for that path + found = (fixTrailingSlash && path == "/" && n.handle[method] != nil) + return - switch n.nType { - case param: - // find param end (either '/'' or path end) - k := 0 - for k < len(path) && path[k] != '/' { - k++ - } + } else { + n = n.children[0] - // add param value to case insensitive path - ciPath = append(ciPath, path[:k]...) + switch n.nType { + case param: + // find param end (either '/' or path end) + k := 0 + for k < len(path) && path[k] != '/' { + k++ + } - // we need to go deeper! - if k < len(path) { - if len(n.children) > 0 { - path = path[k:] - n = n.children[0] - continue - } else { // ... but we can't - if fixTrailingSlash && len(path) == k+1 { - return ciPath, true + // add param value to case insensitive path + ciPath = append(ciPath, path[:k]...) + + // we need to go deeper! + if k < len(path) { + if len(n.children) > 0 { + path = path[k:] + n = n.children[0] + continue + } else { // ... but we can't + if fixTrailingSlash && len(path) == k+1 { + return ciPath, true + } + return } - return } - } - if n.handle[method] != nil { - return ciPath, true - } else if fixTrailingSlash && len(n.children) == 1 { - // No handle found. Check if a handle for this path + a - // trailing slash exists - n = n.children[0] - if n.path == "/" && n.handle[method] != nil { - return append(ciPath, '/'), true + if n.handle[method] != nil { + return ciPath, true + } else if fixTrailingSlash && len(n.children) == 1 { + // No handle found. Check if a handle for this path + a + // trailing slash exists + n = n.children[0] + if n.path == "/" && n.handle[method] != nil { + return append(ciPath, '/'), true + } } - } - return + return - case catchAll: - return append(ciPath, path...), true + case catchAll: + return append(ciPath, path...), true - default: - panic("Unknown node type") + default: + panic("Unknown node type") + } } - } else { - r := unicode.ToLower(rune(path[0])) - for i, index := range n.indices { - // must use recursive approach since both index and - // ToLower(index) could exist. We must check both. - if r == unicode.ToLower(rune(index)) { - out, found := n.children[i].findCaseInsensitivePath(method, path, fixTrailingSlash) - if found { - return append(ciPath, out...), true + // We should have reached the node containing the handle. + // Check if this node has a handle registered for the given method. + if n.handle[method] != nil { + return ciPath, true + } + + // No handle found. + // Try to fix the path by adding a trailing slash + if fixTrailingSlash { + for i, index := range n.indices { + if index == '/' { + n = n.children[i] + if (n.path == "/" && n.handle[method] != nil) || + (n.nType == catchAll && n.children[0].handle[method] != nil) { + return append(ciPath, '/'), true + } + return } } } - - // Nothing found. We can recommend to redirect to the same URL - // without a trailing slash if a leaf exists for that path - found = (fixTrailingSlash && path == "/" && n.handle[method] != nil) return } } @@ -474,12 +485,13 @@ func (n *node) findCaseInsensitivePath(method, path string, fixTrailingSlash boo // Nothing found. // Try to fix the path by adding / removing a trailing slash if fixTrailingSlash { + if path == "/" { + return ciPath, true + } if len(path)+1 == len(n.path) && n.path[len(path)] == '/' && strings.ToLower(path) == strings.ToLower(n.path[:len(path)]) && n.handle[method] != nil { return append(ciPath, n.path...), true - } else if path == "/" { - return ciPath, true } } return