mirror of
https://github.com/kataras/iris.git
synced 2026-01-09 13:05:56 +00:00
Update to 7.0.5 | Dynamic and static paths are not in conflict anymore.
Read more at: https://github.com/kataras/iris/blob/master/HISTORY.md Former-commit-id: b636d25c141ebdd5ad095ae9271433876a96e7ff
This commit is contained in:
@@ -5,15 +5,15 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/nettools"
|
||||
"github.com/kataras/iris/core/router/httprouter"
|
||||
"github.com/kataras/iris/core/router/node"
|
||||
)
|
||||
|
||||
// RequestHandler the middle man between acquiring a context and releasing it.
|
||||
@@ -31,13 +31,12 @@ type tree struct {
|
||||
// subdomain is empty for default-hostname routes,
|
||||
// ex: mysubdomain.
|
||||
Subdomain string
|
||||
Entry *httprouter.Node
|
||||
Nodes *node.Nodes
|
||||
}
|
||||
|
||||
type routerHandler struct {
|
||||
trees []*tree
|
||||
vhost atomic.Value // is a string setted at the first it founds a subdomain, we need that here in order to reduce the resolveVHost calls
|
||||
hosts bool // true if at least one route contains a Subdomain.
|
||||
hosts bool // true if at least one route contains a Subdomain.
|
||||
}
|
||||
|
||||
var _ RequestHandler = &routerHandler{}
|
||||
@@ -54,20 +53,20 @@ func (h *routerHandler) getTree(method, subdomain string) *tree {
|
||||
}
|
||||
|
||||
func (h *routerHandler) addRoute(method, subdomain, path string, handlers context.Handlers) error {
|
||||
// get or create a tree and add the route
|
||||
if len(path) == 0 || path[0] != '/' {
|
||||
return fmt.Errorf("router: path %q must begin with %q", path, "/")
|
||||
}
|
||||
|
||||
t := h.getTree(method, subdomain)
|
||||
|
||||
if t == nil {
|
||||
//first time we register a route to this method with this domain
|
||||
t = &tree{Method: method, Subdomain: subdomain, Entry: new(httprouter.Node)}
|
||||
n := make(node.Nodes, 0)
|
||||
// first time we register a route to this method with this subdomain
|
||||
t = &tree{Method: method, Subdomain: subdomain, Nodes: &n}
|
||||
h.trees = append(h.trees, t)
|
||||
}
|
||||
|
||||
if err := t.Entry.AddRoute(path, handlers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return t.Nodes.Add(path, handlers)
|
||||
}
|
||||
|
||||
// NewDefaultHandler returns the handler which is responsible
|
||||
@@ -98,11 +97,41 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
method := ctx.Method()
|
||||
path := ctx.Path()
|
||||
|
||||
if !ctx.Application().ConfigurationReadOnly().GetDisablePathCorrection() {
|
||||
|
||||
if len(path) > 1 && path[len(path)-1] == '/' {
|
||||
// Remove trailing slash and client-permant rule for redirection,
|
||||
// if confgiuration allows that and path has an extra slash.
|
||||
|
||||
// update the new path and redirect.
|
||||
r := ctx.Request()
|
||||
path = path[:len(path)-1]
|
||||
r.URL.Path = path
|
||||
url := r.URL.String()
|
||||
|
||||
ctx.Redirect(url, http.StatusMovedPermanently)
|
||||
|
||||
// RFC2616 recommends that a short note "SHOULD" be included in the
|
||||
// response because older user agents may not understand 301/307.
|
||||
// Shouldn't send the response for POST or HEAD; that leaves GET.
|
||||
if method == http.MethodGet {
|
||||
note := "<a href=\"" +
|
||||
html.EscapeString(url) +
|
||||
"\">Moved Permanently</a>.\n"
|
||||
|
||||
ctx.ResponseWriter().WriteString(note)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for i := range h.trees {
|
||||
t := h.trees[i]
|
||||
@@ -110,39 +139,6 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Changed my mind for subdomains, there are unnecessary steps here
|
||||
// most servers don't need these and on other servers may force the server to send a 404 not found
|
||||
// on a valid subdomain, by commenting my previous implementation we allow any request host to be discovarable for subdomains.
|
||||
// if h.hosts && t.Subdomain != "" {
|
||||
|
||||
// if h.vhost.Load() == nil {
|
||||
// h.vhost.Store(nettools.ResolveVHost(ctx.Application().ConfigurationReadOnly().GetAddr()))
|
||||
// }
|
||||
|
||||
// host := h.vhost.Load().(string)
|
||||
// requestHost := ctx.Host()
|
||||
|
||||
// if requestHost != host {
|
||||
// // we have a subdomain
|
||||
// if strings.Contains(t.Subdomain, DynamicSubdomainIndicator) {
|
||||
// } else {
|
||||
// // if subdomain+host is not the request host
|
||||
// // and
|
||||
// // if request host didn't matched the server's host
|
||||
// // check if reached the server
|
||||
// // with a local address, this case is the developer him/herself,
|
||||
// // if both of them failed then continue and ignore this tree.
|
||||
// if t.Subdomain+host != requestHost && !nettools.IsLoopbackHost(requestHost) {
|
||||
// // go to the next tree, we have a subdomain but it is not the correct
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// //("it's subdomain but the request is not the same as the vhost)
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
// new, simpler and without the need of known the real host:
|
||||
if h.hosts && t.Subdomain != "" {
|
||||
requestHost := ctx.Host()
|
||||
if nettools.IsLoopbackSubdomain(requestHost) {
|
||||
@@ -175,62 +171,13 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
handlers, mustRedirect := t.Entry.ResolveRoute(ctx)
|
||||
handlers := t.Nodes.Find(path, ctx.Params())
|
||||
if len(handlers) > 0 {
|
||||
ctx.SetHandlers(handlers)
|
||||
ctx.Handlers()[0](ctx)
|
||||
// to remove the .Next(maybe not a good idea), reduces the performance a bit:
|
||||
// ctx.Handlers()[0](ctx) // execute the first, as soon as possible
|
||||
// // execute the chain of handlers, carefully
|
||||
// current := ctx.HandlerIndex(-1)
|
||||
// for {
|
||||
// if ctx.IsStopped() || current >= n {
|
||||
// break
|
||||
// }
|
||||
|
||||
// ctx.HandlerIndex(current)
|
||||
// ctx.Handlers()[current](ctx)
|
||||
// current++
|
||||
// if i := ctx.HandlerIndex(-1); i > current { // navigate to previous handler is not allowed
|
||||
// current = i
|
||||
// }
|
||||
// }
|
||||
|
||||
ctx.Do(handlers)
|
||||
// found
|
||||
return
|
||||
} else if mustRedirect && !ctx.Application().ConfigurationReadOnly().GetDisablePathCorrection() { // && ctx.Method() == MethodConnect {
|
||||
urlToRedirect := ctx.Path()
|
||||
pathLen := len(urlToRedirect)
|
||||
|
||||
if pathLen > 1 {
|
||||
if urlToRedirect[pathLen-1] == '/' {
|
||||
urlToRedirect = urlToRedirect[:pathLen-1] // remove the last /
|
||||
} else {
|
||||
// it has path prefix, it doesn't ends with / and it hasn't be found, then just append the slash
|
||||
urlToRedirect = urlToRedirect + "/"
|
||||
}
|
||||
|
||||
statusForRedirect := http.StatusMovedPermanently // StatusMovedPermanently, this document is obselte, clients caches this.
|
||||
if t.Method == http.MethodPost ||
|
||||
t.Method == http.MethodPut ||
|
||||
t.Method == http.MethodDelete {
|
||||
statusForRedirect = http.StatusTemporaryRedirect // To maintain POST data
|
||||
}
|
||||
|
||||
ctx.Redirect(urlToRedirect, statusForRedirect)
|
||||
// RFC2616 recommends that a short note "SHOULD" be included in the
|
||||
// response because older user agents may not understand 301/307.
|
||||
// Shouldn't send the response for POST or HEAD; that leaves GET.
|
||||
if t.Method == http.MethodGet {
|
||||
note := "<a href=\"" +
|
||||
html.EscapeString(urlToRedirect) +
|
||||
"\">Moved Permanently</a>.\n"
|
||||
|
||||
ctx.ResponseWriter().WriteString(note)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// not found
|
||||
// not found or method not allowed.
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
Copyright (c) 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Gerasimos Maropoulos nor the name of his
|
||||
username, kataras, may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Third-Parties:
|
||||
|
||||
Copyright (c) 2013 Julien Schmidt. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* The names of the contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL JULIEN SCHMIDT BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
@@ -1,439 +0,0 @@
|
||||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of the below source code is governed by the BSD 3-Clause license.
|
||||
|
||||
package httprouter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/errors"
|
||||
)
|
||||
|
||||
func min(a, b int) int {
|
||||
if a <= b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func countParams(path string) uint8 {
|
||||
var n uint
|
||||
for i := 0; i < len(path); i++ {
|
||||
if path[i] != ':' && path[i] != '*' {
|
||||
continue
|
||||
}
|
||||
n++
|
||||
}
|
||||
if n >= 255 {
|
||||
return 255
|
||||
}
|
||||
return uint8(n)
|
||||
}
|
||||
|
||||
type nodeType uint8
|
||||
|
||||
const (
|
||||
static nodeType = iota // default
|
||||
root
|
||||
param
|
||||
catchAll
|
||||
)
|
||||
|
||||
// Node is the default request handler's tree's entry type.
|
||||
// Examples of its algorithm can be found via googling or via youtube
|
||||
// search term: trie, tree sort, data structures: tree, reversed tree, sort tree etc...
|
||||
type Node struct {
|
||||
path string
|
||||
wildChild bool
|
||||
nType nodeType
|
||||
maxParams uint8
|
||||
indices string
|
||||
children []*Node
|
||||
handle context.Handlers
|
||||
priority uint32
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 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--
|
||||
}
|
||||
|
||||
// build new index char string
|
||||
if newPos != pos {
|
||||
n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
|
||||
n.indices[pos:pos+1] + // the index char we move
|
||||
n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
|
||||
}
|
||||
|
||||
return newPos
|
||||
}
|
||||
|
||||
// AddRoute adds a route with the given handler to the path.
|
||||
func (n *Node) AddRoute(path string, handle context.Handlers) error {
|
||||
fullPath := path
|
||||
n.priority++
|
||||
numParams := countParams(path)
|
||||
|
||||
// non-empty tree
|
||||
if len(n.path) > 0 || len(n.children) > 0 {
|
||||
walk:
|
||||
for {
|
||||
// Update maxParams of the current Node
|
||||
if numParams > n.maxParams {
|
||||
n.maxParams = numParams
|
||||
}
|
||||
|
||||
// Find the longest common prefix.
|
||||
// This also implies that the common prefix contains no ':' or '*'
|
||||
// since the existing key can't contain those chars.
|
||||
i := 0
|
||||
max := min(len(path), len(n.path))
|
||||
for i < max && path[i] == n.path[i] {
|
||||
i++
|
||||
}
|
||||
|
||||
// Split edge
|
||||
if i < len(n.path) {
|
||||
child := Node{
|
||||
path: n.path[i:],
|
||||
wildChild: n.wildChild,
|
||||
nType: static,
|
||||
indices: n.indices,
|
||||
children: n.children,
|
||||
handle: n.handle,
|
||||
priority: n.priority - 1,
|
||||
}
|
||||
|
||||
// Update maxParams (max of all children)
|
||||
for i := range child.children {
|
||||
if child.children[i].maxParams > child.maxParams {
|
||||
child.maxParams = child.children[i].maxParams
|
||||
}
|
||||
}
|
||||
|
||||
n.children = []*Node{&child}
|
||||
// []byte for proper unicode char conversion, see #65
|
||||
n.indices = string([]byte{n.path[i]})
|
||||
n.path = path[:i]
|
||||
n.handle = nil
|
||||
n.wildChild = false
|
||||
}
|
||||
|
||||
// Make new Node a child of this Node
|
||||
if i < len(path) {
|
||||
path = path[i:]
|
||||
|
||||
if n.wildChild {
|
||||
n = n.children[0]
|
||||
n.priority++
|
||||
|
||||
// Update maxParams of the child Node
|
||||
if numParams > n.maxParams {
|
||||
n.maxParams = numParams
|
||||
}
|
||||
numParams--
|
||||
|
||||
// Check if the wildcard matches
|
||||
if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
|
||||
// Check for longer wildcard, e.g. :name and :names
|
||||
(len(n.path) >= len(path) || path[len(n.path)] == '/') {
|
||||
continue walk
|
||||
} else {
|
||||
// Wildcard conflict
|
||||
pathSeg := strings.SplitN(path, "/", 2)[0]
|
||||
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
||||
return errors.New("'" + pathSeg +
|
||||
"' in new path '" + fullPath +
|
||||
"' conflicts with existing wildcard '" + n.path +
|
||||
"' in existing prefix '" + prefix +
|
||||
"'")
|
||||
}
|
||||
}
|
||||
|
||||
c := path[0]
|
||||
|
||||
// slash after param
|
||||
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||
n = n.children[0]
|
||||
n.priority++
|
||||
continue walk
|
||||
}
|
||||
|
||||
// Check if a child with the next path byte exists
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
if c == n.indices[i] {
|
||||
i = n.incrementChildPrio(i)
|
||||
n = n.children[i]
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise insert it
|
||||
if c != ':' && c != '*' {
|
||||
// []byte for proper unicode char conversion, see #65
|
||||
n.indices += string([]byte{c})
|
||||
child := &Node{
|
||||
maxParams: numParams,
|
||||
}
|
||||
n.children = append(n.children, child)
|
||||
n.incrementChildPrio(len(n.indices) - 1)
|
||||
n = child
|
||||
}
|
||||
return n.insertChild(numParams, path, fullPath, handle)
|
||||
|
||||
} else if i == len(path) { // Make Node a (in-path) leaf
|
||||
if n.handle != nil {
|
||||
return errors.New("a handle is already registered for path '" + fullPath + "'")
|
||||
}
|
||||
n.handle = handle
|
||||
}
|
||||
return nil
|
||||
}
|
||||
} else { // Empty tree
|
||||
n.insertChild(numParams, path, fullPath, handle)
|
||||
n.nType = root
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Node) insertChild(numParams uint8, path, fullPath string, handle context.Handlers) error {
|
||||
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]
|
||||
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 '*'
|
||||
case ':', '*':
|
||||
return errors.New("only one wildcard per path segment is allowed, has: '" +
|
||||
path[i:] + "' in path '" + fullPath + "'")
|
||||
default:
|
||||
end++
|
||||
}
|
||||
}
|
||||
|
||||
// check if this Node existing children which would be
|
||||
// unreachable if we insert the wildcard here
|
||||
if len(n.children) > 0 {
|
||||
return errors.New("wildcard route '" + path[i:end] +
|
||||
"' conflicts with existing children in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
// check if the wildcard has a name
|
||||
if end-i < 2 {
|
||||
return errors.New("wildcards must be named with a non-empty name in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
if c == ':' { // param
|
||||
// split path at the beginning of the wildcard
|
||||
if i > 0 {
|
||||
n.path = path[offset:i]
|
||||
offset = i
|
||||
}
|
||||
|
||||
child := &Node{
|
||||
nType: param,
|
||||
maxParams: numParams,
|
||||
}
|
||||
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
|
||||
|
||||
child := &Node{
|
||||
maxParams: numParams,
|
||||
priority: 1,
|
||||
}
|
||||
n.children = []*Node{child}
|
||||
n = child
|
||||
}
|
||||
|
||||
} else { // catchAll
|
||||
if end != max || numParams > 1 {
|
||||
return errors.New("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] == '/' {
|
||||
return errors.New("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
// currently fixed width 1 for '/'
|
||||
i--
|
||||
if path[i] != '/' {
|
||||
return errors.New("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,
|
||||
}
|
||||
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,
|
||||
handle: handle,
|
||||
priority: 1,
|
||||
}
|
||||
n.children = []*Node{child}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// insert remaining path part and handle to the leaf
|
||||
n.path = path[offset:]
|
||||
n.handle = handle
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolveRoute sets the handlers registered to a given path which is acquiring by ctx.Path().
|
||||
// The values of
|
||||
// wildcards are saved to the context's Values.
|
||||
// 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
|
||||
// given context.
|
||||
//
|
||||
// ResolveRoute finds the correct registered route from the Node when the ctx.Handlers() > 0.
|
||||
func (n *Node) ResolveRoute(ctx context.Context) (handlers context.Handlers, tsr bool) { //(p context.RequestParams, tsr bool) {
|
||||
path := ctx.Request().URL.Path
|
||||
handlers = ctx.Handlers()
|
||||
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):]
|
||||
// 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] {
|
||||
n = n.children[i]
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found.
|
||||
// We can recommend to redirect to the same URL without a
|
||||
// trailing slash if a leaf exists for that path.
|
||||
tsr = (path == "/" && n.handle != nil)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// handle wildcard child
|
||||
n = n.children[0]
|
||||
switch n.nType {
|
||||
case param:
|
||||
// find param end (either '/' or path end)
|
||||
end := 0
|
||||
for end < len(path) && path[end] != '/' {
|
||||
end++
|
||||
}
|
||||
|
||||
// save param value
|
||||
ctx.Params().Set(n.path[1:], path[:end])
|
||||
// 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
|
||||
tsr = (len(path) == end+1)
|
||||
return
|
||||
}
|
||||
|
||||
if handlers = n.handle; handlers != nil {
|
||||
return
|
||||
} else 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]
|
||||
tsr = (n.path == "/" && n.handle != nil)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case catchAll:
|
||||
ctx.Params().Set(n.path[2:], path)
|
||||
handlers = n.handle
|
||||
return
|
||||
|
||||
default:
|
||||
// invalid Node type here
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if path == n.path {
|
||||
// We should have reached the Node containing the handle.
|
||||
// Check if this Node has a handle registered.
|
||||
if handlers = n.handle; handlers != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if path == "/" && n.wildChild && n.nType != root {
|
||||
tsr = true
|
||||
return
|
||||
}
|
||||
|
||||
// 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] == '/' {
|
||||
n = n.children[i]
|
||||
tsr = (len(n.path) == 1 && n.handle != nil) ||
|
||||
(n.nType == catchAll && n.children[0].handle != nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Nothing found. We can recommend to redirect to the same URL with an
|
||||
// extra trailing slash if a leaf exists for that path
|
||||
tsr = (path == "/") ||
|
||||
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
|
||||
path == n.path[:len(n.path)-1] && n.handle != nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -189,8 +189,9 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) {
|
||||
for i, p := range tmpl.Params {
|
||||
if p.Type == ast.ParamTypePath {
|
||||
if i != len(tmpl.Params)-1 {
|
||||
return "", errors.New("parameter type \"ParamTypePath\" is allowed to exists to the very last of a path")
|
||||
return "", errors.New("parameter type \"ParamTypePath\" should be putted to the very last of a path")
|
||||
}
|
||||
|
||||
routePath = strings.Replace(routePath, p.Src, WildcardParam(p.Name), 1)
|
||||
} else {
|
||||
routePath = strings.Replace(routePath, p.Src, Param(p.Name), 1)
|
||||
@@ -202,6 +203,7 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) {
|
||||
|
||||
// note: returns nil if not needed, the caller(router) should be check for that before adding that on route's Middleware
|
||||
func convertTmplToHandler(tmpl *macro.Template) context.Handler {
|
||||
|
||||
needMacroHandler := false
|
||||
|
||||
// check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params.
|
||||
|
||||
@@ -31,7 +31,7 @@ func (l *Lexer) readChar() {
|
||||
l.ch = l.input[l.readPos]
|
||||
}
|
||||
l.pos = l.readPos
|
||||
l.readPos += 1
|
||||
l.readPos++
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
285
core/router/node/node.go
Normal file
285
core/router/node/node.go
Normal file
@@ -0,0 +1,285 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/errors"
|
||||
)
|
||||
|
||||
// Nodes a conversion type for []*node.
|
||||
type Nodes []*node
|
||||
|
||||
type node struct {
|
||||
s string
|
||||
wildcardParamName string // name of the wildcard parameter, only one per whole Node is allowed
|
||||
paramNames []string // only-names
|
||||
children Nodes
|
||||
handlers context.Handlers
|
||||
root bool
|
||||
}
|
||||
|
||||
// ErrDublicate returned from MakeChild when more than one routes have the same registered path.
|
||||
var ErrDublicate = errors.New("more than one routes have the same registered path")
|
||||
|
||||
// Add adds a node to the tree, returns an ErrDublicate error on failure.
|
||||
func (nodes *Nodes) Add(path string, handlers context.Handlers) error {
|
||||
// resolve params and if that node should be added as root
|
||||
var params []string
|
||||
var paramStart, paramEnd int
|
||||
for {
|
||||
paramStart = strings.IndexByte(path[paramEnd:], ':')
|
||||
if paramStart == -1 {
|
||||
break
|
||||
}
|
||||
paramStart += paramEnd
|
||||
paramStart++
|
||||
paramEnd = strings.IndexByte(path[paramStart:], '/')
|
||||
|
||||
if paramEnd == -1 {
|
||||
params = append(params, path[paramStart:])
|
||||
path = path[:paramStart]
|
||||
break
|
||||
}
|
||||
paramEnd += paramStart
|
||||
params = append(params, path[paramStart:paramEnd])
|
||||
path = path[:paramStart] + path[paramEnd:]
|
||||
paramEnd -= paramEnd - paramStart
|
||||
}
|
||||
|
||||
for _, idx := range paramsPos(path) {
|
||||
|
||||
if err := nodes.add(path[:idx], nil, nil, true); err != nil { // take the static path to its own node
|
||||
return err
|
||||
}
|
||||
// create a second, empty, dynamic parameter node without the last slash
|
||||
if nidx := idx + 1; len(path) < nidx {
|
||||
if err := nodes.add(path[:nidx], nil, nil, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// last, create the node filled by the full path, parameters and its handlers
|
||||
if err := nodes.add(path, params, handlers, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sort by static path, remember, they were already sorted by subdomains too.
|
||||
nodes.Sort()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nodes *Nodes) add(path string, paramNames []string, handlers context.Handlers, root bool) (err error) {
|
||||
// wraia etsi doulevei ara
|
||||
// na to kanw na exei to node to diko tou wildcard parameter name
|
||||
// kai sto telos na pernei auto, me vasi to *paramname
|
||||
// alla edw mesa 9a ginete register vasi tou last /
|
||||
loop:
|
||||
for _, n := range *nodes {
|
||||
|
||||
minlen := len(n.s)
|
||||
if len(path) < minlen {
|
||||
minlen = len(path)
|
||||
}
|
||||
|
||||
for i := 0; i < minlen; i++ {
|
||||
if n.s[i] == path[i] {
|
||||
continue
|
||||
}
|
||||
if i == 0 {
|
||||
continue loop
|
||||
}
|
||||
|
||||
*n = node{
|
||||
s: n.s[:i],
|
||||
children: Nodes{
|
||||
{
|
||||
s: n.s[i:],
|
||||
wildcardParamName: n.wildcardParamName,
|
||||
paramNames: n.paramNames,
|
||||
children: n.children,
|
||||
handlers: n.handlers,
|
||||
},
|
||||
{
|
||||
s: path[i:],
|
||||
wildcardParamName: n.wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
handlers: handlers,
|
||||
},
|
||||
},
|
||||
root: n.root,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(path) < len(n.s) {
|
||||
*n = node{
|
||||
s: n.s[:len(path)],
|
||||
wildcardParamName: n.wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
children: Nodes{
|
||||
{
|
||||
s: n.s[len(path):],
|
||||
wildcardParamName: n.wildcardParamName,
|
||||
paramNames: n.paramNames,
|
||||
children: n.children,
|
||||
handlers: n.handlers,
|
||||
},
|
||||
},
|
||||
handlers: handlers,
|
||||
root: n.root,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(path) > len(n.s) {
|
||||
err = n.children.add(path[len(n.s):], paramNames, handlers, false)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(handlers) == 0 { // missing handlers
|
||||
return nil
|
||||
}
|
||||
if len(n.handlers) > 0 { // n.handlers already setted
|
||||
return ErrDublicate
|
||||
}
|
||||
n.paramNames = paramNames
|
||||
n.handlers = handlers
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// set the wildcard param name to the root.
|
||||
|
||||
wildcardIdx := strings.IndexByte(path, '*')
|
||||
wildcardParamName := ""
|
||||
if wildcardIdx > 0 {
|
||||
wildcardParamName = path[wildcardIdx+1:]
|
||||
path = path[0:wildcardIdx-1] + "/" // replace *paramName with single slash
|
||||
}
|
||||
|
||||
n := &node{
|
||||
s: path,
|
||||
wildcardParamName: wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
handlers: handlers,
|
||||
root: root,
|
||||
}
|
||||
|
||||
*nodes = append(*nodes, n)
|
||||
return
|
||||
}
|
||||
|
||||
// Find resolves the path, fills its params
|
||||
// and returns the registered to the resolved node's handlers.
|
||||
func (nodes Nodes) Find(path string, params *context.RequestParams) context.Handlers {
|
||||
n, paramValues := nodes.findChild(path, nil)
|
||||
if n != nil {
|
||||
// map the params,
|
||||
// n.params are the param names
|
||||
if len(paramValues) > 0 {
|
||||
for i, name := range n.paramNames {
|
||||
params.Set(name, paramValues[i])
|
||||
}
|
||||
// last is the wildcard,
|
||||
// if paramValues are exceed from the registered param names.
|
||||
// Note that n.wildcardParamName can be not empty but that doesn't meaning
|
||||
// that it contains a wildcard path, so the check is required.
|
||||
if len(paramValues) > len(n.paramNames) {
|
||||
lastWildcardVal := paramValues[len(paramValues)-1]
|
||||
params.Set(n.wildcardParamName, lastWildcardVal)
|
||||
}
|
||||
}
|
||||
return n.handlers
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nodes Nodes) findChild(path string, params []string) (*node, []string) {
|
||||
for _, n := range nodes {
|
||||
if n.s == ":" {
|
||||
paramEnd := strings.IndexByte(path, '/')
|
||||
if paramEnd == -1 {
|
||||
if len(n.handlers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return n, append(params, path)
|
||||
}
|
||||
return n.children.findChild(path[paramEnd:], append(params, path[:paramEnd]))
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(path, n.s) {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(path) == len(n.s) { // Node matched until the end of path.
|
||||
if len(n.handlers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return n, params
|
||||
}
|
||||
|
||||
child, childParamNames := n.children.findChild(path[len(n.s):], params)
|
||||
|
||||
if child == nil || len(child.handlers) == 0 {
|
||||
// is wildcard and it is not root neither has children
|
||||
if n.s[len(n.s)-1] == '/' && !(n.root && (n.s == "/" || len(n.children) > 0)) {
|
||||
if len(n.handlers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return n, append(params, path[len(n.s):])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return child, childParamNames
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// childLen returns all the children's and their children's length.
|
||||
func (n *node) childLen() (i int) {
|
||||
for _, n := range n.children {
|
||||
i++
|
||||
i += n.childLen()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (n *node) isDynamic() bool {
|
||||
return n.s == ":"
|
||||
}
|
||||
|
||||
// Sort sets the static paths first.
|
||||
func (nodes Nodes) Sort() {
|
||||
|
||||
sort.Slice(nodes, func(i, j int) bool {
|
||||
if nodes[i].isDynamic() {
|
||||
return false
|
||||
}
|
||||
if nodes[j].isDynamic() {
|
||||
return true
|
||||
}
|
||||
return nodes[i].childLen() > nodes[j].childLen()
|
||||
})
|
||||
|
||||
for _, n := range nodes {
|
||||
n.children.Sort()
|
||||
}
|
||||
}
|
||||
|
||||
func paramsPos(s string) (pos []int) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
p := strings.IndexByte(s[i:], ':')
|
||||
if p == -1 {
|
||||
break
|
||||
}
|
||||
pos = append(pos, p+i)
|
||||
i = p + i
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func (r Route) IsOnline() bool {
|
||||
return r.Method != MethodNone
|
||||
}
|
||||
|
||||
// formats the parsed to the underline httprouter's path syntax.
|
||||
// formats the parsed to the underline path syntax.
|
||||
// path = "/api/users/:id"
|
||||
// return "/api/users/%v"
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user