mirror of https://github.com/gin-gonic/gin.git
Hold matched route full path in the Context (#1826)
* Return nodeValue from getValue method * Hold route full path in the Context * Add small example
This commit is contained in:
parent
78a8b5c9d5
commit
35e33d3638
|
@ -252,6 +252,11 @@ func main() {
|
||||||
c.String(http.StatusOK, message)
|
c.String(http.StatusOK, message)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// For each matched request Context will hold the route definition
|
||||||
|
router.POST("/user/:name/*action", func(c *gin.Context) {
|
||||||
|
c.FullPath() == "/user/:name/*action" // true
|
||||||
|
})
|
||||||
|
|
||||||
router.Run(":8080")
|
router.Run(":8080")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
11
context.go
11
context.go
|
@ -48,6 +48,7 @@ type Context struct {
|
||||||
Params Params
|
Params Params
|
||||||
handlers HandlersChain
|
handlers HandlersChain
|
||||||
index int8
|
index int8
|
||||||
|
fullPath string
|
||||||
|
|
||||||
engine *Engine
|
engine *Engine
|
||||||
|
|
||||||
|
@ -70,6 +71,7 @@ func (c *Context) reset() {
|
||||||
c.Params = c.Params[0:0]
|
c.Params = c.Params[0:0]
|
||||||
c.handlers = nil
|
c.handlers = nil
|
||||||
c.index = -1
|
c.index = -1
|
||||||
|
c.fullPath = ""
|
||||||
c.Keys = nil
|
c.Keys = nil
|
||||||
c.Errors = c.Errors[0:0]
|
c.Errors = c.Errors[0:0]
|
||||||
c.Accepted = nil
|
c.Accepted = nil
|
||||||
|
@ -111,6 +113,15 @@ func (c *Context) Handler() HandlerFunc {
|
||||||
return c.handlers.Last()
|
return c.handlers.Last()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FullPath returns a matched route full path. For not found routes
|
||||||
|
// returns an empty string.
|
||||||
|
// router.GET("/user/:id", func(c *gin.Context) {
|
||||||
|
// c.FullPath() == "/user/:id" // true
|
||||||
|
// })
|
||||||
|
func (c *Context) FullPath() string {
|
||||||
|
return c.fullPath
|
||||||
|
}
|
||||||
|
|
||||||
/************************************/
|
/************************************/
|
||||||
/*********** FLOW CONTROL ***********/
|
/*********** FLOW CONTROL ***********/
|
||||||
/************************************/
|
/************************************/
|
||||||
|
|
14
gin.go
14
gin.go
|
@ -252,6 +252,7 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
||||||
root := engine.trees.get(method)
|
root := engine.trees.get(method)
|
||||||
if root == nil {
|
if root == nil {
|
||||||
root = new(node)
|
root = new(node)
|
||||||
|
root.fullPath = "/"
|
||||||
engine.trees = append(engine.trees, methodTree{method: method, root: root})
|
engine.trees = append(engine.trees, methodTree{method: method, root: root})
|
||||||
}
|
}
|
||||||
root.addRoute(path, handlers)
|
root.addRoute(path, handlers)
|
||||||
|
@ -382,16 +383,17 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||||
}
|
}
|
||||||
root := t[i].root
|
root := t[i].root
|
||||||
// Find route in tree
|
// Find route in tree
|
||||||
handlers, params, tsr := root.getValue(rPath, c.Params, unescape)
|
value := root.getValue(rPath, c.Params, unescape)
|
||||||
if handlers != nil {
|
if value.handlers != nil {
|
||||||
c.handlers = handlers
|
c.handlers = value.handlers
|
||||||
c.Params = params
|
c.Params = value.params
|
||||||
|
c.fullPath = value.fullPath
|
||||||
c.Next()
|
c.Next()
|
||||||
c.writermem.WriteHeaderNow()
|
c.writermem.WriteHeaderNow()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if httpMethod != "CONNECT" && rPath != "/" {
|
if httpMethod != "CONNECT" && rPath != "/" {
|
||||||
if tsr && engine.RedirectTrailingSlash {
|
if value.tsr && engine.RedirectTrailingSlash {
|
||||||
redirectTrailingSlash(c)
|
redirectTrailingSlash(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -407,7 +409,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||||
if tree.method == httpMethod {
|
if tree.method == httpMethod {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil {
|
if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
|
||||||
c.handlers = engine.allNoMethod
|
c.handlers = engine.allNoMethod
|
||||||
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||||
return
|
return
|
||||||
|
|
|
@ -554,3 +554,38 @@ func TestRouteServeErrorWithWriteHeader(t *testing.T) {
|
||||||
assert.Equal(t, 421, w.Code)
|
assert.Equal(t, 421, w.Code)
|
||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteContextHoldsFullPath(t *testing.T) {
|
||||||
|
router := New()
|
||||||
|
|
||||||
|
// Test routes
|
||||||
|
routes := []string{
|
||||||
|
"/",
|
||||||
|
"/simple",
|
||||||
|
"/project/:name",
|
||||||
|
"/project/:name/build/*params",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
actualRoute := route
|
||||||
|
router.GET(route, func(c *Context) {
|
||||||
|
// For each defined route context should contain its full path
|
||||||
|
assert.Equal(t, actualRoute, c.FullPath())
|
||||||
|
c.AbortWithStatus(http.StatusOK)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
w := performRequest(router, "GET", route)
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test not found
|
||||||
|
router.Use(func(c *Context) {
|
||||||
|
// For not found routes full path is empty
|
||||||
|
assert.Equal(t, "", c.FullPath())
|
||||||
|
})
|
||||||
|
|
||||||
|
w := performRequest(router, "GET", "/not-found")
|
||||||
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
}
|
||||||
|
|
72
tree.go
72
tree.go
|
@ -94,6 +94,7 @@ type node struct {
|
||||||
nType nodeType
|
nType nodeType
|
||||||
maxParams uint8
|
maxParams uint8
|
||||||
wildChild bool
|
wildChild bool
|
||||||
|
fullPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// increments priority of the given child and reorders if necessary.
|
// increments priority of the given child and reorders if necessary.
|
||||||
|
@ -154,6 +155,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
|
||||||
children: n.children,
|
children: n.children,
|
||||||
handlers: n.handlers,
|
handlers: n.handlers,
|
||||||
priority: n.priority - 1,
|
priority: n.priority - 1,
|
||||||
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update maxParams (max of all children)
|
// Update maxParams (max of all children)
|
||||||
|
@ -229,6 +231,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
|
||||||
n.indices += string([]byte{c})
|
n.indices += string([]byte{c})
|
||||||
child := &node{
|
child := &node{
|
||||||
maxParams: numParams,
|
maxParams: numParams,
|
||||||
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
n.children = append(n.children, child)
|
n.children = append(n.children, child)
|
||||||
n.incrementChildPrio(len(n.indices) - 1)
|
n.incrementChildPrio(len(n.indices) - 1)
|
||||||
|
@ -296,6 +299,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
|
||||||
child := &node{
|
child := &node{
|
||||||
nType: param,
|
nType: param,
|
||||||
maxParams: numParams,
|
maxParams: numParams,
|
||||||
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
n.children = []*node{child}
|
n.children = []*node{child}
|
||||||
n.wildChild = true
|
n.wildChild = true
|
||||||
|
@ -312,6 +316,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
|
||||||
child := &node{
|
child := &node{
|
||||||
maxParams: numParams,
|
maxParams: numParams,
|
||||||
priority: 1,
|
priority: 1,
|
||||||
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
n.children = []*node{child}
|
n.children = []*node{child}
|
||||||
n = child
|
n = child
|
||||||
|
@ -339,6 +344,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
|
||||||
wildChild: true,
|
wildChild: true,
|
||||||
nType: catchAll,
|
nType: catchAll,
|
||||||
maxParams: 1,
|
maxParams: 1,
|
||||||
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
n.children = []*node{child}
|
n.children = []*node{child}
|
||||||
n.indices = string(path[i])
|
n.indices = string(path[i])
|
||||||
|
@ -352,6 +358,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
|
||||||
maxParams: 1,
|
maxParams: 1,
|
||||||
handlers: handlers,
|
handlers: handlers,
|
||||||
priority: 1,
|
priority: 1,
|
||||||
|
fullPath: fullPath,
|
||||||
}
|
}
|
||||||
n.children = []*node{child}
|
n.children = []*node{child}
|
||||||
|
|
||||||
|
@ -364,13 +371,21 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
|
||||||
n.handlers = handlers
|
n.handlers = handlers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nodeValue holds return values of (*Node).getValue method
|
||||||
|
type nodeValue struct {
|
||||||
|
handlers HandlersChain
|
||||||
|
params Params
|
||||||
|
tsr bool
|
||||||
|
fullPath string
|
||||||
|
}
|
||||||
|
|
||||||
// getValue returns the handle registered with the given path (key). The values of
|
// getValue returns the handle registered with the given path (key). The values of
|
||||||
// wildcards are saved to a map.
|
// wildcards are saved to a map.
|
||||||
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
|
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
|
||||||
// made if a handle exists with an extra (without the) trailing slash for the
|
// made if a handle exists with an extra (without the) trailing slash for the
|
||||||
// given path.
|
// given path.
|
||||||
func (n *node) getValue(path string, po Params, unescape bool) (handlers HandlersChain, p Params, tsr bool) {
|
func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue) {
|
||||||
p = po
|
value.params = po
|
||||||
walk: // Outer loop for walking the tree
|
walk: // Outer loop for walking the tree
|
||||||
for {
|
for {
|
||||||
if len(path) > len(n.path) {
|
if len(path) > len(n.path) {
|
||||||
|
@ -391,7 +406,7 @@ walk: // Outer loop for walking the tree
|
||||||
// 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
|
||||||
// trailing slash if a leaf exists for that path.
|
// trailing slash if a leaf exists for that path.
|
||||||
tsr = path == "/" && n.handlers != nil
|
value.tsr = path == "/" && n.handlers != nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,20 +421,20 @@ walk: // Outer loop for walking the tree
|
||||||
}
|
}
|
||||||
|
|
||||||
// save param value
|
// save param value
|
||||||
if cap(p) < int(n.maxParams) {
|
if cap(value.params) < int(n.maxParams) {
|
||||||
p = make(Params, 0, n.maxParams)
|
value.params = make(Params, 0, n.maxParams)
|
||||||
}
|
}
|
||||||
i := len(p)
|
i := len(value.params)
|
||||||
p = p[:i+1] // expand slice within preallocated capacity
|
value.params = value.params[:i+1] // expand slice within preallocated capacity
|
||||||
p[i].Key = n.path[1:]
|
value.params[i].Key = n.path[1:]
|
||||||
val := path[:end]
|
val := path[:end]
|
||||||
if unescape {
|
if unescape {
|
||||||
var err error
|
var err error
|
||||||
if p[i].Value, err = url.QueryUnescape(val); err != nil {
|
if value.params[i].Value, err = url.QueryUnescape(val); err != nil {
|
||||||
p[i].Value = val // fallback, in case of error
|
value.params[i].Value = val // fallback, in case of error
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p[i].Value = val
|
value.params[i].Value = val
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need to go deeper!
|
// we need to go deeper!
|
||||||
|
@ -431,40 +446,42 @@ walk: // Outer loop for walking the tree
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... but we can't
|
// ... but we can't
|
||||||
tsr = len(path) == end+1
|
value.tsr = len(path) == end+1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if handlers = n.handlers; handlers != nil {
|
if value.handlers = n.handlers; value.handlers != nil {
|
||||||
|
value.fullPath = n.fullPath
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(n.children) == 1 {
|
if len(n.children) == 1 {
|
||||||
// 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]
|
||||||
tsr = n.path == "/" && n.handlers != nil
|
value.tsr = n.path == "/" && n.handlers != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
case catchAll:
|
case catchAll:
|
||||||
// save param value
|
// save param value
|
||||||
if cap(p) < int(n.maxParams) {
|
if cap(value.params) < int(n.maxParams) {
|
||||||
p = make(Params, 0, n.maxParams)
|
value.params = make(Params, 0, n.maxParams)
|
||||||
}
|
}
|
||||||
i := len(p)
|
i := len(value.params)
|
||||||
p = p[:i+1] // expand slice within preallocated capacity
|
value.params = value.params[:i+1] // expand slice within preallocated capacity
|
||||||
p[i].Key = n.path[2:]
|
value.params[i].Key = n.path[2:]
|
||||||
if unescape {
|
if unescape {
|
||||||
var err error
|
var err error
|
||||||
if p[i].Value, err = url.QueryUnescape(path); err != nil {
|
if value.params[i].Value, err = url.QueryUnescape(path); err != nil {
|
||||||
p[i].Value = path // fallback, in case of error
|
value.params[i].Value = path // fallback, in case of error
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p[i].Value = path
|
value.params[i].Value = path
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers = n.handlers
|
value.handlers = n.handlers
|
||||||
|
value.fullPath = n.fullPath
|
||||||
return
|
return
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -474,12 +491,13 @@ walk: // Outer loop for walking the tree
|
||||||
} else if path == n.path {
|
} else if path == n.path {
|
||||||
// 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 handlers = n.handlers; handlers != nil {
|
if value.handlers = n.handlers; value.handlers != nil {
|
||||||
|
value.fullPath = n.fullPath
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == "/" && n.wildChild && n.nType != root {
|
if path == "/" && n.wildChild && n.nType != root {
|
||||||
tsr = true
|
value.tsr = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,7 +506,7 @@ walk: // Outer loop for walking the tree
|
||||||
for i := 0; i < len(n.indices); i++ {
|
for i := 0; i < len(n.indices); i++ {
|
||||||
if n.indices[i] == '/' {
|
if n.indices[i] == '/' {
|
||||||
n = n.children[i]
|
n = n.children[i]
|
||||||
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)
|
(n.nType == catchAll && n.children[0].handlers != nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -499,7 +517,7 @@ walk: // Outer loop for walking the tree
|
||||||
|
|
||||||
// Nothing found. We can recommend to redirect to the same URL with an
|
// Nothing found. We can recommend to redirect to the same URL with an
|
||||||
// extra trailing slash if a leaf exists for that path
|
// extra trailing slash if a leaf exists for that path
|
||||||
tsr = (path == "/") ||
|
value.tsr = (path == "/") ||
|
||||||
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
|
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
|
||||||
path == n.path[:len(n.path)-1] && n.handlers != nil)
|
path == n.path[:len(n.path)-1] && n.handlers != nil)
|
||||||
return
|
return
|
||||||
|
|
26
tree_test.go
26
tree_test.go
|
@ -35,22 +35,22 @@ func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ..
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, request := range requests {
|
for _, request := range requests {
|
||||||
handler, ps, _ := tree.getValue(request.path, nil, unescape)
|
value := tree.getValue(request.path, nil, unescape)
|
||||||
|
|
||||||
if handler == nil {
|
if value.handlers == nil {
|
||||||
if !request.nilHandler {
|
if !request.nilHandler {
|
||||||
t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path)
|
t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path)
|
||||||
}
|
}
|
||||||
} else if request.nilHandler {
|
} else if request.nilHandler {
|
||||||
t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path)
|
t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path)
|
||||||
} else {
|
} else {
|
||||||
handler[0](nil)
|
value.handlers[0](nil)
|
||||||
if fakeHandlerValue != request.route {
|
if fakeHandlerValue != request.route {
|
||||||
t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route)
|
t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(ps, request.ps) {
|
if !reflect.DeepEqual(value.params, request.ps) {
|
||||||
t.Errorf("Params mismatch for route '%s'", request.path)
|
t.Errorf("Params mismatch for route '%s'", request.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -454,10 +454,10 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
|
||||||
"/doc/",
|
"/doc/",
|
||||||
}
|
}
|
||||||
for _, route := range tsrRoutes {
|
for _, route := range tsrRoutes {
|
||||||
handler, _, tsr := tree.getValue(route, nil, false)
|
value := tree.getValue(route, nil, false)
|
||||||
if handler != nil {
|
if value.handlers != nil {
|
||||||
t.Fatalf("non-nil handler for TSR route '%s", route)
|
t.Fatalf("non-nil handler for TSR route '%s", route)
|
||||||
} else if !tsr {
|
} else if !value.tsr {
|
||||||
t.Errorf("expected TSR recommendation for route '%s'", route)
|
t.Errorf("expected TSR recommendation for route '%s'", route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,10 +471,10 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
|
||||||
"/api/world/abc",
|
"/api/world/abc",
|
||||||
}
|
}
|
||||||
for _, route := range noTsrRoutes {
|
for _, route := range noTsrRoutes {
|
||||||
handler, _, tsr := tree.getValue(route, nil, false)
|
value := tree.getValue(route, nil, false)
|
||||||
if handler != nil {
|
if value.handlers != nil {
|
||||||
t.Fatalf("non-nil handler for No-TSR route '%s", route)
|
t.Fatalf("non-nil handler for No-TSR route '%s", route)
|
||||||
} else if tsr {
|
} else if value.tsr {
|
||||||
t.Errorf("expected no TSR recommendation for route '%s'", route)
|
t.Errorf("expected no TSR recommendation for route '%s'", route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -490,10 +490,10 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
|
||||||
t.Fatalf("panic inserting test route: %v", recv)
|
t.Fatalf("panic inserting test route: %v", recv)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler, _, tsr := tree.getValue("/", nil, false)
|
value := tree.getValue("/", nil, false)
|
||||||
if handler != nil {
|
if value.handlers != nil {
|
||||||
t.Fatalf("non-nil handler")
|
t.Fatalf("non-nil handler")
|
||||||
} else if tsr {
|
} else if value.tsr {
|
||||||
t.Errorf("expected no TSR recommendation")
|
t.Errorf("expected no TSR recommendation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue