mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-17 05:42:09 +08:00
Merge branch 'master' into master
This commit is contained in:
commit
6a15de79c5
@ -2096,3 +2096,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor
|
||||
* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares.
|
||||
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
|
||||
* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
|
||||
* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
|
||||
|
240
tree.go
240
tree.go
@ -107,16 +107,15 @@ type node struct {
|
||||
|
||||
// increments priority of the given child and reorders if necessary.
|
||||
func (n *node) incrementChildPrio(pos int) int {
|
||||
n.children[pos].priority++
|
||||
prio := n.children[pos].priority
|
||||
cs := n.children
|
||||
cs[pos].priority++
|
||||
prio := cs[pos].priority
|
||||
|
||||
// adjust position (move to front)
|
||||
// Adjust position (move to front)
|
||||
newPos := pos
|
||||
for newPos > 0 && n.children[newPos-1].priority < prio {
|
||||
// swap node positions
|
||||
n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1]
|
||||
|
||||
newPos--
|
||||
for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
|
||||
// Swap node positions
|
||||
cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
|
||||
}
|
||||
|
||||
// build new index char string
|
||||
@ -231,7 +230,7 @@ walk:
|
||||
}
|
||||
|
||||
// Check if a child with the next path byte exists
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
for i, max := 0, len(n.indices); i < max; i++ {
|
||||
if c == n.indices[i] {
|
||||
parentFullPathIndex += len(n.path)
|
||||
i = n.incrementChildPrio(i)
|
||||
@ -254,75 +253,91 @@ walk:
|
||||
}
|
||||
n.insertChild(numParams, path, fullPath, handlers)
|
||||
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
|
||||
}
|
||||
|
||||
// Otherwise and handle to current node
|
||||
if n.handlers != nil {
|
||||
panic("handlers are already registered for path '" + fullPath + "'")
|
||||
}
|
||||
n.handlers = handlers
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) {
|
||||
var offset int // already handled bytes of the path
|
||||
|
||||
// find prefix until first wildcard (beginning with ':' or '*')
|
||||
for i, max := 0, len(path); numParams > 0; i++ {
|
||||
c := path[i]
|
||||
// Search for a wildcard segment and check the name for invalid characters.
|
||||
// Returns -1 as index, if no wildcard war found.
|
||||
func findWildcard(path string) (wildcard string, i int, valid bool) {
|
||||
// Find start
|
||||
for start, c := range []byte(path) {
|
||||
// A wildcard starts with ':' (param) or '*' (catch-all)
|
||||
if c != ':' && c != '*' {
|
||||
continue
|
||||
}
|
||||
|
||||
// find wildcard end (either '/' or path end)
|
||||
end := i + 1
|
||||
for end < max && path[end] != '/' {
|
||||
switch path[end] {
|
||||
// the wildcard name must not contain ':' and '*'
|
||||
// Find end and check for invalid characters
|
||||
valid = true
|
||||
for end, c := range []byte(path[start+1:]) {
|
||||
switch c {
|
||||
case '/':
|
||||
return path[start : start+1+end], start, valid
|
||||
case ':', '*':
|
||||
panic("only one wildcard per path segment is allowed, has: '" +
|
||||
path[i:] + "' in path '" + fullPath + "'")
|
||||
default:
|
||||
end++
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
return path[start:], start, valid
|
||||
}
|
||||
return "", -1, false
|
||||
}
|
||||
|
||||
// check if this Node existing children which would be
|
||||
// unreachable if we insert the wildcard here
|
||||
if len(n.children) > 0 {
|
||||
panic("wildcard route '" + path[i:end] +
|
||||
"' conflicts with existing children in path '" + fullPath + "'")
|
||||
func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) {
|
||||
for numParams > 0 {
|
||||
// Find prefix until first wildcard
|
||||
wildcard, i, valid := findWildcard(path)
|
||||
if i < 0 { // No wildcard found
|
||||
break
|
||||
}
|
||||
|
||||
// The wildcard name must not contain ':' and '*'
|
||||
if !valid {
|
||||
panic("only one wildcard per path segment is allowed, has: '" +
|
||||
wildcard + "' in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
// check if the wildcard has a name
|
||||
if end-i < 2 {
|
||||
if len(wildcard) < 2 {
|
||||
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
if c == ':' { // param
|
||||
// split path at the beginning of the wildcard
|
||||
// Check if this node has existing children which would be
|
||||
// unreachable if we insert the wildcard here
|
||||
if len(n.children) > 0 {
|
||||
panic("wildcard segment '" + wildcard +
|
||||
"' conflicts with existing children in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
if wildcard[0] == ':' { // param
|
||||
if i > 0 {
|
||||
n.path = path[offset:i]
|
||||
offset = i
|
||||
// Insert prefix before the current wildcard
|
||||
n.path = path[:i]
|
||||
path = path[i:]
|
||||
}
|
||||
|
||||
n.wildChild = true
|
||||
child := &node{
|
||||
nType: param,
|
||||
path: wildcard,
|
||||
maxParams: numParams,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
n.children = []*node{child}
|
||||
n.wildChild = true
|
||||
n = child
|
||||
n.priority++
|
||||
numParams--
|
||||
|
||||
// if the path doesn't end with the wildcard, then there
|
||||
// will be another non-wildcard subpath starting with '/'
|
||||
if end < max {
|
||||
n.path = path[offset:end]
|
||||
offset = end
|
||||
if len(wildcard) < len(path) {
|
||||
path = path[len(wildcard):]
|
||||
|
||||
child := &node{
|
||||
maxParams: numParams,
|
||||
@ -331,54 +346,63 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
|
||||
}
|
||||
n.children = []*node{child}
|
||||
n = child
|
||||
continue
|
||||
}
|
||||
|
||||
} else { // catchAll
|
||||
if end != max || numParams > 1 {
|
||||
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
// currently fixed width 1 for '/'
|
||||
i--
|
||||
if path[i] != '/' {
|
||||
panic("no / before catch-all in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
n.path = path[offset:i]
|
||||
|
||||
// first node: catchAll node with empty path
|
||||
child := &node{
|
||||
wildChild: true,
|
||||
nType: catchAll,
|
||||
maxParams: 1,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
n.children = []*node{child}
|
||||
n.indices = string(path[i])
|
||||
n = child
|
||||
n.priority++
|
||||
|
||||
// second node: node holding the variable
|
||||
child = &node{
|
||||
path: path[i:],
|
||||
nType: catchAll,
|
||||
maxParams: 1,
|
||||
handlers: handlers,
|
||||
priority: 1,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
n.children = []*node{child}
|
||||
|
||||
// Otherwise we're done. Insert the handle in the new leaf
|
||||
n.handlers = handlers
|
||||
return
|
||||
}
|
||||
|
||||
// catchAll
|
||||
if i+len(wildcard) != len(path) || numParams > 1 {
|
||||
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
// currently fixed width 1 for '/'
|
||||
i--
|
||||
if path[i] != '/' {
|
||||
panic("no / before catch-all in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
n.path = path[:i]
|
||||
|
||||
// First node: catchAll node with empty path
|
||||
child := &node{
|
||||
wildChild: true,
|
||||
nType: catchAll,
|
||||
maxParams: 1,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
// update maxParams of the parent node
|
||||
if n.maxParams < 1 {
|
||||
n.maxParams = 1
|
||||
}
|
||||
n.children = []*node{child}
|
||||
n.indices = string('/')
|
||||
n = child
|
||||
n.priority++
|
||||
|
||||
// second node: node holding the variable
|
||||
child = &node{
|
||||
path: path[i:],
|
||||
nType: catchAll,
|
||||
maxParams: 1,
|
||||
handlers: handlers,
|
||||
priority: 1,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
n.children = []*node{child}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// insert remaining path part and handle to the leaf
|
||||
n.path = path[offset:]
|
||||
// If no wildcard was found, simple insert the path and handle
|
||||
n.path = path
|
||||
n.handlers = handlers
|
||||
n.fullPath = fullPath
|
||||
}
|
||||
@ -400,17 +424,20 @@ func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue)
|
||||
value.params = po
|
||||
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):]
|
||||
prefix := n.path
|
||||
if len(path) > len(prefix) {
|
||||
if path[:len(prefix)] == prefix {
|
||||
path = path[len(prefix):]
|
||||
// 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 := 0; i < len(n.indices); i++ {
|
||||
if c == n.indices[i] {
|
||||
indices := n.indices
|
||||
for i, max := 0, len(indices); i < max; i++ {
|
||||
if c == indices[i] {
|
||||
n = n.children[i]
|
||||
prefix = n.path
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
@ -454,6 +481,7 @@ walk: // Outer loop for walking the tree
|
||||
if len(n.children) > 0 {
|
||||
path = path[end:]
|
||||
n = n.children[0]
|
||||
prefix = n.path
|
||||
continue walk
|
||||
}
|
||||
|
||||
@ -500,7 +528,7 @@ walk: // Outer loop for walking the tree
|
||||
panic("invalid node type")
|
||||
}
|
||||
}
|
||||
} else if path == n.path {
|
||||
} else if path == prefix {
|
||||
// We should have reached the node containing the handle.
|
||||
// Check if this node has a handle registered.
|
||||
if value.handlers = n.handlers; value.handlers != nil {
|
||||
@ -515,8 +543,9 @@ walk: // Outer loop for walking the tree
|
||||
|
||||
// No handle found. Check if a handle for this path + a
|
||||
// trailing slash exists for trailing slash recommendation
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
if n.indices[i] == '/' {
|
||||
indices := n.indices
|
||||
for i, max := 0, len(indices); i < max; i++ {
|
||||
if indices[i] == '/' {
|
||||
n = n.children[i]
|
||||
value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
|
||||
(n.nType == catchAll && n.children[0].handlers != nil)
|
||||
@ -530,8 +559,8 @@ walk: // Outer loop for walking the tree
|
||||
// Nothing found. We can recommend to redirect to the same URL with an
|
||||
// extra trailing slash if a leaf exists for that path
|
||||
value.tsr = (path == "/") ||
|
||||
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
|
||||
path == n.path[:len(n.path)-1] && n.handlers != nil)
|
||||
(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
|
||||
path == prefix[:len(prefix)-1] && n.handlers != nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -597,25 +626,25 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
||||
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++
|
||||
// Find param end (either '/' or path end)
|
||||
end := 0
|
||||
for end < len(path) && path[end] != '/' {
|
||||
end++
|
||||
}
|
||||
|
||||
// add param value to case insensitive path
|
||||
ciPath = append(ciPath, path[:k]...)
|
||||
ciPath = append(ciPath, path[:end]...)
|
||||
|
||||
// we need to go deeper!
|
||||
if k < len(path) {
|
||||
if end < len(path) {
|
||||
if len(n.children) > 0 {
|
||||
path = path[k:]
|
||||
path = path[end:]
|
||||
n = n.children[0]
|
||||
continue
|
||||
}
|
||||
|
||||
// ... but we can't
|
||||
if fixTrailingSlash && len(path) == k+1 {
|
||||
if fixTrailingSlash && len(path) == end+1 {
|
||||
return ciPath, true
|
||||
}
|
||||
return
|
||||
@ -623,7 +652,8 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
||||
|
||||
if n.handlers != nil {
|
||||
return ciPath, true
|
||||
} else if fixTrailingSlash && len(n.children) == 1 {
|
||||
}
|
||||
if fixTrailingSlash && len(n.children) == 1 {
|
||||
// No handle found. Check if a handle for this path + a
|
||||
// trailing slash exists
|
||||
n = n.children[0]
|
||||
|
@ -368,6 +368,13 @@ func TestTreeCatchAllConflictRoot(t *testing.T) {
|
||||
testRoutes(t, routes)
|
||||
}
|
||||
|
||||
func TestTreeCatchMaxParams(t *testing.T) {
|
||||
tree := &node{}
|
||||
var route = "/cmd/*filepath"
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
checkMaxParams(t, tree)
|
||||
}
|
||||
|
||||
func TestTreeDoubleWildcard(t *testing.T) {
|
||||
const panicMsg = "only one wildcard per path segment is allowed"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user