forked from mirror/gin
tree: sync part httprouter codes and reduce if/else (#2163)
This commit is contained in:
parent
3957f6bb4b
commit
3abc96e3cd
370
tree.go
370
tree.go
|
@ -62,6 +62,15 @@ func min(a, b int) int {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func longestCommonPrefix(a, b string) int {
|
||||||
|
i := 0
|
||||||
|
max := min(len(a), len(b))
|
||||||
|
for i < max && a[i] == b[i] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
func countParams(path string) uint8 {
|
func countParams(path string) uint8 {
|
||||||
var n uint
|
var n uint
|
||||||
for i := 0; i < len(path); i++ {
|
for i := 0; i < len(path); i++ {
|
||||||
|
@ -127,135 +136,132 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
|
||||||
n.priority++
|
n.priority++
|
||||||
numParams := countParams(path)
|
numParams := countParams(path)
|
||||||
|
|
||||||
|
// Empty tree
|
||||||
|
if len(n.path) == 0 && len(n.children) == 0 {
|
||||||
|
n.insertChild(numParams, path, fullPath, handlers)
|
||||||
|
n.nType = root
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
parentFullPathIndex := 0
|
parentFullPathIndex := 0
|
||||||
|
|
||||||
// non-empty tree
|
walk:
|
||||||
if len(n.path) > 0 || len(n.children) > 0 {
|
for {
|
||||||
walk:
|
// Update maxParams of the current node
|
||||||
for {
|
if numParams > n.maxParams {
|
||||||
// Update maxParams of the current node
|
n.maxParams = numParams
|
||||||
if numParams > n.maxParams {
|
}
|
||||||
n.maxParams = numParams
|
|
||||||
|
// Find the longest common prefix.
|
||||||
|
// This also implies that the common prefix contains no ':' or '*'
|
||||||
|
// since the existing key can't contain those chars.
|
||||||
|
i := longestCommonPrefix(path, n.path)
|
||||||
|
|
||||||
|
// Split edge
|
||||||
|
if i < len(n.path) {
|
||||||
|
child := node{
|
||||||
|
path: n.path[i:],
|
||||||
|
wildChild: n.wildChild,
|
||||||
|
indices: n.indices,
|
||||||
|
children: n.children,
|
||||||
|
handlers: n.handlers,
|
||||||
|
priority: n.priority - 1,
|
||||||
|
fullPath: n.fullPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the longest common prefix.
|
// Update maxParams (max of all children)
|
||||||
// This also implies that the common prefix contains no ':' or '*'
|
for i := range child.children {
|
||||||
// since the existing key can't contain those chars.
|
if child.children[i].maxParams > child.maxParams {
|
||||||
i := 0
|
child.maxParams = child.children[i].maxParams
|
||||||
max := min(len(path), len(n.path))
|
}
|
||||||
for i < max && path[i] == n.path[i] {
|
|
||||||
i++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split edge
|
n.children = []*node{&child}
|
||||||
if i < len(n.path) {
|
// []byte for proper unicode char conversion, see #65
|
||||||
child := node{
|
n.indices = string([]byte{n.path[i]})
|
||||||
path: n.path[i:],
|
n.path = path[:i]
|
||||||
wildChild: n.wildChild,
|
n.handlers = nil
|
||||||
indices: n.indices,
|
n.wildChild = false
|
||||||
children: n.children,
|
n.fullPath = fullPath[:parentFullPathIndex+i]
|
||||||
handlers: n.handlers,
|
}
|
||||||
priority: n.priority - 1,
|
|
||||||
fullPath: n.fullPath,
|
// Make new node a child of this node
|
||||||
|
if i < len(path) {
|
||||||
|
path = path[i:]
|
||||||
|
|
||||||
|
if n.wildChild {
|
||||||
|
parentFullPathIndex += len(n.path)
|
||||||
|
n = n.children[0]
|
||||||
|
n.priority++
|
||||||
|
|
||||||
|
// Update maxParams of the child node
|
||||||
|
if numParams > n.maxParams {
|
||||||
|
n.maxParams = numParams
|
||||||
}
|
}
|
||||||
|
numParams--
|
||||||
|
|
||||||
// Update maxParams (max of all children)
|
// Check if the wildcard matches
|
||||||
for i := range child.children {
|
if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
|
||||||
if child.children[i].maxParams > child.maxParams {
|
// check for longer wildcard, e.g. :name and :names
|
||||||
child.maxParams = child.children[i].maxParams
|
if len(n.path) >= len(path) || path[len(n.path)] == '/' {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n.children = []*node{&child}
|
|
||||||
// []byte for proper unicode char conversion, see #65
|
|
||||||
n.indices = string([]byte{n.path[i]})
|
|
||||||
n.path = path[:i]
|
|
||||||
n.handlers = nil
|
|
||||||
n.wildChild = false
|
|
||||||
n.fullPath = fullPath[:parentFullPathIndex+i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make new node a child of this node
|
|
||||||
if i < len(path) {
|
|
||||||
path = path[i:]
|
|
||||||
|
|
||||||
if n.wildChild {
|
|
||||||
parentFullPathIndex += len(n.path)
|
|
||||||
n = n.children[0]
|
|
||||||
n.priority++
|
|
||||||
|
|
||||||
// Update maxParams of the child node
|
|
||||||
if numParams > n.maxParams {
|
|
||||||
n.maxParams = numParams
|
|
||||||
}
|
|
||||||
numParams--
|
|
||||||
|
|
||||||
// Check if the wildcard matches
|
|
||||||
if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
|
|
||||||
// check for longer wildcard, e.g. :name and :names
|
|
||||||
if len(n.path) >= len(path) || path[len(n.path)] == '/' {
|
|
||||||
continue walk
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pathSeg := path
|
|
||||||
if n.nType != catchAll {
|
|
||||||
pathSeg = strings.SplitN(path, "/", 2)[0]
|
|
||||||
}
|
|
||||||
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
|
||||||
panic("'" + pathSeg +
|
|
||||||
"' in new path '" + fullPath +
|
|
||||||
"' conflicts with existing wildcard '" + n.path +
|
|
||||||
"' in existing prefix '" + prefix +
|
|
||||||
"'")
|
|
||||||
}
|
|
||||||
|
|
||||||
c := path[0]
|
|
||||||
|
|
||||||
// slash after param
|
|
||||||
if n.nType == param && c == '/' && len(n.children) == 1 {
|
|
||||||
parentFullPathIndex += len(n.path)
|
|
||||||
n = n.children[0]
|
|
||||||
n.priority++
|
|
||||||
continue walk
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a child with the next path byte exists
|
|
||||||
for i := 0; i < len(n.indices); i++ {
|
|
||||||
if c == n.indices[i] {
|
|
||||||
parentFullPathIndex += len(n.path)
|
|
||||||
i = n.incrementChildPrio(i)
|
|
||||||
n = n.children[i]
|
|
||||||
continue walk
|
continue walk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise insert it
|
pathSeg := path
|
||||||
if c != ':' && c != '*' {
|
if n.nType != catchAll {
|
||||||
// []byte for proper unicode char conversion, see #65
|
pathSeg = strings.SplitN(path, "/", 2)[0]
|
||||||
n.indices += string([]byte{c})
|
|
||||||
child := &node{
|
|
||||||
maxParams: numParams,
|
|
||||||
fullPath: fullPath,
|
|
||||||
}
|
|
||||||
n.children = append(n.children, child)
|
|
||||||
n.incrementChildPrio(len(n.indices) - 1)
|
|
||||||
n = child
|
|
||||||
}
|
}
|
||||||
n.insertChild(numParams, path, fullPath, handlers)
|
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
||||||
return
|
panic("'" + pathSeg +
|
||||||
|
"' in new path '" + fullPath +
|
||||||
} else if i == len(path) { // Make node a (in-path) leaf
|
"' conflicts with existing wildcard '" + n.path +
|
||||||
if n.handlers != nil {
|
"' in existing prefix '" + prefix +
|
||||||
panic("handlers are already registered for path '" + fullPath + "'")
|
"'")
|
||||||
}
|
|
||||||
n.handlers = handlers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c := path[0]
|
||||||
|
|
||||||
|
// slash after param
|
||||||
|
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||||
|
parentFullPathIndex += len(n.path)
|
||||||
|
n = n.children[0]
|
||||||
|
n.priority++
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a child with the next path byte exists
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if c == n.indices[i] {
|
||||||
|
parentFullPathIndex += len(n.path)
|
||||||
|
i = n.incrementChildPrio(i)
|
||||||
|
n = n.children[i]
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise insert it
|
||||||
|
if c != ':' && c != '*' {
|
||||||
|
// []byte for proper unicode char conversion, see #65
|
||||||
|
n.indices += string([]byte{c})
|
||||||
|
child := &node{
|
||||||
|
maxParams: numParams,
|
||||||
|
fullPath: fullPath,
|
||||||
|
}
|
||||||
|
n.children = append(n.children, child)
|
||||||
|
n.incrementChildPrio(len(n.indices) - 1)
|
||||||
|
n = child
|
||||||
|
}
|
||||||
|
n.insertChild(numParams, path, fullPath, handlers)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
} else if i == len(path) { // Make node a (in-path) leaf
|
||||||
|
if n.handlers != nil {
|
||||||
|
panic("handlers are already registered for path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
n.handlers = handlers
|
||||||
}
|
}
|
||||||
} else { // Empty tree
|
return
|
||||||
n.insertChild(numParams, path, fullPath, handlers)
|
|
||||||
n.nType = root
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -542,75 +548,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
||||||
path = path[len(n.path):]
|
path = path[len(n.path):]
|
||||||
ciPath = append(ciPath, n.path...)
|
ciPath = append(ciPath, n.path...)
|
||||||
|
|
||||||
if len(path) > 0 {
|
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 {
|
|
||||||
// must use recursive approach since both index and
|
|
||||||
// ToLower(index) could exist. We must check both.
|
|
||||||
if r == unicode.ToLower(index) {
|
|
||||||
out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash)
|
|
||||||
if found {
|
|
||||||
return append(ciPath, out...), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.handlers != nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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++
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... but we can't
|
|
||||||
if fixTrailingSlash && len(path) == k+1 {
|
|
||||||
return ciPath, true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.handlers != 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.handlers != nil {
|
|
||||||
return append(ciPath, '/'), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
|
|
||||||
case catchAll:
|
|
||||||
return append(ciPath, path...), true
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic("invalid node type")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We should have reached the node containing the handle.
|
// We should have reached the node containing the handle.
|
||||||
// Check if this node has a handle registered.
|
// Check if this node has a handle registered.
|
||||||
if n.handlers != nil {
|
if n.handlers != nil {
|
||||||
|
@ -633,6 +571,74 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// must use recursive approach since both index and
|
||||||
|
// ToLower(index) could exist. We must check both.
|
||||||
|
if r == unicode.ToLower(index) {
|
||||||
|
out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash)
|
||||||
|
if found {
|
||||||
|
return append(ciPath, out...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.handlers != nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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++
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... but we can't
|
||||||
|
if fixTrailingSlash && len(path) == k+1 {
|
||||||
|
return ciPath, true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.handlers != 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.handlers != nil {
|
||||||
|
return append(ciPath, '/'), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
case catchAll:
|
||||||
|
return append(ciPath, path...), true
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("invalid node type")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing found.
|
// Nothing found.
|
||||||
|
|
Loading…
Reference in New Issue