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
@ -282,6 +282,11 @@ func main() {
|
|||||||
c.String(http.StatusOK, "The available groups are [...]")
|
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")
|
router.Run(":8080")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -1275,6 +1280,7 @@ func main() {
|
|||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
router.StaticFS("/", http.Dir("dist"))
|
||||||
router.Static("/assets", "./assets")
|
router.Static("/assets", "./assets")
|
||||||
router.StaticFS("/more_static", http.Dir("my_file_system"))
|
router.StaticFS("/more_static", http.Dir("my_file_system"))
|
||||||
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
|
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
|
||||||
|
@ -414,9 +414,14 @@ func testGetRequestHandler(t *testing.T, h http.Handler, url string) {
|
|||||||
|
|
||||||
func TestTreeRunDynamicRouting(t *testing.T) {
|
func TestTreeRunDynamicRouting(t *testing.T) {
|
||||||
router := New()
|
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("/", 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("/: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/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") })
|
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", 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("/: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/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("/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/: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") })
|
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+"/", "", "home")
|
||||||
testRequest(t, ts.URL+"/aa/aa", "", "/aa/*xx")
|
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/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")
|
||||||
testRequest(t, ts.URL+"/all/cc", "", "/:cc/cc")
|
testRequest(t, ts.URL+"/all/cc", "", "/:cc/cc")
|
||||||
testRequest(t, ts.URL+"/a/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/t/abc/", "", "/get/:param/abc/")
|
||||||
testRequest(t, ts.URL+"/get/aa/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/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/secondthing/test", "", "/something/secondthing/test")
|
||||||
testRequest(t, ts.URL+"/something/secondthingaaaa/thirdthing", "", "/something/:paramname/thirdthing")
|
testRequest(t, ts.URL+"/something/secondthingaaaa/thirdthing", "", "/something/:paramname/thirdthing")
|
||||||
testRequest(t, ts.URL+"/something/abcdad/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/123abf/testss", "", "/get/abc/123abf/:param")
|
||||||
testRequest(t, ts.URL+"/get/abc/123abfff/te", "", "/get/abc/123abfff/:param")
|
testRequest(t, ts.URL+"/get/abc/123abfff/te", "", "/get/abc/123abfff/:param")
|
||||||
// 404 not found
|
// 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/e", "404 Not Found")
|
||||||
testRequest(t, ts.URL+"/c/d/e1", "404 Not Found")
|
testRequest(t, ts.URL+"/c/d/e1", "404 Not Found")
|
||||||
testRequest(t, ts.URL+"/c/d/eee", "404 Not Found")
|
testRequest(t, ts.URL+"/c/d/eee", "404 Not Found")
|
||||||
|
195
tree.go
195
tree.go
@ -6,6 +6,7 @@ package gin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
@ -147,6 +148,19 @@ func (n *node) incrementChildPrio(pos int) int {
|
|||||||
return newPos
|
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.
|
// addRoute adds a node with the given handle to the path.
|
||||||
// Not concurrency-safe!
|
// Not concurrency-safe!
|
||||||
func (n *node) addRoute(path string, handlers HandlersChain) {
|
func (n *node) addRoute(path string, handlers HandlersChain) {
|
||||||
@ -168,6 +182,9 @@ walk:
|
|||||||
// This also implies that the common prefix contains no ':' or '*'
|
// This also implies that the common prefix contains no ':' or '*'
|
||||||
// since the existing key can't contain those chars.
|
// since the existing key can't contain those chars.
|
||||||
i := longestCommonPrefix(path, n.path)
|
i := longestCommonPrefix(path, n.path)
|
||||||
|
if i > 0 && i < len(path) && path[i-1:i+1] == "/*" {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
|
||||||
// Split edge
|
// Split edge
|
||||||
if i < len(n.path) {
|
if i < len(n.path) {
|
||||||
@ -195,15 +212,27 @@ walk:
|
|||||||
if i < len(path) {
|
if i < len(path) {
|
||||||
path = path[i:]
|
path = path[i:]
|
||||||
c := path[0]
|
c := path[0]
|
||||||
|
isCatchAll := strings.HasPrefix(path, "/*")
|
||||||
|
|
||||||
// '/' after param
|
if len(n.children) > 0 {
|
||||||
if n.nType == param && c == '/' && len(n.children) == 1 {
|
child := n.children[len(n.children)-1]
|
||||||
parentFullPathIndex += len(n.path)
|
if child.nType == param && c == ':' {
|
||||||
n = n.children[0]
|
if strings.HasPrefix(path, child.path) && (len(path) == len(child.path) || path[len(child.path)] == '/') {
|
||||||
n.priority++
|
child.priority++
|
||||||
|
n = child
|
||||||
continue walk
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c != ':' && !isCatchAll {
|
||||||
// Check if a child with the next path byte exists
|
// Check if a child with the next path byte exists
|
||||||
for i, max := 0, len(n.indices); i < max; i++ {
|
for i, max := 0, len(n.indices); i < max; i++ {
|
||||||
if c == n.indices[i] {
|
if c == n.indices[i] {
|
||||||
@ -213,9 +242,7 @@ walk:
|
|||||||
continue walk
|
continue walk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise insert it
|
// Otherwise insert it
|
||||||
if c != ':' && c != '*' && n.nType != catchAll {
|
|
||||||
// []byte for proper unicode char conversion, see #65
|
// []byte for proper unicode char conversion, see #65
|
||||||
n.indices += bytesconv.BytesToString([]byte{c})
|
n.indices += bytesconv.BytesToString([]byte{c})
|
||||||
child := &node{
|
child := &node{
|
||||||
@ -224,31 +251,6 @@ walk:
|
|||||||
n.addChild(child)
|
n.addChild(child)
|
||||||
n.incrementChildPrio(len(n.indices) - 1)
|
n.incrementChildPrio(len(n.indices) - 1)
|
||||||
n = child
|
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)
|
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 '/'
|
// will be another subpath starting with '/'
|
||||||
if len(wildcard) < len(path) {
|
if len(wildcard) < len(path) {
|
||||||
path = path[len(wildcard):]
|
path = path[len(wildcard):]
|
||||||
|
if !strings.HasPrefix(path, "/*") {
|
||||||
child := &node{
|
child := &node{
|
||||||
priority: 1,
|
priority: 1,
|
||||||
fullPath: fullPath,
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
n.addChild(child)
|
n.addChild(child)
|
||||||
|
n.indices = "/"
|
||||||
n = child
|
n = child
|
||||||
|
}
|
||||||
continue
|
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 + "'")
|
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 '/'
|
// currently fixed width 1 for '/'
|
||||||
i--
|
i--
|
||||||
if path[i] != '/' {
|
if path[i] != '/' {
|
||||||
panic("no / before catch-all in path '" + fullPath + "'")
|
panic("no / before catch-all in path '" + fullPath + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
n.path = path[:i]
|
|
||||||
|
|
||||||
// First node: catchAll node with empty path
|
|
||||||
child := &node{
|
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:],
|
path: path[i:],
|
||||||
nType: catchAll,
|
nType: catchAll,
|
||||||
handlers: handlers,
|
handlers: handlers,
|
||||||
priority: 1,
|
priority: 1,
|
||||||
fullPath: fullPath,
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
n.children = []*node{child}
|
n.addChild(child)
|
||||||
|
n.path += path[:i]
|
||||||
|
n.wildChild = true
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -419,19 +401,38 @@ type skippedNode struct {
|
|||||||
// given path.
|
// given path.
|
||||||
func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
|
func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
|
||||||
var globalParamsCount int16
|
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
|
walk: // Outer loop for walking the tree
|
||||||
for {
|
for {
|
||||||
prefix := n.path
|
prefix := n.path
|
||||||
if len(path) > len(prefix) {
|
// Search in children
|
||||||
if path[:len(prefix)] == prefix {
|
if len(path) > len(prefix) && path[:len(prefix)] == prefix || n.nType == param {
|
||||||
|
if n.nType == param {
|
||||||
|
prefix = ""
|
||||||
|
}
|
||||||
path = path[len(prefix):]
|
path = path[len(prefix):]
|
||||||
|
|
||||||
// Try all the non-wildcard children first by matching the indices
|
// Try all the non-wildcard children first by matching the indices
|
||||||
idxc := path[0]
|
idxc := path[0]
|
||||||
for i, c := range []byte(n.indices) {
|
for i, c := range []byte(n.indices) {
|
||||||
if c == idxc {
|
if c == idxc {
|
||||||
// strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
|
parentHasHandlers = len(n.handlers) > 0
|
||||||
if n.wildChild {
|
if n.wildChild {
|
||||||
index := len(*skippedNodes)
|
index := len(*skippedNodes)
|
||||||
*skippedNodes = (*skippedNodes)[:index+1]
|
*skippedNodes = (*skippedNodes)[:index+1]
|
||||||
@ -458,21 +459,9 @@ walk: // Outer loop for walking the tree
|
|||||||
if !n.wildChild {
|
if !n.wildChild {
|
||||||
// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
|
// 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
|
// the current node needs to roll back to last valid skippedNode
|
||||||
if path != "/" {
|
if path != "/" && rollbackSkipped() {
|
||||||
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
|
continue walk
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing found.
|
// Nothing found.
|
||||||
// We can recommend to redirect to the same URL without a
|
// We can recommend to redirect to the same URL without a
|
||||||
@ -520,7 +509,10 @@ walk: // Outer loop for walking the tree
|
|||||||
if end < len(path) {
|
if end < len(path) {
|
||||||
if len(n.children) > 0 {
|
if len(n.children) > 0 {
|
||||||
path = path[end:]
|
path = path[end:]
|
||||||
|
parentHasHandlers = len(n.handlers) > 0
|
||||||
|
if n.children[len(n.children)-1].nType != catchAll {
|
||||||
n = n.children[0]
|
n = n.children[0]
|
||||||
|
}
|
||||||
continue walk
|
continue walk
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -537,11 +529,17 @@ walk: // Outer loop for walking the tree
|
|||||||
// No handle found. Check if a handle for this path + a
|
// No handle found. Check if a handle for this path + a
|
||||||
// trailing slash exists for TSR recommendation
|
// trailing slash exists for TSR recommendation
|
||||||
n = n.children[0]
|
n = n.children[0]
|
||||||
value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/")
|
value.tsr = (n.path == "/" && n.handlers != nil) || n.nType == catchAll
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
case catchAll:
|
case catchAll:
|
||||||
|
if path[0] != '/' {
|
||||||
|
if rollbackSkipped() {
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
// Save param value
|
// Save param value
|
||||||
if params != nil {
|
if params != nil {
|
||||||
if value.params == nil {
|
if value.params == nil {
|
||||||
@ -561,7 +559,6 @@ walk: // Outer loop for walking the tree
|
|||||||
Value: val,
|
Value: val,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
value.handlers = n.handlers
|
value.handlers = n.handlers
|
||||||
value.fullPath = n.fullPath
|
value.fullPath = n.fullPath
|
||||||
return
|
return
|
||||||
@ -570,27 +567,13 @@ walk: // Outer loop for walking the tree
|
|||||||
panic("invalid node type")
|
panic("invalid node type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if path == prefix {
|
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
|
// 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
|
// the current node needs to roll back to last valid skippedNode
|
||||||
if n.handlers == nil && path != "/" {
|
if n.handlers == nil && rollbackSkipped() {
|
||||||
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
|
continue walk
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// n = latestNode.children[len(latestNode.children)-1]
|
|
||||||
}
|
|
||||||
// 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 value.handlers = n.handlers; value.handlers != nil {
|
if value.handlers = n.handlers; value.handlers != nil {
|
||||||
@ -616,37 +599,27 @@ walk: // Outer loop for walking the tree
|
|||||||
for i, c := range []byte(n.indices) {
|
for i, c := range []byte(n.indices) {
|
||||||
if c == '/' {
|
if c == '/' {
|
||||||
n = n.children[i]
|
n = n.children[i]
|
||||||
value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
|
value.tsr = len(n.path) == 1 && n.handlers != nil
|
||||||
(n.nType == catchAll && n.children[0].handlers != nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value.tsr = n.children[len(n.children)-1].nType == catchAll
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing found. We can recommend to redirect to the same URL with an
|
// Nothing found. Look for trailing slash recommendation
|
||||||
// extra trailing slash if a leaf exists for that path
|
value.tsr =
|
||||||
value.tsr = path == "/" ||
|
// URL of parent node has one less trailing slash than path
|
||||||
(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
|
path == "/" && parentHasHandlers ||
|
||||||
path == prefix[:len(prefix)-1] && n.handlers != nil)
|
// 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
|
// roll back to last valid skippedNode
|
||||||
if !value.tsr && path != "/" {
|
if !value.tsr && rollbackSkipped() {
|
||||||
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
|
continue walk
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
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/:project",
|
||||||
"/info/:user/project/golang",
|
"/info/:user/project/golang",
|
||||||
"/aa/*xx",
|
"/aa/*xx",
|
||||||
|
"/ab/aa",
|
||||||
"/ab/*xx",
|
"/ab/*xx",
|
||||||
|
"/ab/zz",
|
||||||
|
"/abc/def",
|
||||||
|
"/abc/d/:ee/*all",
|
||||||
|
"/abc/de/*all",
|
||||||
"/:cc",
|
"/:cc",
|
||||||
"/c1/:dd/e",
|
"/c1/:dd/e",
|
||||||
"/c1/:dd/e1",
|
"/c1/:dd/e1",
|
||||||
@ -170,6 +175,7 @@ func TestTreeWildcard(t *testing.T) {
|
|||||||
"/:cc/:dd/:ee/:ff/gg",
|
"/:cc/:dd/:ee/:ff/gg",
|
||||||
"/:cc/:dd/:ee/:ff/:gg/hh",
|
"/:cc/:dd/:ee/:ff/:gg/hh",
|
||||||
"/get/test/abc/",
|
"/get/test/abc/",
|
||||||
|
"/get/:param/*all",
|
||||||
"/get/:param/abc/",
|
"/get/:param/abc/",
|
||||||
"/something/:paramname/thirdthing",
|
"/something/:paramname/thirdthing",
|
||||||
"/something/secondthing/test",
|
"/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/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"}}},
|
{"/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"}}},
|
{"/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/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"}}},
|
{"/a", false, "/:cc", Params{Param{Key: "cc", Value: "a"}}},
|
||||||
// * Error with argument being intercepted
|
// * Error with argument being intercepted
|
||||||
// new PR handle (/all /all/cc /a/cc)
|
// 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/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/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/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/secondthing/test", false, "/something/secondthing/test", nil},
|
||||||
{"/something/abcdad/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "abcdad"}}},
|
{"/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"}}},
|
{"/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},
|
{"/cmd/:tool/:badsub/details", true},
|
||||||
{"/src/*filepath", false},
|
{"/src/*filepath", false},
|
||||||
{"/src/:file", true},
|
{"/src/:file", true},
|
||||||
{"/src/static.json", true},
|
{"/src/static.json", false},
|
||||||
{"/src/*filepathx", true},
|
{"/src/*filepathx", true},
|
||||||
{"/src/", true},
|
{"/src/", false},
|
||||||
{"/src/foo/bar", true},
|
{"/src/foo/bar", false},
|
||||||
{"/src1/", false},
|
{"/src1/", false},
|
||||||
{"/src1/*filepath", true},
|
{"/src1/*filepath", false},
|
||||||
{"/src2*filepath", true},
|
{"/src2*filepath", true},
|
||||||
{"/src2/*filepath", false},
|
{"/src2/*filepath", false},
|
||||||
{"/search/:query", false},
|
{"/search/:query", false},
|
||||||
@ -436,7 +455,7 @@ func TestTreeChildConflict(t *testing.T) {
|
|||||||
{"/cmd/:tool/misc", false},
|
{"/cmd/:tool/misc", false},
|
||||||
{"/cmd/:tool/:othersub", true},
|
{"/cmd/:tool/:othersub", true},
|
||||||
{"/src/AUTHORS", false},
|
{"/src/AUTHORS", false},
|
||||||
{"/src/*filepath", true},
|
{"/src/*filepath", false},
|
||||||
{"/user_x", false},
|
{"/user_x", false},
|
||||||
{"/user_:name", false},
|
{"/user_:name", false},
|
||||||
{"/id/:id", false},
|
{"/id/:id", false},
|
||||||
@ -474,7 +493,7 @@ func TestTreeDuplicatePath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//printChildren(tree, "")
|
// printChildren(tree, "")
|
||||||
|
|
||||||
checkRequests(t, tree, testRequests{
|
checkRequests(t, tree, testRequests{
|
||||||
{"/", false, "/", nil},
|
{"/", false, "/", nil},
|
||||||
@ -515,12 +534,53 @@ func TestTreeCatchAllConflict(t *testing.T) {
|
|||||||
testRoutes(t, routes)
|
testRoutes(t, routes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTreeCatchAllConflictRoot(t *testing.T) {
|
func TestTreeCatchAllRoot(t *testing.T) {
|
||||||
routes := []testRoute{
|
tree := &node{}
|
||||||
{"/", false},
|
routes := []string{
|
||||||
{"/*filepath", true},
|
"/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) {
|
func TestTreeCatchMaxParams(t *testing.T) {
|
||||||
@ -568,11 +628,13 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
|
|||||||
"/b/",
|
"/b/",
|
||||||
"/search/:query",
|
"/search/:query",
|
||||||
"/cmd/:tool/",
|
"/cmd/:tool/",
|
||||||
|
"/src/",
|
||||||
"/src/*filepath",
|
"/src/*filepath",
|
||||||
"/x",
|
"/x",
|
||||||
"/x/y",
|
"/x/y",
|
||||||
"/y/",
|
"/y/",
|
||||||
"/y/z",
|
"/y/z",
|
||||||
|
"/z/*all",
|
||||||
"/0/:id",
|
"/0/:id",
|
||||||
"/0/:id/1",
|
"/0/:id/1",
|
||||||
"/1/:id/",
|
"/1/:id/",
|
||||||
@ -614,6 +676,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
|
|||||||
"/src",
|
"/src",
|
||||||
"/x/",
|
"/x/",
|
||||||
"/y",
|
"/y",
|
||||||
|
"/z",
|
||||||
"/0/go/",
|
"/0/go/",
|
||||||
"/1/go",
|
"/1/go",
|
||||||
"/a",
|
"/a",
|
||||||
@ -911,9 +974,9 @@ func TestTreeWildcardConflictEx(t *testing.T) {
|
|||||||
existPath string
|
existPath string
|
||||||
existSegPath string
|
existSegPath string
|
||||||
}{
|
}{
|
||||||
{"/who/are/foo", "/foo", `/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`},
|
||||||
{"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`},
|
{"/who/are/*foo", `/\*foo`, `/who/are/\*you`, `/\*you`},
|
||||||
{"/con:nection", ":nection", `/con:tact`, `:tact`},
|
{"/con:nection", ":nection", `/con:tact`, `:tact`},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user