1
0
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:
kataras
2017-06-11 23:07:50 +03:00
parent 5fa9789c35
commit d031ad55b8
21 changed files with 545 additions and 651 deletions

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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
}
}

View File

@@ -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.

View File

@@ -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
View 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
}

View File

@@ -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"
//