mirror of
https://github.com/kataras/iris.git
synced 2026-01-04 10:47:20 +00:00
add my new trie data structure implementation written from scratch and specifically designed for HTTP (and Iris) - see https://github.com/kataras/muxie for the net/http version of it
Former-commit-id: 4eed1585f29b57418b61f6de058f5d6db4bb98bf
This commit is contained in:
@@ -56,7 +56,7 @@ func genPaths(routesLength, minCharLength, maxCharLength int) []string {
|
||||
b.WriteString(randStringBytesMaskImprSrc(pathSegmentCharsLength))
|
||||
b.WriteString("/{name:string}/") // sugar.
|
||||
b.WriteString(randStringBytesMaskImprSrc(pathSegmentCharsLength))
|
||||
b.WriteString("/{age:number}/end")
|
||||
b.WriteString("/{age:int}/end")
|
||||
paths[i] = b.String()
|
||||
|
||||
b.Reset()
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/errors"
|
||||
"github.com/kataras/iris/core/netutil"
|
||||
"github.com/kataras/iris/core/router/node"
|
||||
)
|
||||
|
||||
// RequestHandler the middle man between acquiring a context and releasing it.
|
||||
@@ -25,25 +24,17 @@ type RequestHandler interface {
|
||||
RouteExists(ctx context.Context, method, path string) bool
|
||||
}
|
||||
|
||||
type tree struct {
|
||||
Method string
|
||||
// subdomain is empty for default-hostname routes,
|
||||
// ex: mysubdomain.
|
||||
Subdomain string
|
||||
Nodes *node.Nodes
|
||||
}
|
||||
|
||||
type routerHandler struct {
|
||||
trees []*tree
|
||||
trees []*trie
|
||||
hosts bool // true if at least one route contains a Subdomain.
|
||||
}
|
||||
|
||||
var _ RequestHandler = &routerHandler{}
|
||||
|
||||
func (h *routerHandler) getTree(method, subdomain string) *tree {
|
||||
func (h *routerHandler) getTree(method, subdomain string) *trie {
|
||||
for i := range h.trees {
|
||||
t := h.trees[i]
|
||||
if t.Method == method && t.Subdomain == subdomain {
|
||||
if t.method == method && t.subdomain == subdomain {
|
||||
return t
|
||||
}
|
||||
}
|
||||
@@ -63,12 +54,14 @@ func (h *routerHandler) addRoute(r *Route) error {
|
||||
t := h.getTree(method, subdomain)
|
||||
|
||||
if t == nil {
|
||||
n := node.Nodes{}
|
||||
n := newTrieNode()
|
||||
// first time we register a route to this method with this subdomain
|
||||
t = &tree{Method: method, Subdomain: subdomain, Nodes: &n}
|
||||
t = &trie{method: method, subdomain: subdomain, root: n}
|
||||
h.trees = append(h.trees, t)
|
||||
}
|
||||
return t.Nodes.Add(routeName, path, handlers)
|
||||
|
||||
t.insert(path, routeName, handlers)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewDefaultHandler returns the handler which is responsible
|
||||
@@ -188,11 +181,11 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
|
||||
for i := range h.trees {
|
||||
t := h.trees[i]
|
||||
if method != t.Method {
|
||||
if method != t.method {
|
||||
continue
|
||||
}
|
||||
|
||||
if h.hosts && t.Subdomain != "" {
|
||||
if h.hosts && t.subdomain != "" {
|
||||
requestHost := ctx.Host()
|
||||
if netutil.IsLoopbackSubdomain(requestHost) {
|
||||
// this fixes a bug when listening on
|
||||
@@ -201,7 +194,7 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
continue // it's not a subdomain, it's something like 127.0.0.1 probably
|
||||
}
|
||||
// it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty
|
||||
if t.Subdomain == SubdomainWildcardIndicator {
|
||||
if t.subdomain == SubdomainWildcardIndicator {
|
||||
// mydomain.com -> invalid
|
||||
// localhost -> invalid
|
||||
// sub.mydomain.com -> valid
|
||||
@@ -219,14 +212,14 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
// continue to that, any subdomain is valid.
|
||||
} else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot.
|
||||
} else if !strings.HasPrefix(requestHost, t.subdomain) { // t.subdomain contains the dot.
|
||||
continue
|
||||
}
|
||||
}
|
||||
routeName, handlers := t.Nodes.Find(path, ctx.Params())
|
||||
if len(handlers) > 0 {
|
||||
ctx.SetCurrentRouteName(routeName)
|
||||
ctx.Do(handlers)
|
||||
n := t.search(path, ctx.Params())
|
||||
if n != nil {
|
||||
ctx.SetCurrentRouteName(n.RouteName)
|
||||
ctx.Do(n.Handlers)
|
||||
// found
|
||||
return
|
||||
}
|
||||
@@ -237,15 +230,12 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
if ctx.Application().ConfigurationReadOnly().GetFireMethodNotAllowed() {
|
||||
for i := range h.trees {
|
||||
t := h.trees[i]
|
||||
// a bit slower than previous implementation but @kataras let me to apply this change
|
||||
// because it's more reliable.
|
||||
//
|
||||
// if `Configuration#FireMethodNotAllowed` is kept as defaulted(false) then this function will not
|
||||
// run, therefore performance kept as before.
|
||||
if t.Nodes.Exists(path) {
|
||||
if h.subdomainAndPathAndMethodExists(ctx, t, "", path) {
|
||||
// RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||
// The response MUST include an Allow header containing a list of valid methods for the requested resource.
|
||||
ctx.Header("Allow", t.Method)
|
||||
ctx.Header("Allow", t.method)
|
||||
ctx.StatusCode(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
@@ -255,55 +245,55 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
ctx.StatusCode(http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (h *routerHandler) subdomainAndPathAndMethodExists(ctx context.Context, t *trie, method, path string) bool {
|
||||
if method != "" && method != t.method {
|
||||
return false
|
||||
}
|
||||
|
||||
if h.hosts && t.subdomain != "" {
|
||||
requestHost := ctx.Host()
|
||||
if netutil.IsLoopbackSubdomain(requestHost) {
|
||||
// this fixes a bug when listening on
|
||||
// 127.0.0.1:8080 for example
|
||||
// and have a wildcard subdomain and a route registered to root domain.
|
||||
return false // it's not a subdomain, it's something like 127.0.0.1 probably
|
||||
}
|
||||
// it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty
|
||||
if t.subdomain == SubdomainWildcardIndicator {
|
||||
// mydomain.com -> invalid
|
||||
// localhost -> invalid
|
||||
// sub.mydomain.com -> valid
|
||||
// sub.localhost -> valid
|
||||
serverHost := ctx.Application().ConfigurationReadOnly().GetVHost()
|
||||
if serverHost == requestHost {
|
||||
return false // it's not a subdomain, it's a full domain (with .com...)
|
||||
}
|
||||
|
||||
dotIdx := strings.IndexByte(requestHost, '.')
|
||||
slashIdx := strings.IndexByte(requestHost, '/')
|
||||
if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) {
|
||||
// if "." was found anywhere but not at the first path segment (host).
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
// continue to that, any subdomain is valid.
|
||||
} else if !strings.HasPrefix(requestHost, t.subdomain) { // t.subdomain contains the dot.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
n := t.search(path, ctx.Params())
|
||||
return n != nil
|
||||
}
|
||||
|
||||
// RouteExists reports whether a particular route exists
|
||||
// It will search from the current subdomain of context's host, if not inside the root domain.
|
||||
func (h *routerHandler) RouteExists(ctx context.Context, method, path string) bool {
|
||||
for i := range h.trees {
|
||||
t := h.trees[i]
|
||||
if method != t.Method {
|
||||
continue
|
||||
}
|
||||
|
||||
if h.hosts && t.Subdomain != "" {
|
||||
requestHost := ctx.Host()
|
||||
if netutil.IsLoopbackSubdomain(requestHost) {
|
||||
// this fixes a bug when listening on
|
||||
// 127.0.0.1:8080 for example
|
||||
// and have a wildcard subdomain and a route registered to root domain.
|
||||
continue // it's not a subdomain, it's something like 127.0.0.1 probably
|
||||
}
|
||||
// it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty
|
||||
if t.Subdomain == SubdomainWildcardIndicator {
|
||||
// mydomain.com -> invalid
|
||||
// localhost -> invalid
|
||||
// sub.mydomain.com -> valid
|
||||
// sub.localhost -> valid
|
||||
serverHost := ctx.Application().ConfigurationReadOnly().GetVHost()
|
||||
if serverHost == requestHost {
|
||||
continue // it's not a subdomain, it's a full domain (with .com...)
|
||||
}
|
||||
|
||||
dotIdx := strings.IndexByte(requestHost, '.')
|
||||
slashIdx := strings.IndexByte(requestHost, '/')
|
||||
if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) {
|
||||
// if "." was found anywhere but not at the first path segment (host).
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
// continue to that, any subdomain is valid.
|
||||
} else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
_, handlers := t.Nodes.Find(path, ctx.Params())
|
||||
if len(handlers) > 0 {
|
||||
// found
|
||||
if h.subdomainAndPathAndMethodExists(ctx, t, method, path) {
|
||||
return true
|
||||
}
|
||||
|
||||
// not found or method not allowed.
|
||||
break
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
@@ -1,448 +0,0 @@
|
||||
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
|
||||
routeName string
|
||||
wildcardParamName string // name of the wildcard parameter, only one per whole Node is allowed
|
||||
paramNames []string // only-names
|
||||
childrenNodes Nodes
|
||||
handlers context.Handlers
|
||||
root bool
|
||||
rootWildcard bool // if it's a wildcard {path} type on root, it should allow everything but it is not conflicts with
|
||||
// any other static or dynamic or wildcard paths if exists on other nodes.
|
||||
}
|
||||
|
||||
// ErrDublicate returnned from `Add` when two or more routes have the same registered path.
|
||||
var ErrDublicate = errors.New("two or more routes have the same registered path")
|
||||
|
||||
/// TODO: clean up needed until v8.5
|
||||
|
||||
// Add adds a node to the tree, returns an ErrDublicate error on failure.
|
||||
func (nodes *Nodes) Add(routeName string, path string, handlers context.Handlers) error {
|
||||
// println("[Add] adding path: " + path)
|
||||
// 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
|
||||
}
|
||||
|
||||
var p []int
|
||||
for i := 0; i < len(path); i++ {
|
||||
idx := strings.IndexByte(path[i:], ':')
|
||||
if idx == -1 {
|
||||
break
|
||||
}
|
||||
p = append(p, idx+i)
|
||||
i = idx + i
|
||||
}
|
||||
|
||||
for _, idx := range p {
|
||||
// print("-2 nodes.Add: path: " + path + " params len: ")
|
||||
// println(len(params))
|
||||
if err := nodes.add(routeName, path[:idx], nil, nil, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// print("-1 nodes.Add: path: " + path + " params len: ")
|
||||
// println(len(params))
|
||||
if nidx := idx + 1; len(path) > nidx {
|
||||
if err := nodes.add(routeName, path[:nidx], nil, nil, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// print("nodes.Add: path: " + path + " params len: ")
|
||||
// println(len(params))
|
||||
if err := nodes.add(routeName, path, params, handlers, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// prioritize by static path remember, they were already sorted by subdomains too.
|
||||
nodes.prioritize()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nodes *Nodes) add(routeName, path string, paramNames []string, handlers context.Handlers, root bool) (err error) {
|
||||
// println("[add] route name: " + routeName)
|
||||
// println("[add] adding path: " + path)
|
||||
|
||||
// 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 /
|
||||
|
||||
// set the wildcard param name to the root and its children.
|
||||
wildcardIdx := strings.IndexByte(path, '*')
|
||||
wildcardParamName := ""
|
||||
if wildcardIdx > 0 && len(paramNames) == 0 { // 27 Oct comment: && len(paramNames) == 0 {
|
||||
wildcardParamName = path[wildcardIdx+1:]
|
||||
path = path[0:wildcardIdx-1] + "/" // replace *paramName with single slash
|
||||
|
||||
// if path[len(path)-1] == '/' {
|
||||
// if root wildcard, then add it as it's and return
|
||||
rootWildcard := path == "/"
|
||||
if rootWildcard {
|
||||
path += "/" // if root wildcard, then do it like "//" instead of simple "/"
|
||||
}
|
||||
|
||||
n := &node{
|
||||
rootWildcard: rootWildcard,
|
||||
s: path,
|
||||
routeName: routeName,
|
||||
wildcardParamName: wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
handlers: handlers,
|
||||
root: root,
|
||||
}
|
||||
*nodes = append(*nodes, n)
|
||||
// println("1. nodes.Add path: " + path)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
loop:
|
||||
for _, n := range *nodes {
|
||||
if n.rootWildcard {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(n.paramNames) == 0 && n.wildcardParamName != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
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],
|
||||
childrenNodes: Nodes{
|
||||
{
|
||||
s: n.s[i:],
|
||||
routeName: n.routeName,
|
||||
wildcardParamName: n.wildcardParamName, // wildcardParamName
|
||||
paramNames: n.paramNames,
|
||||
childrenNodes: n.childrenNodes,
|
||||
handlers: n.handlers,
|
||||
},
|
||||
{
|
||||
s: path[i:],
|
||||
routeName: routeName,
|
||||
wildcardParamName: wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
handlers: handlers,
|
||||
},
|
||||
},
|
||||
root: n.root,
|
||||
}
|
||||
|
||||
// println("2. change n and return " + n.s[:i] + " and " + path[i:])
|
||||
return
|
||||
}
|
||||
|
||||
if len(path) < len(n.s) {
|
||||
// println("3. change n and return | n.s[:len(path)] = " + n.s[:len(path)-1] + " and child: " + n.s[len(path)-1:])
|
||||
|
||||
*n = node{
|
||||
s: n.s[:len(path)],
|
||||
routeName: routeName,
|
||||
wildcardParamName: wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
childrenNodes: Nodes{
|
||||
{
|
||||
s: n.s[len(path):],
|
||||
routeName: n.routeName,
|
||||
wildcardParamName: n.wildcardParamName, // wildcardParamName
|
||||
paramNames: n.paramNames,
|
||||
childrenNodes: n.childrenNodes,
|
||||
handlers: n.handlers,
|
||||
},
|
||||
},
|
||||
handlers: handlers,
|
||||
root: n.root,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(path) > len(n.s) {
|
||||
if n.wildcardParamName != "" {
|
||||
n := &node{
|
||||
s: path,
|
||||
routeName: routeName,
|
||||
wildcardParamName: wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
handlers: handlers,
|
||||
root: root,
|
||||
}
|
||||
// println("3.5. nodes.Add path: " + n.s)
|
||||
*nodes = append(*nodes, n)
|
||||
return
|
||||
}
|
||||
|
||||
pathToAdd := path[len(n.s):]
|
||||
// println("4. nodes.Add route name: " + routeName)
|
||||
// println("4. nodes.Add path: " + pathToAdd)
|
||||
err = n.childrenNodes.add(routeName, pathToAdd, 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
|
||||
n.routeName = routeName
|
||||
return
|
||||
}
|
||||
|
||||
// START
|
||||
// Author's note:
|
||||
// 27 Oct 2017; fixes s|i|l+static+p
|
||||
// without breaking the current tests.
|
||||
if wildcardIdx > 0 {
|
||||
wildcardParamName = path[wildcardIdx+1:]
|
||||
path = path[0:wildcardIdx-1] + "/"
|
||||
}
|
||||
// END
|
||||
|
||||
n := &node{
|
||||
s: path,
|
||||
routeName: routeName,
|
||||
wildcardParamName: wildcardParamName,
|
||||
paramNames: paramNames,
|
||||
handlers: handlers,
|
||||
root: root,
|
||||
}
|
||||
*nodes = append(*nodes, n)
|
||||
|
||||
// println("5. node add on path: " + path + " n.s: " + n.s + " wildcard param: " + n.wildcardParamName)
|
||||
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) (string, context.Handlers) {
|
||||
n, paramValues := nodes.findChild(path, nil)
|
||||
if n != nil {
|
||||
// map the params,
|
||||
// n.params are the param names
|
||||
if len(paramValues) > 0 {
|
||||
// println("-----------")
|
||||
// print("param values returned len: ")
|
||||
// println(len(paramValues))
|
||||
// println("first value is: " + paramValues[0])
|
||||
// print("n.paramNames len: ")
|
||||
// println(len(n.paramNames))
|
||||
for i, name := range n.paramNames {
|
||||
// println("setting param name: " + name + " = " + paramValues[i])
|
||||
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) {
|
||||
// println("len(paramValues) > len(n.paramNames)")
|
||||
lastWildcardVal := paramValues[len(paramValues)-1]
|
||||
// println("setting wildcard param name: " + n.wildcardParamName + " = " + lastWildcardVal)
|
||||
params.Set(n.wildcardParamName, lastWildcardVal)
|
||||
}
|
||||
}
|
||||
|
||||
return n.routeName, n.handlers
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Exists returns true if a node with that "path" exists,
|
||||
// otherise false.
|
||||
//
|
||||
// We don't care about parameters here.
|
||||
func (nodes Nodes) Exists(path string) bool {
|
||||
n, _ := nodes.findChild(path, nil)
|
||||
return n != nil && len(n.handlers) > 0
|
||||
}
|
||||
|
||||
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.childrenNodes.findChild(path[paramEnd:], append(params, path[:paramEnd]))
|
||||
}
|
||||
|
||||
// println("n.s: " + n.s)
|
||||
// print("n.childrenNodes len: ")
|
||||
// println(len(n.childrenNodes))
|
||||
// print("n.root: ")
|
||||
// println(n.root)
|
||||
|
||||
// by runtime check of:,
|
||||
// if n.s == "//" && n.root && n.wildcardParamName != "" {
|
||||
// but this will slow down, so we have a static field on the node itself:
|
||||
if n.rootWildcard {
|
||||
// println("return from n.rootWildcard")
|
||||
// single root wildcard
|
||||
if len(path) < 2 {
|
||||
// do not remove that, it seems useless but it's not,
|
||||
// we had an error while production, this fixes that.
|
||||
path = "/" + path
|
||||
}
|
||||
return n, append(params, path[1:])
|
||||
}
|
||||
|
||||
// second conditional may be unnecessary
|
||||
// because of the n.rootWildcard before, but do it.
|
||||
if n.wildcardParamName != "" && len(path) > 2 {
|
||||
// println("n has wildcard n.s: " + n.s + " on path: " + path)
|
||||
// n.s = static/, path = static
|
||||
|
||||
// println(n.s + " vs path: " + path)
|
||||
|
||||
// we could have /other/ as n.s so
|
||||
// we must do this check, remember:
|
||||
// now wildcards live on their own nodes
|
||||
if len(path) == len(n.s)-1 {
|
||||
// then it's like:
|
||||
// path = /other2
|
||||
// ns = /other2/
|
||||
if path == n.s[0:len(n.s)-1] {
|
||||
return n, params
|
||||
}
|
||||
}
|
||||
|
||||
// othwerwise path = /other2/dsadas
|
||||
// ns= /other2/
|
||||
if strings.HasPrefix(path, n.s) {
|
||||
if len(path) > len(n.s)+1 {
|
||||
return n, append(params, path[len(n.s):]) // without slash
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(path, n.s) {
|
||||
// fmt.Printf("---here root: %v, n.s: "+n.s+" and path: "+path+" is dynamic: %v , wildcardParamName: %s, children len: %v \n", n.root, n.isDynamic(), n.wildcardParamName, len(n.childrenNodes))
|
||||
// println(path + " n.s: " + n.s + " continue...")
|
||||
continue
|
||||
}
|
||||
|
||||
if len(path) == len(n.s) {
|
||||
if len(n.handlers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return n, params
|
||||
}
|
||||
|
||||
child, childParamNames := n.childrenNodes.findChild(path[len(n.s):], params)
|
||||
|
||||
// print("childParamNames len: ")
|
||||
// println(len(childParamNames))
|
||||
|
||||
// if len(childParamNames) > 0 {
|
||||
// println("childParamsNames[0] = " + childParamNames[0])
|
||||
// }
|
||||
|
||||
if child == nil || len(child.handlers) == 0 {
|
||||
if n.s[len(n.s)-1] == '/' && !(n.root && (n.s == "/" || len(n.childrenNodes) > 0)) {
|
||||
if len(n.handlers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// println("if child == nil.... | n.s = " + n.s)
|
||||
// print("n.paramNames len: ")
|
||||
// println(n.paramNames)
|
||||
// print("n.wildcardParamName is: ")
|
||||
// println(n.wildcardParamName)
|
||||
// print("return n, append(params, path[len(n.s) | params: ")
|
||||
// println(path[len(n.s):])
|
||||
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.childrenNodes {
|
||||
i++
|
||||
i += n.childLen()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (n *node) isDynamic() bool {
|
||||
return n.s == ":" || n.wildcardParamName != "" || n.rootWildcard
|
||||
}
|
||||
|
||||
// prioritize sets the static paths first.
|
||||
func (nodes Nodes) prioritize() {
|
||||
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.childrenNodes.prioritize()
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,6 @@ import (
|
||||
"github.com/kataras/iris/macro/interpreter/lexer"
|
||||
)
|
||||
|
||||
const (
|
||||
// ParamStart the character in string representation where the underline router starts its dynamic named parameter.
|
||||
ParamStart = ":"
|
||||
// WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard
|
||||
// path parameter.
|
||||
WildcardParamStart = "*"
|
||||
)
|
||||
|
||||
// Param receives a parameter name prefixed with the ParamStart symbol.
|
||||
func Param(name string) string {
|
||||
return prefix(name, ParamStart)
|
||||
@@ -35,10 +27,8 @@ func WildcardParam(name string) string {
|
||||
|
||||
func convertMacroTmplToNodePath(tmpl macro.Template) string {
|
||||
routePath := tmpl.Src
|
||||
if len(tmpl.Params) > 0 {
|
||||
if routePath[len(routePath)-1] == '/' {
|
||||
routePath = routePath[0 : len(routePath)-2] // remove the last "/" if macro syntax instead of underline's.
|
||||
}
|
||||
if len(routePath) > 1 && routePath[len(routePath)-1] == '/' {
|
||||
routePath = routePath[0 : len(routePath)-1] // remove any last "/"
|
||||
}
|
||||
|
||||
// if it has started with {} and it's valid
|
||||
|
||||
@@ -53,8 +53,8 @@ func TestSplitPath(t *testing.T) {
|
||||
[]string{"/user", "/admin"}},
|
||||
{"/single_no_params",
|
||||
[]string{"/single_no_params"}},
|
||||
{"/single/{id:number}",
|
||||
[]string{"/single/{id:number}"}},
|
||||
{"/single/{id:int}",
|
||||
[]string{"/single/{id:int}"}},
|
||||
}
|
||||
|
||||
equalSlice := func(s1 []string, s2 []string) bool {
|
||||
|
||||
@@ -122,20 +122,25 @@ func TestRouterWildcardRootMany(t *testing.T) {
|
||||
|
||||
func TestRouterWildcardRootManyAndRootStatic(t *testing.T) {
|
||||
var tt = []testRoute{
|
||||
// all routes will be handlded by "h" because we added wildcard to root,
|
||||
// routes that may return 404 will be handled by the below route ("h" handler) because we added wildcard to root,
|
||||
// this feature is very important and can remove noumerous of previous hacks on our apps.
|
||||
//
|
||||
// Static paths and parameters have priority over wildcard, all three types can be registered in the same path prefix.
|
||||
//
|
||||
// Remember, all of those routes are registered don't be tricked by the visual appearance of the below test blocks.
|
||||
{"GET", "/{p:path}", h, []testRouteRequest{
|
||||
{"GET", "", "/other2almost/some", iris.StatusOK, same_as_request_path},
|
||||
}},
|
||||
{"GET", "/static/{p:path}", h, []testRouteRequest{
|
||||
{"GET", "", "/static", iris.StatusOK, same_as_request_path},
|
||||
{"GET", "", "/static", iris.StatusOK, same_as_request_path}, // HERE<- IF NOT FOUND THEN BACKWARDS TO WILDCARD IF THERE IS ONE, HMM.
|
||||
{"GET", "", "/static/something/here", iris.StatusOK, same_as_request_path},
|
||||
}},
|
||||
{"GET", "/", h, []testRouteRequest{
|
||||
{"GET", "", "/", iris.StatusOK, same_as_request_path},
|
||||
}},
|
||||
{"GET", "/other/{paramother:path}", h2, []testRouteRequest{
|
||||
{"GET", "", "/other", iris.StatusForbidden, same_as_request_path},
|
||||
// OK and not h2 because of the root wildcard.
|
||||
{"GET", "", "/other", iris.StatusOK, same_as_request_path},
|
||||
{"GET", "", "/other/wildcard", iris.StatusForbidden, same_as_request_path},
|
||||
{"GET", "", "/other/wildcard/here", iris.StatusForbidden, same_as_request_path},
|
||||
}},
|
||||
@@ -145,6 +150,7 @@ func TestRouterWildcardRootManyAndRootStatic(t *testing.T) {
|
||||
}},
|
||||
{"GET", "/other2/static", h3, []testRouteRequest{
|
||||
{"GET", "", "/other2/static", iris.StatusOK, prefix_static_path_following_by_request_path},
|
||||
// h2(Forbiddenn) instead of h3 OK because it will be handled by the /other2/{paramothersecond:path}'s handler which gives 403.
|
||||
{"GET", "", "/other2/staticed", iris.StatusForbidden, same_as_request_path},
|
||||
}},
|
||||
}
|
||||
@@ -165,6 +171,7 @@ func testTheRoutes(t *testing.T, tests []testRoute, debug bool) {
|
||||
// run the tests
|
||||
for _, tt := range tests {
|
||||
for _, req := range tt.requests {
|
||||
// t.Logf("req: %s:%s\n", tt.method, tt.path)
|
||||
method := req.method
|
||||
if method == "" {
|
||||
method = tt.method
|
||||
|
||||
267
core/router/trie.go
Normal file
267
core/router/trie.go
Normal file
@@ -0,0 +1,267 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
const (
|
||||
// ParamStart the character in string representation where the underline router starts its dynamic named parameter.
|
||||
ParamStart = ":"
|
||||
// WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard
|
||||
// path parameter.
|
||||
WildcardParamStart = "*"
|
||||
)
|
||||
|
||||
// An iris-specific identical version of the https://github.com/kataras/muxie version 1.0.0 released at 15 Oct 2018
|
||||
type trieNode struct {
|
||||
parent *trieNode
|
||||
|
||||
children map[string]*trieNode
|
||||
hasDynamicChild bool // does one of the children contains a parameter or wildcard?
|
||||
childNamedParameter bool // is the child a named parameter (single segmnet)
|
||||
childWildcardParameter bool // or it is a wildcard (can be more than one path segments) ?
|
||||
paramKeys []string // the param keys without : or *.
|
||||
end bool // it is a complete node, here we stop and we can say that the node is valid.
|
||||
key string // if end == true then key is filled with the original value of the insertion's key.
|
||||
// if key != "" && its parent has childWildcardParameter == true,
|
||||
// we need it to track the static part for the closest-wildcard's parameter storage.
|
||||
staticKey string
|
||||
|
||||
// insert data.
|
||||
Handlers context.Handlers
|
||||
RouteName string
|
||||
}
|
||||
|
||||
func newTrieNode() *trieNode {
|
||||
n := new(trieNode)
|
||||
return n
|
||||
}
|
||||
|
||||
func (tn *trieNode) hasChild(s string) bool {
|
||||
return tn.getChild(s) != nil
|
||||
}
|
||||
|
||||
func (tn *trieNode) getChild(s string) *trieNode {
|
||||
if tn.children == nil {
|
||||
tn.children = make(map[string]*trieNode)
|
||||
}
|
||||
|
||||
return tn.children[s]
|
||||
}
|
||||
|
||||
func (tn *trieNode) addChild(s string, n *trieNode) {
|
||||
if tn.children == nil {
|
||||
tn.children = make(map[string]*trieNode)
|
||||
}
|
||||
|
||||
if _, exists := tn.children[s]; exists {
|
||||
return
|
||||
}
|
||||
|
||||
n.parent = tn
|
||||
tn.children[s] = n
|
||||
}
|
||||
|
||||
func (tn *trieNode) findClosestParentWildcardNode() *trieNode {
|
||||
tn = tn.parent
|
||||
for tn != nil {
|
||||
if tn.childWildcardParameter {
|
||||
return tn.getChild(WildcardParamStart)
|
||||
}
|
||||
|
||||
tn = tn.parent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tn *trieNode) String() string {
|
||||
return tn.key
|
||||
}
|
||||
|
||||
type trie struct {
|
||||
root *trieNode
|
||||
|
||||
// if true then it will handle any path if not other parent wildcard exists,
|
||||
// so even 404 (on http services) is up to it, see trie#insert.
|
||||
hasRootWildcard bool
|
||||
|
||||
method string
|
||||
// subdomain is empty for default-hostname routes,
|
||||
// ex: mysubdomain.
|
||||
subdomain string
|
||||
}
|
||||
|
||||
func newTrie() *trie {
|
||||
return &trie{
|
||||
root: newTrieNode(),
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
pathSep = "/"
|
||||
pathSepB = '/'
|
||||
)
|
||||
|
||||
func slowPathSplit(path string) []string {
|
||||
if path == "/" {
|
||||
return []string{"/"}
|
||||
}
|
||||
|
||||
return strings.Split(path, pathSep)[1:]
|
||||
}
|
||||
|
||||
func (tr *trie) insert(path, routeName string, handlers context.Handlers) {
|
||||
input := slowPathSplit(path)
|
||||
|
||||
n := tr.root
|
||||
var paramKeys []string
|
||||
|
||||
for _, s := range input {
|
||||
c := s[0]
|
||||
|
||||
if isParam, isWildcard := c == ParamStart[0], c == WildcardParamStart[0]; isParam || isWildcard {
|
||||
n.hasDynamicChild = true
|
||||
paramKeys = append(paramKeys, s[1:]) // without : or *.
|
||||
|
||||
// if node has already a wildcard, don't force a value, check for true only.
|
||||
if isParam {
|
||||
n.childNamedParameter = true
|
||||
s = ParamStart
|
||||
}
|
||||
|
||||
if isWildcard {
|
||||
n.childWildcardParameter = true
|
||||
s = WildcardParamStart
|
||||
if tr.root == n {
|
||||
tr.hasRootWildcard = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !n.hasChild(s) {
|
||||
child := newTrieNode()
|
||||
n.addChild(s, child)
|
||||
}
|
||||
|
||||
n = n.getChild(s)
|
||||
}
|
||||
|
||||
n.RouteName = routeName
|
||||
n.Handlers = handlers
|
||||
n.paramKeys = paramKeys
|
||||
n.key = path
|
||||
n.end = true
|
||||
|
||||
i := strings.Index(path, ParamStart)
|
||||
if i == -1 {
|
||||
i = strings.Index(path, WildcardParamStart)
|
||||
}
|
||||
if i == -1 {
|
||||
i = len(n.key)
|
||||
}
|
||||
|
||||
n.staticKey = path[:i]
|
||||
}
|
||||
|
||||
func (tr *trie) search(q string, params *context.RequestParams) *trieNode {
|
||||
end := len(q)
|
||||
n := tr.root
|
||||
if end == 1 && q[0] == pathSepB {
|
||||
return n.getChild(pathSep)
|
||||
}
|
||||
|
||||
start := 1
|
||||
i := 1
|
||||
var paramValues []string
|
||||
|
||||
for {
|
||||
if i == end || q[i] == pathSepB {
|
||||
if child := n.getChild(q[start:i]); child != nil {
|
||||
n = child
|
||||
} else if n.childNamedParameter {
|
||||
n = n.getChild(ParamStart)
|
||||
if ln := len(paramValues); cap(paramValues) > ln {
|
||||
paramValues = paramValues[:ln+1]
|
||||
paramValues[ln] = q[start:i]
|
||||
} else {
|
||||
paramValues = append(paramValues, q[start:i])
|
||||
}
|
||||
} else if n.childWildcardParameter {
|
||||
n = n.getChild(WildcardParamStart)
|
||||
if ln := len(paramValues); cap(paramValues) > ln {
|
||||
paramValues = paramValues[:ln+1]
|
||||
paramValues[ln] = q[start:]
|
||||
} else {
|
||||
paramValues = append(paramValues, q[start:])
|
||||
}
|
||||
break
|
||||
} else {
|
||||
n = n.findClosestParentWildcardNode()
|
||||
if n != nil {
|
||||
// means that it has :param/static and *wildcard, we go trhough the :param
|
||||
// but the next path segment is not the /static, so go back to *wildcard
|
||||
// instead of not found.
|
||||
//
|
||||
// Fixes:
|
||||
// /hello/*p
|
||||
// /hello/:p1/static/:p2
|
||||
// req: http://localhost:8080/hello/dsadsa/static/dsadsa => found
|
||||
// req: http://localhost:8080/hello/dsadsa => but not found!
|
||||
// and
|
||||
// /second/wild/*p
|
||||
// /second/wild/static/otherstatic/
|
||||
// req: /second/wild/static/otherstatic/random => but not found!
|
||||
params.Set(n.paramKeys[0], q[len(n.staticKey):])
|
||||
return n
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if i == end {
|
||||
break
|
||||
}
|
||||
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
if n == nil || !n.end {
|
||||
if n != nil { // we need it on both places, on last segment (below) or on the first unnknown (above).
|
||||
if n = n.findClosestParentWildcardNode(); n != nil {
|
||||
params.Set(n.paramKeys[0], q[len(n.staticKey):])
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
if tr.hasRootWildcard {
|
||||
// that's the case for root wildcard, tests are passing
|
||||
// even without it but stick with it for reference.
|
||||
// Note ote that something like:
|
||||
// Routes: /other2/*myparam and /other2/static
|
||||
// Reqs: /other2/staticed will be handled
|
||||
// the /other2/*myparam and not the root wildcard, which is what we want.
|
||||
//
|
||||
n = tr.root.getChild(WildcardParamStart)
|
||||
params.Set(n.paramKeys[0], q[1:])
|
||||
return n
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, paramValue := range paramValues {
|
||||
if len(n.paramKeys) > i {
|
||||
params.Set(n.paramKeys[i], paramValue)
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
Reference in New Issue
Block a user