mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-16 13:22:09 +08:00
Merge 90a6a2cd688f593e5b2cc645a7deffd4982a405c into 8659ab573cf7d26b2fa2a41e90075d84606188f1
This commit is contained in:
commit
cfefee80db
@ -281,6 +281,11 @@ func main() {
|
||||
router.GET("/user/groups", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "The available groups are [...]")
|
||||
})
|
||||
|
||||
// This handler will match when none of the above routes match
|
||||
router.GET("/*action", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "Unknown Action")
|
||||
})
|
||||
|
||||
router.Run(":8080")
|
||||
}
|
||||
@ -1275,11 +1280,12 @@ func main() {
|
||||
```go
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
router.StaticFS("/", http.Dir("dist"))
|
||||
router.Static("/assets", "./assets")
|
||||
router.StaticFS("/more_static", http.Dir("my_file_system"))
|
||||
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
|
||||
router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system"))
|
||||
|
||||
|
||||
// Listen and serve on 0.0.0.0:8080
|
||||
router.Run(":8080")
|
||||
}
|
||||
|
@ -414,9 +414,14 @@ func testGetRequestHandler(t *testing.T, h http.Handler, url string) {
|
||||
|
||||
func TestTreeRunDynamicRouting(t *testing.T) {
|
||||
router := New()
|
||||
router.GET("/aa/*xx", func(c *Context) { c.String(http.StatusOK, "/aa/*xx") })
|
||||
router.GET("/ab/*xx", func(c *Context) { c.String(http.StatusOK, "/ab/*xx") })
|
||||
router.GET("/", func(c *Context) { c.String(http.StatusOK, "home") })
|
||||
router.GET("/aa/*xx", func(c *Context) { c.String(http.StatusOK, "/aa/*xx") })
|
||||
router.GET("/ab/aa", func(c *Context) { c.String(http.StatusOK, "/ab/aa") })
|
||||
router.GET("/ab/*xx", func(c *Context) { c.String(http.StatusOK, "/ab/*xx") })
|
||||
router.GET("/ab/zz", func(c *Context) { c.String(http.StatusOK, "/ab/zz") })
|
||||
router.GET("/abc/def", func(c *Context) { c.String(http.StatusOK, "/abc/def") })
|
||||
router.GET("/abc/d/:ee/*all", func(c *Context) { c.String(http.StatusOK, "/abc/d/:ee/*all") })
|
||||
router.GET("/abc/de/*all", func(c *Context) { c.String(http.StatusOK, "/abc/de/*all") })
|
||||
router.GET("/:cc", func(c *Context) { c.String(http.StatusOK, "/:cc") })
|
||||
router.GET("/c1/:dd/e", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e") })
|
||||
router.GET("/c1/:dd/e1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e1") })
|
||||
@ -429,6 +434,7 @@ func TestTreeRunDynamicRouting(t *testing.T) {
|
||||
router.GET("/:cc/:dd/:ee/:ff/gg", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/gg") })
|
||||
router.GET("/:cc/:dd/:ee/:ff/:gg/hh", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/:gg/hh") })
|
||||
router.GET("/get/test/abc/", func(c *Context) { c.String(http.StatusOK, "/get/test/abc/") })
|
||||
router.GET("/get/:param/*all", func(c *Context) { c.String(http.StatusOK, "/get/:param/*all") })
|
||||
router.GET("/get/:param/abc/", func(c *Context) { c.String(http.StatusOK, "/get/:param/abc/") })
|
||||
router.GET("/something/:paramname/thirdthing", func(c *Context) { c.String(http.StatusOK, "/something/:paramname/thirdthing") })
|
||||
router.GET("/something/secondthing/test", func(c *Context) { c.String(http.StatusOK, "/something/secondthing/test") })
|
||||
@ -457,7 +463,13 @@ func TestTreeRunDynamicRouting(t *testing.T) {
|
||||
|
||||
testRequest(t, ts.URL+"/", "", "home")
|
||||
testRequest(t, ts.URL+"/aa/aa", "", "/aa/*xx")
|
||||
testRequest(t, ts.URL+"/ab/aa", "", "/ab/aa")
|
||||
testRequest(t, ts.URL+"/ab/zz", "", "/ab/zz")
|
||||
testRequest(t, ts.URL+"/ab/ab", "", "/ab/*xx")
|
||||
testRequest(t, ts.URL+"/ab/aab", "", "/ab/*xx")
|
||||
testRequest(t, ts.URL+"/ab/", "", "/ab/*xx")
|
||||
testRequest(t, ts.URL+"/abc/d/e", "", "/abc/d/:ee/*all")
|
||||
testRequest(t, ts.URL+"/abc/d//", "", "/abc/d/:ee/*all")
|
||||
testRequest(t, ts.URL+"/all", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/all/cc", "", "/:cc/cc")
|
||||
testRequest(t, ts.URL+"/a/cc", "", "/:cc/cc")
|
||||
@ -496,6 +508,10 @@ func TestTreeRunDynamicRouting(t *testing.T) {
|
||||
testRequest(t, ts.URL+"/get/t/abc/", "", "/get/:param/abc/")
|
||||
testRequest(t, ts.URL+"/get/aa/abc/", "", "/get/:param/abc/")
|
||||
testRequest(t, ts.URL+"/get/abas/abc/", "", "/get/:param/abc/")
|
||||
testRequest(t, ts.URL+"/get/testt/abc", "", "/get/:param/abc/")
|
||||
testRequest(t, ts.URL+"/get/test/", "", "/get/:param")
|
||||
testRequest(t, ts.URL+"/get/test/abcde/", "", "/get/:param/*all")
|
||||
testRequest(t, ts.URL+"/get/test//abc", "", "/get/:param/*all")
|
||||
testRequest(t, ts.URL+"/something/secondthing/test", "", "/something/secondthing/test")
|
||||
testRequest(t, ts.URL+"/something/secondthingaaaa/thirdthing", "", "/something/:paramname/thirdthing")
|
||||
testRequest(t, ts.URL+"/something/abcdad/thirdthing", "", "/something/:paramname/thirdthing")
|
||||
@ -547,6 +563,9 @@ func TestTreeRunDynamicRouting(t *testing.T) {
|
||||
testRequest(t, ts.URL+"/get/abc/123abf/testss", "", "/get/abc/123abf/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abfff/te", "", "/get/abc/123abfff/:param")
|
||||
// 404 not found
|
||||
testRequest(t, ts.URL+"/abc/deX", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/abc/defX", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/abc/d/", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/c/d/e", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/c/d/e1", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/c/d/eee", "404 Not Found")
|
||||
|
459
tree.go
459
tree.go
@ -6,6 +6,7 @@ package gin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"unicode"
|
||||
@ -147,6 +148,19 @@ func (n *node) incrementChildPrio(pos int) int {
|
||||
return newPos
|
||||
}
|
||||
|
||||
func (n *node) panicWildcardConflict(fullPath, path string) {
|
||||
wildChild := n.children[len(n.children)-1]
|
||||
pathSeg := path
|
||||
if wildChild.nType != catchAll {
|
||||
pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
|
||||
}
|
||||
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + wildChild.path
|
||||
panic(fmt.Sprintf(
|
||||
"'%s' in new path '%s' conflicts with existing wildcard '%s' in existing prefix '%s'",
|
||||
pathSeg, fullPath, wildChild.path, prefix,
|
||||
))
|
||||
}
|
||||
|
||||
// addRoute adds a node with the given handle to the path.
|
||||
// Not concurrency-safe!
|
||||
func (n *node) addRoute(path string, handlers HandlersChain) {
|
||||
@ -168,6 +182,9 @@ walk:
|
||||
// This also implies that the common prefix contains no ':' or '*'
|
||||
// since the existing key can't contain those chars.
|
||||
i := longestCommonPrefix(path, n.path)
|
||||
if i > 0 && i < len(path) && path[i-1:i+1] == "/*" {
|
||||
i--
|
||||
}
|
||||
|
||||
// Split edge
|
||||
if i < len(n.path) {
|
||||
@ -195,27 +212,37 @@ walk:
|
||||
if i < len(path) {
|
||||
path = path[i:]
|
||||
c := path[0]
|
||||
isCatchAll := strings.HasPrefix(path, "/*")
|
||||
|
||||
// '/' 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, max := 0, len(n.indices); i < max; i++ {
|
||||
if c == n.indices[i] {
|
||||
parentFullPathIndex += len(n.path)
|
||||
i = n.incrementChildPrio(i)
|
||||
n = n.children[i]
|
||||
continue walk
|
||||
if len(n.children) > 0 {
|
||||
child := n.children[len(n.children)-1]
|
||||
if child.nType == param && c == ':' {
|
||||
if strings.HasPrefix(path, child.path) && (len(path) == len(child.path) || path[len(child.path)] == '/') {
|
||||
child.priority++
|
||||
n = child
|
||||
continue walk
|
||||
}
|
||||
n.panicWildcardConflict(fullPath, path)
|
||||
}
|
||||
if child.nType == catchAll && (isCatchAll || strings.HasPrefix(path, "/:")) {
|
||||
n.panicWildcardConflict(fullPath, path)
|
||||
}
|
||||
if isCatchAll && child.path == "/" && child.wildChild && child.children[len(child.children)-1].nType == param {
|
||||
n.panicWildcardConflict(fullPath, path)
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise insert it
|
||||
if c != ':' && c != '*' && n.nType != catchAll {
|
||||
if c != ':' && !isCatchAll {
|
||||
// Check if a child with the next path byte exists
|
||||
for i, max := 0, len(n.indices); i < max; i++ {
|
||||
if c == n.indices[i] {
|
||||
parentFullPathIndex += len(n.path)
|
||||
i = n.incrementChildPrio(i)
|
||||
n = n.children[i]
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
// Otherwise insert it
|
||||
// []byte for proper unicode char conversion, see #65
|
||||
n.indices += bytesconv.BytesToString([]byte{c})
|
||||
child := &node{
|
||||
@ -224,31 +251,6 @@ walk:
|
||||
n.addChild(child)
|
||||
n.incrementChildPrio(len(n.indices) - 1)
|
||||
n = child
|
||||
} else if n.wildChild {
|
||||
// inserting a wildcard node, need to check if it conflicts with the existing wildcard
|
||||
n = n.children[len(n.children)-1]
|
||||
n.priority++
|
||||
|
||||
// Check if the wildcard matches
|
||||
if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
|
||||
// Adding a child to a catchAll is not possible
|
||||
n.nType != catchAll &&
|
||||
// Check for longer wildcard, e.g. :name and :names
|
||||
(len(n.path) >= len(path) || path[len(n.path)] == '/') {
|
||||
continue walk
|
||||
}
|
||||
|
||||
// Wildcard conflict
|
||||
pathSeg := path
|
||||
if n.nType != catchAll {
|
||||
pathSeg = strings.SplitN(pathSeg, "/", 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 +
|
||||
"'")
|
||||
}
|
||||
|
||||
n.insertChild(path, fullPath, handlers)
|
||||
@ -330,13 +332,15 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
|
||||
// will be another subpath starting with '/'
|
||||
if len(wildcard) < len(path) {
|
||||
path = path[len(wildcard):]
|
||||
|
||||
child := &node{
|
||||
priority: 1,
|
||||
fullPath: fullPath,
|
||||
if !strings.HasPrefix(path, "/*") {
|
||||
child := &node{
|
||||
priority: 1,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
n.addChild(child)
|
||||
n.indices = "/"
|
||||
n = child
|
||||
}
|
||||
n.addChild(child)
|
||||
n = child
|
||||
continue
|
||||
}
|
||||
|
||||
@ -350,44 +354,22 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
|
||||
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] == '/' {
|
||||
pathSeg := strings.SplitN(n.children[0].path, "/", 2)[0]
|
||||
panic("catch-all wildcard '" + path +
|
||||
"' in new path '" + fullPath +
|
||||
"' conflicts with existing path segment '" + pathSeg +
|
||||
"' in existing prefix '" + n.path + pathSeg +
|
||||
"'")
|
||||
}
|
||||
|
||||
// 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,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
|
||||
n.addChild(child)
|
||||
n.indices = string('/')
|
||||
n = child
|
||||
n.priority++
|
||||
|
||||
// second node: node holding the variable
|
||||
child = &node{
|
||||
path: path[i:],
|
||||
nType: catchAll,
|
||||
handlers: handlers,
|
||||
priority: 1,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
n.children = []*node{child}
|
||||
n.addChild(child)
|
||||
n.path += path[:i]
|
||||
n.wildChild = true
|
||||
|
||||
return
|
||||
}
|
||||
@ -419,177 +401,178 @@ type skippedNode struct {
|
||||
// given path.
|
||||
func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
|
||||
var globalParamsCount int16
|
||||
parentHasHandlers := false
|
||||
|
||||
rollbackSkipped := func() bool {
|
||||
if l := len(*skippedNodes); l > 0 {
|
||||
skippedNode := (*skippedNodes)[l-1]
|
||||
*skippedNodes = (*skippedNodes)[:l-1]
|
||||
path = skippedNode.path
|
||||
n = skippedNode.node
|
||||
if value.params != nil {
|
||||
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||
}
|
||||
globalParamsCount = skippedNode.paramsCount
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
walk: // Outer loop for walking the tree
|
||||
for {
|
||||
prefix := n.path
|
||||
if len(path) > len(prefix) {
|
||||
if path[:len(prefix)] == prefix {
|
||||
path = path[len(prefix):]
|
||||
// Search in children
|
||||
if len(path) > len(prefix) && path[:len(prefix)] == prefix || n.nType == param {
|
||||
if n.nType == param {
|
||||
prefix = ""
|
||||
}
|
||||
path = path[len(prefix):]
|
||||
|
||||
// Try all the non-wildcard children first by matching the indices
|
||||
idxc := path[0]
|
||||
for i, c := range []byte(n.indices) {
|
||||
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{
|
||||
path: prefix + path,
|
||||
node: &node{
|
||||
path: n.path,
|
||||
wildChild: n.wildChild,
|
||||
nType: n.nType,
|
||||
priority: n.priority,
|
||||
children: n.children,
|
||||
handlers: n.handlers,
|
||||
fullPath: n.fullPath,
|
||||
},
|
||||
paramsCount: globalParamsCount,
|
||||
}
|
||||
// Try all the non-wildcard children first by matching the indices
|
||||
idxc := path[0]
|
||||
for i, c := range []byte(n.indices) {
|
||||
if c == idxc {
|
||||
parentHasHandlers = len(n.handlers) > 0
|
||||
if n.wildChild {
|
||||
index := len(*skippedNodes)
|
||||
*skippedNodes = (*skippedNodes)[:index+1]
|
||||
(*skippedNodes)[index] = skippedNode{
|
||||
path: prefix + path,
|
||||
node: &node{
|
||||
path: n.path,
|
||||
wildChild: n.wildChild,
|
||||
nType: n.nType,
|
||||
priority: n.priority,
|
||||
children: n.children,
|
||||
handlers: n.handlers,
|
||||
fullPath: n.fullPath,
|
||||
},
|
||||
paramsCount: globalParamsCount,
|
||||
}
|
||||
}
|
||||
|
||||
n = n.children[i]
|
||||
n = n.children[i]
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
|
||||
if !n.wildChild {
|
||||
// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
|
||||
// the current node needs to roll back to last valid skippedNode
|
||||
if path != "/" && rollbackSkipped() {
|
||||
continue walk
|
||||
}
|
||||
|
||||
// Nothing found.
|
||||
// We can recommend to redirect to the same URL without a
|
||||
// trailing slash if a leaf exists for that path.
|
||||
value.tsr = path == "/" && n.handlers != nil
|
||||
return
|
||||
}
|
||||
|
||||
// Handle wildcard child, which is always at the end of the array
|
||||
n = n.children[len(n.children)-1]
|
||||
globalParamsCount++
|
||||
|
||||
switch n.nType {
|
||||
case param:
|
||||
// fix truncate the parameter
|
||||
// tree_test.go line: 204
|
||||
|
||||
// Find param end (either '/' or path end)
|
||||
end := 0
|
||||
for end < len(path) && path[end] != '/' {
|
||||
end++
|
||||
}
|
||||
|
||||
// Save param value
|
||||
if params != nil && cap(*params) > 0 {
|
||||
if value.params == nil {
|
||||
value.params = params
|
||||
}
|
||||
// Expand slice within preallocated capacity
|
||||
i := len(*value.params)
|
||||
*value.params = (*value.params)[:i+1]
|
||||
val := path[:end]
|
||||
if unescape {
|
||||
if v, err := url.QueryUnescape(val); err == nil {
|
||||
val = v
|
||||
}
|
||||
}
|
||||
(*value.params)[i] = Param{
|
||||
Key: n.path[1:],
|
||||
Value: val,
|
||||
}
|
||||
}
|
||||
|
||||
// we need to go deeper!
|
||||
if end < len(path) {
|
||||
if len(n.children) > 0 {
|
||||
path = path[end:]
|
||||
parentHasHandlers = len(n.handlers) > 0
|
||||
if n.children[len(n.children)-1].nType != catchAll {
|
||||
n = n.children[0]
|
||||
}
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
|
||||
if !n.wildChild {
|
||||
// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
|
||||
// the current node needs to roll back to last valid skippedNode
|
||||
if path != "/" {
|
||||
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
|
||||
if value.params != nil {
|
||||
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||
}
|
||||
globalParamsCount = skippedNode.paramsCount
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found.
|
||||
// We can recommend to redirect to the same URL without a
|
||||
// trailing slash if a leaf exists for that path.
|
||||
value.tsr = path == "/" && n.handlers != nil
|
||||
// ... but we can't
|
||||
value.tsr = len(path) == end+1
|
||||
return
|
||||
}
|
||||
|
||||
// Handle wildcard child, which is always at the end of the array
|
||||
n = n.children[len(n.children)-1]
|
||||
globalParamsCount++
|
||||
|
||||
switch n.nType {
|
||||
case param:
|
||||
// fix truncate the parameter
|
||||
// tree_test.go line: 204
|
||||
|
||||
// Find param end (either '/' or path end)
|
||||
end := 0
|
||||
for end < len(path) && path[end] != '/' {
|
||||
end++
|
||||
}
|
||||
|
||||
// Save param value
|
||||
if params != nil && cap(*params) > 0 {
|
||||
if value.params == nil {
|
||||
value.params = params
|
||||
}
|
||||
// Expand slice within preallocated capacity
|
||||
i := len(*value.params)
|
||||
*value.params = (*value.params)[:i+1]
|
||||
val := path[:end]
|
||||
if unescape {
|
||||
if v, err := url.QueryUnescape(val); err == nil {
|
||||
val = v
|
||||
}
|
||||
}
|
||||
(*value.params)[i] = Param{
|
||||
Key: n.path[1:],
|
||||
Value: val,
|
||||
}
|
||||
}
|
||||
|
||||
// we need to go deeper!
|
||||
if end < len(path) {
|
||||
if len(n.children) > 0 {
|
||||
path = path[end:]
|
||||
n = n.children[0]
|
||||
continue walk
|
||||
}
|
||||
|
||||
// ... but we can't
|
||||
value.tsr = len(path) == end+1
|
||||
return
|
||||
}
|
||||
|
||||
if value.handlers = n.handlers; value.handlers != nil {
|
||||
value.fullPath = n.fullPath
|
||||
return
|
||||
}
|
||||
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]
|
||||
value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/")
|
||||
}
|
||||
return
|
||||
|
||||
case catchAll:
|
||||
// Save param value
|
||||
if params != nil {
|
||||
if value.params == nil {
|
||||
value.params = params
|
||||
}
|
||||
// Expand slice within preallocated capacity
|
||||
i := len(*value.params)
|
||||
*value.params = (*value.params)[:i+1]
|
||||
val := path
|
||||
if unescape {
|
||||
if v, err := url.QueryUnescape(path); err == nil {
|
||||
val = v
|
||||
}
|
||||
}
|
||||
(*value.params)[i] = Param{
|
||||
Key: n.path[2:],
|
||||
Value: val,
|
||||
}
|
||||
}
|
||||
|
||||
value.handlers = n.handlers
|
||||
if value.handlers = n.handlers; value.handlers != nil {
|
||||
value.fullPath = n.fullPath
|
||||
return
|
||||
|
||||
default:
|
||||
panic("invalid node type")
|
||||
}
|
||||
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]
|
||||
value.tsr = (n.path == "/" && n.handlers != nil) || n.nType == catchAll
|
||||
}
|
||||
return
|
||||
|
||||
case catchAll:
|
||||
if path[0] != '/' {
|
||||
if rollbackSkipped() {
|
||||
continue walk
|
||||
}
|
||||
return
|
||||
}
|
||||
// Save param value
|
||||
if params != nil {
|
||||
if value.params == nil {
|
||||
value.params = params
|
||||
}
|
||||
// Expand slice within preallocated capacity
|
||||
i := len(*value.params)
|
||||
*value.params = (*value.params)[:i+1]
|
||||
val := path
|
||||
if unescape {
|
||||
if v, err := url.QueryUnescape(path); err == nil {
|
||||
val = v
|
||||
}
|
||||
}
|
||||
(*value.params)[i] = Param{
|
||||
Key: n.path[2:],
|
||||
Value: val,
|
||||
}
|
||||
}
|
||||
value.handlers = n.handlers
|
||||
value.fullPath = n.fullPath
|
||||
return
|
||||
|
||||
default:
|
||||
panic("invalid node type")
|
||||
}
|
||||
}
|
||||
|
||||
if path == prefix {
|
||||
// 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 roll back to last valid skippedNode
|
||||
if n.handlers == nil && path != "/" {
|
||||
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
|
||||
if value.params != nil {
|
||||
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||
}
|
||||
globalParamsCount = skippedNode.paramsCount
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
// n = latestNode.children[len(latestNode.children)-1]
|
||||
if n.handlers == nil && rollbackSkipped() {
|
||||
continue walk
|
||||
}
|
||||
// We should have reached the node containing the handle.
|
||||
// Check if this node has a handle registered.
|
||||
@ -616,36 +599,26 @@ walk: // Outer loop for walking the tree
|
||||
for i, c := range []byte(n.indices) {
|
||||
if c == '/' {
|
||||
n = n.children[i]
|
||||
value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
|
||||
(n.nType == catchAll && n.children[0].handlers != nil)
|
||||
value.tsr = len(n.path) == 1 && n.handlers != nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
value.tsr = n.children[len(n.children)-1].nType == catchAll
|
||||
return
|
||||
}
|
||||
|
||||
// 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(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
|
||||
path == prefix[:len(prefix)-1] && n.handlers != nil)
|
||||
// Nothing found. Look for trailing slash recommendation
|
||||
value.tsr =
|
||||
// URL of parent node has one less trailing slash than path
|
||||
path == "/" && parentHasHandlers ||
|
||||
// URL of current node has one more trailing slash than path
|
||||
len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
|
||||
path == prefix[:len(prefix)-1] && n.handlers != nil
|
||||
|
||||
// roll back to last valid skippedNode
|
||||
if !value.tsr && path != "/" {
|
||||
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
|
||||
if value.params != nil {
|
||||
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||
}
|
||||
globalParamsCount = skippedNode.paramsCount
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
if !value.tsr && rollbackSkipped() {
|
||||
continue walk
|
||||
}
|
||||
|
||||
return
|
||||
|
91
tree_test.go
91
tree_test.go
@ -160,7 +160,12 @@ func TestTreeWildcard(t *testing.T) {
|
||||
"/info/:user/project/:project",
|
||||
"/info/:user/project/golang",
|
||||
"/aa/*xx",
|
||||
"/ab/aa",
|
||||
"/ab/*xx",
|
||||
"/ab/zz",
|
||||
"/abc/def",
|
||||
"/abc/d/:ee/*all",
|
||||
"/abc/de/*all",
|
||||
"/:cc",
|
||||
"/c1/:dd/e",
|
||||
"/c1/:dd/e1",
|
||||
@ -170,6 +175,7 @@ func TestTreeWildcard(t *testing.T) {
|
||||
"/:cc/:dd/:ee/:ff/gg",
|
||||
"/:cc/:dd/:ee/:ff/:gg/hh",
|
||||
"/get/test/abc/",
|
||||
"/get/:param/*all",
|
||||
"/get/:param/abc/",
|
||||
"/something/:paramname/thirdthing",
|
||||
"/something/secondthing/test",
|
||||
@ -225,7 +231,16 @@ func TestTreeWildcard(t *testing.T) {
|
||||
{"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}},
|
||||
{"/info/gordon/project/golang", false, "/info/:user/project/golang", Params{Param{Key: "user", Value: "gordon"}}},
|
||||
{"/aa/aa", false, "/aa/*xx", Params{Param{Key: "xx", Value: "/aa"}}},
|
||||
{"/ab/aa", false, "/ab/aa", nil},
|
||||
{"/ab/zz", false, "/ab/zz", nil},
|
||||
{"/ab/ab", false, "/ab/*xx", Params{Param{Key: "xx", Value: "/ab"}}},
|
||||
{"/ab/aab", false, "/ab/*xx", Params{Param{Key: "xx", Value: "/aab"}}},
|
||||
{"/ab/", false, "/ab/*xx", Params{Param{Key: "xx", Value: "/"}}},
|
||||
{"/abc/d/e", true, "/abc/d/:ee/*all", Params{Param{Key: "ee", Value: "e"}}},
|
||||
{"/abc/d//", false, "/abc/d/:ee/*all", Params{Param{Key: "ee", Value: ""}, Param{Key: "all", Value: "/"}}},
|
||||
{"/abc/deX", true, "", Params{Param{Key: "cc", Value: "abc"}, Param{Key: "dd", Value: "deX"}}},
|
||||
{"/abc/defX", true, "", Params{Param{Key: "cc", Value: "abc"}, Param{Key: "dd", Value: "defX"}}},
|
||||
{"/abc/d/", true, "", Params{Param{Key: "cc", Value: "abc"}, Param{Key: "dd", Value: "d"}}},
|
||||
{"/a", false, "/:cc", Params{Param{Key: "cc", Value: "a"}}},
|
||||
// * Error with argument being intercepted
|
||||
// new PR handle (/all /all/cc /a/cc)
|
||||
@ -261,6 +276,10 @@ func TestTreeWildcard(t *testing.T) {
|
||||
{"/get/t/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "t"}}},
|
||||
{"/get/aa/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "aa"}}},
|
||||
{"/get/abas/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "abas"}}},
|
||||
{"/get/testt/abc", true, "/get/:param/abc/", Params{Param{Key: "param", Value: "testt"}}},
|
||||
{"/get/test/", true, "/get/:param", Params{Param{Key: "param", Value: "test"}}},
|
||||
{"/get/test/abcde/", false, "/get/:param/*all", Params{Param{Key: "param", Value: "test"}, Param{Key: "all", Value: "/abcde/"}}},
|
||||
{"/get/test//abc", false, "/get/:param/*all", Params{Param{Key: "param", Value: "test"}, Param{Key: "all", Value: "//abc"}}},
|
||||
{"/something/secondthing/test", false, "/something/secondthing/test", nil},
|
||||
{"/something/abcdad/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "abcdad"}}},
|
||||
{"/something/secondthingaaaa/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "secondthingaaaa"}}},
|
||||
@ -402,12 +421,12 @@ func TestTreeWildcardConflict(t *testing.T) {
|
||||
{"/cmd/:tool/:badsub/details", true},
|
||||
{"/src/*filepath", false},
|
||||
{"/src/:file", true},
|
||||
{"/src/static.json", true},
|
||||
{"/src/static.json", false},
|
||||
{"/src/*filepathx", true},
|
||||
{"/src/", true},
|
||||
{"/src/foo/bar", true},
|
||||
{"/src/", false},
|
||||
{"/src/foo/bar", false},
|
||||
{"/src1/", false},
|
||||
{"/src1/*filepath", true},
|
||||
{"/src1/*filepath", false},
|
||||
{"/src2*filepath", true},
|
||||
{"/src2/*filepath", false},
|
||||
{"/search/:query", false},
|
||||
@ -436,7 +455,7 @@ func TestTreeChildConflict(t *testing.T) {
|
||||
{"/cmd/:tool/misc", false},
|
||||
{"/cmd/:tool/:othersub", true},
|
||||
{"/src/AUTHORS", false},
|
||||
{"/src/*filepath", true},
|
||||
{"/src/*filepath", false},
|
||||
{"/user_x", false},
|
||||
{"/user_:name", false},
|
||||
{"/id/:id", false},
|
||||
@ -474,7 +493,7 @@ func TestTreeDuplicatePath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
//printChildren(tree, "")
|
||||
// printChildren(tree, "")
|
||||
|
||||
checkRequests(t, tree, testRequests{
|
||||
{"/", false, "/", nil},
|
||||
@ -515,12 +534,53 @@ func TestTreeCatchAllConflict(t *testing.T) {
|
||||
testRoutes(t, routes)
|
||||
}
|
||||
|
||||
func TestTreeCatchAllConflictRoot(t *testing.T) {
|
||||
routes := []testRoute{
|
||||
{"/", false},
|
||||
{"/*filepath", true},
|
||||
func TestTreeCatchAllRoot(t *testing.T) {
|
||||
tree := &node{}
|
||||
routes := []string{
|
||||
"/index.html",
|
||||
"/*all",
|
||||
}
|
||||
testRoutes(t, routes)
|
||||
for _, route := range routes {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
}
|
||||
checkRequests(t, tree, testRequests{
|
||||
{"/", false, "/*all", Params{Param{"all", "/"}}},
|
||||
{"/index.html", false, "/index.html", nil},
|
||||
{"/users", false, "/*all", Params{Param{"all", "/users"}}},
|
||||
}, true)
|
||||
|
||||
// More tests
|
||||
tree = &node{}
|
||||
routes = []string{
|
||||
"/",
|
||||
"/index.html",
|
||||
"/*all",
|
||||
}
|
||||
for _, route := range routes {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
}
|
||||
checkRequests(t, tree, testRequests{
|
||||
{"/", false, "/", nil},
|
||||
{"/index.html", false, "/index.html", nil},
|
||||
{"/users", false, "/*all", Params{Param{"all", "/users"}}},
|
||||
}, true)
|
||||
}
|
||||
|
||||
func TestCatchAllPathNoLeadingSlash(t *testing.T) {
|
||||
tree := &node{}
|
||||
|
||||
routes := [...]string{
|
||||
"/abc/defg/:param",
|
||||
"/abc/def/*all",
|
||||
}
|
||||
for _, route := range routes {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
}
|
||||
|
||||
checkRequests(t, tree, testRequests{
|
||||
{"/abc/defX", true, "", nil},
|
||||
{"/abc/def/X", false, "/abc/def/*all", Params{Param{"all", "/X"}}},
|
||||
})
|
||||
}
|
||||
|
||||
func TestTreeCatchMaxParams(t *testing.T) {
|
||||
@ -568,11 +628,13 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
|
||||
"/b/",
|
||||
"/search/:query",
|
||||
"/cmd/:tool/",
|
||||
"/src/",
|
||||
"/src/*filepath",
|
||||
"/x",
|
||||
"/x/y",
|
||||
"/y/",
|
||||
"/y/z",
|
||||
"/z/*all",
|
||||
"/0/:id",
|
||||
"/0/:id/1",
|
||||
"/1/:id/",
|
||||
@ -614,6 +676,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
|
||||
"/src",
|
||||
"/x/",
|
||||
"/y",
|
||||
"/z",
|
||||
"/0/go/",
|
||||
"/1/go",
|
||||
"/a",
|
||||
@ -911,9 +974,9 @@ func TestTreeWildcardConflictEx(t *testing.T) {
|
||||
existPath string
|
||||
existSegPath string
|
||||
}{
|
||||
{"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`},
|
||||
{"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`},
|
||||
{"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`},
|
||||
{"/who/are/:foo", "/:foo", `/who/are/\*you`, `/\*you`},
|
||||
{"/who/are/:foo/", "/:foo/", `/who/are/\*you`, `/\*you`},
|
||||
{"/who/are/*foo", `/\*foo`, `/who/are/\*you`, `/\*you`},
|
||||
{"/con:nection", ":nection", `/con:tact`, `:tact`},
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user