mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
458 lines
9.7 KiB
Go
458 lines
9.7 KiB
Go
// Copyright (c) 2013 Julien Schmidt, Copyright (c) 2016 Gerasimos Maropoulos,
|
|
|
|
package iris
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
|
|
"github.com/kataras/iris/utils"
|
|
)
|
|
|
|
const (
|
|
isStatic BranchCase = iota
|
|
isRoot
|
|
hasParams
|
|
matchEverything
|
|
)
|
|
|
|
type (
|
|
// PathParameter is a struct which contains Key and Value, used for named path parameters
|
|
PathParameter struct {
|
|
Key string
|
|
Value string
|
|
}
|
|
|
|
// PathParameters type for a slice of PathParameter
|
|
// Tt's a slice of PathParameter type, because it's faster than map
|
|
PathParameters []PathParameter
|
|
|
|
// BranchCase is the type which the type of Branch using in order to determinate what type (parameterized, anything, static...) is the perticular node
|
|
BranchCase uint8
|
|
|
|
// IBranch is the interface which the type Branch must implement
|
|
IBranch interface {
|
|
AddBranch(string, Middleware)
|
|
AddNode(uint8, string, string, Middleware)
|
|
GetBranch(string, PathParameters) (Middleware, PathParameters, bool)
|
|
GivePrecedenceTo(index int) int
|
|
}
|
|
|
|
// Branch is the node of a tree of the routes,
|
|
// in order to learn how this is working, google 'trie' or watch this lecture: https://www.youtube.com/watch?v=uhAUk63tLRM
|
|
// this method is used by the BSD's kernel also
|
|
Branch struct {
|
|
part string
|
|
BranchCase BranchCase
|
|
hasWildNode bool
|
|
tokens string
|
|
nodes []*Branch
|
|
middleware Middleware
|
|
precedence uint64
|
|
paramsLen uint8
|
|
}
|
|
)
|
|
|
|
var _ IBranch = &Branch{}
|
|
|
|
// Get returns a value from a key inside this Parameters
|
|
// If no parameter with this key given then it returns an empty string
|
|
func (params PathParameters) Get(key string) string {
|
|
for _, p := range params {
|
|
if p.Key == key {
|
|
return p.Value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// String returns a string implementation of all parameters that this PathParameters object keeps
|
|
// hasthe form of key1=value1,key2=value2...
|
|
func (params PathParameters) String() string {
|
|
var buff bytes.Buffer
|
|
for i := range params {
|
|
buff.WriteString(params[i].Key)
|
|
buff.WriteString("=")
|
|
buff.WriteString(params[i].Value)
|
|
if i < len(params)-1 {
|
|
buff.WriteString(",")
|
|
}
|
|
|
|
}
|
|
return buff.String()
|
|
}
|
|
|
|
// ParseParams receives a string and returns PathParameters (slice of PathParameter)
|
|
// received string must have this form: key1=value1,key2=value2...
|
|
func ParseParams(str string) PathParameters {
|
|
_paramsstr := strings.Split(str, ",")
|
|
if len(_paramsstr) == 0 {
|
|
return nil
|
|
}
|
|
|
|
params := make(PathParameters, 0) // PathParameters{}
|
|
|
|
// for i := 0; i < len(_paramsstr); i++ {
|
|
for i := range _paramsstr {
|
|
idxOfEq := strings.IndexRune(_paramsstr[i], '=')
|
|
if idxOfEq == -1 {
|
|
//error
|
|
return nil
|
|
}
|
|
|
|
key := _paramsstr[i][:idxOfEq]
|
|
val := _paramsstr[i][idxOfEq+1:]
|
|
params = append(params, PathParameter{key, val})
|
|
}
|
|
return params
|
|
}
|
|
|
|
// GetParamsLen returns the parameters length from a given path
|
|
func GetParamsLen(path string) uint8 {
|
|
var n uint
|
|
for i := 0; i < len(path); i++ {
|
|
if path[i] != ':' && path[i] != '*' { // ParameterStartByte & MatchEverythingByte
|
|
continue
|
|
}
|
|
n++
|
|
}
|
|
if n >= 255 {
|
|
return 255
|
|
}
|
|
return uint8(n)
|
|
}
|
|
|
|
// AddBranch adds a branch to the existing branch or to the tree if no branch has the prefix of
|
|
func (b *Branch) AddBranch(path string, middleware Middleware) {
|
|
fullPath := path
|
|
b.precedence++
|
|
numParams := GetParamsLen(path)
|
|
|
|
if len(b.part) > 0 || len(b.nodes) > 0 {
|
|
loop:
|
|
for {
|
|
if numParams > b.paramsLen {
|
|
b.paramsLen = numParams
|
|
}
|
|
|
|
i := 0
|
|
max := utils.FindLower(len(path), len(b.part))
|
|
for i < max && path[i] == b.part[i] {
|
|
i++
|
|
}
|
|
|
|
if i < len(b.part) {
|
|
node := Branch{
|
|
part: b.part[i:],
|
|
hasWildNode: b.hasWildNode,
|
|
tokens: b.tokens,
|
|
nodes: b.nodes,
|
|
middleware: b.middleware,
|
|
precedence: b.precedence - 1,
|
|
}
|
|
|
|
for i := range node.nodes {
|
|
if node.nodes[i].paramsLen > node.paramsLen {
|
|
node.paramsLen = node.nodes[i].paramsLen
|
|
}
|
|
}
|
|
|
|
b.nodes = []*Branch{&node}
|
|
b.tokens = string([]byte{b.part[i]})
|
|
b.part = path[:i]
|
|
b.middleware = nil
|
|
b.hasWildNode = false
|
|
}
|
|
|
|
if i < len(path) {
|
|
path = path[i:]
|
|
|
|
if b.hasWildNode {
|
|
b = b.nodes[0]
|
|
b.precedence++
|
|
|
|
if numParams > b.paramsLen {
|
|
b.paramsLen = numParams
|
|
}
|
|
numParams--
|
|
|
|
if len(path) >= len(b.part) && b.part == path[:len(b.part)] {
|
|
|
|
if len(b.part) >= len(path) || path[len(b.part)] == '/' {
|
|
continue loop
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
c := path[0]
|
|
|
|
if b.BranchCase == hasParams && c == '/' && len(b.nodes) == 1 {
|
|
b = b.nodes[0]
|
|
b.precedence++
|
|
continue loop
|
|
}
|
|
//we need the i here to be re-setting, so use the same i variable as we declare it on line 176
|
|
for i := range b.tokens {
|
|
if c == b.tokens[i] {
|
|
i = b.GivePrecedenceTo(i)
|
|
b = b.nodes[i]
|
|
continue loop
|
|
}
|
|
}
|
|
|
|
if c != ParameterStartByte && c != MatchEverythingByte {
|
|
|
|
b.tokens += string([]byte{c})
|
|
node := &Branch{
|
|
paramsLen: numParams,
|
|
}
|
|
b.nodes = append(b.nodes, node)
|
|
b.GivePrecedenceTo(len(b.tokens) - 1)
|
|
b = node
|
|
}
|
|
b.AddNode(numParams, path, fullPath, middleware)
|
|
return
|
|
|
|
} else if i == len(path) {
|
|
if b.middleware != nil {
|
|
return
|
|
}
|
|
b.middleware = middleware
|
|
}
|
|
return
|
|
}
|
|
} else {
|
|
b.AddNode(numParams, path, fullPath, middleware)
|
|
b.BranchCase = isRoot
|
|
}
|
|
}
|
|
|
|
// AddNode adds a branch as children to other Branch
|
|
func (b *Branch) AddNode(numParams uint8, path string, fullPath string, middleware Middleware) {
|
|
var offset int
|
|
|
|
for i, max := 0, len(path); numParams > 0; i++ {
|
|
c := path[i]
|
|
if c != ParameterStartByte && c != MatchEverythingByte {
|
|
continue
|
|
}
|
|
|
|
end := i + 1
|
|
for end < max && path[end] != '/' {
|
|
switch path[end] {
|
|
case ParameterStartByte, MatchEverythingByte:
|
|
|
|
default:
|
|
end++
|
|
}
|
|
}
|
|
|
|
if len(b.nodes) > 0 {
|
|
return
|
|
}
|
|
|
|
if end-i < 2 {
|
|
return
|
|
}
|
|
|
|
if c == ParameterStartByte {
|
|
|
|
if i > 0 {
|
|
b.part = path[offset:i]
|
|
offset = i
|
|
}
|
|
|
|
child := &Branch{
|
|
BranchCase: hasParams,
|
|
paramsLen: numParams,
|
|
}
|
|
b.nodes = []*Branch{child}
|
|
b.hasWildNode = true
|
|
b = child
|
|
b.precedence++
|
|
numParams--
|
|
|
|
if end < max {
|
|
b.part = path[offset:end]
|
|
offset = end
|
|
|
|
child := &Branch{
|
|
paramsLen: numParams,
|
|
precedence: 1,
|
|
}
|
|
b.nodes = []*Branch{child}
|
|
b = child
|
|
}
|
|
|
|
} else {
|
|
if end != max || numParams > 1 {
|
|
return
|
|
}
|
|
|
|
if len(b.part) > 0 && b.part[len(b.part)-1] == '/' {
|
|
return
|
|
}
|
|
|
|
i--
|
|
if path[i] != '/' {
|
|
return
|
|
}
|
|
|
|
b.part = path[offset:i]
|
|
|
|
child := &Branch{
|
|
hasWildNode: true,
|
|
BranchCase: matchEverything,
|
|
paramsLen: 1,
|
|
}
|
|
b.nodes = []*Branch{child}
|
|
b.tokens = string(path[i])
|
|
b = child
|
|
b.precedence++
|
|
|
|
child = &Branch{
|
|
part: path[i:],
|
|
BranchCase: matchEverything,
|
|
paramsLen: 1,
|
|
middleware: middleware,
|
|
precedence: 1,
|
|
}
|
|
b.nodes = []*Branch{child}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
b.part = path[offset:]
|
|
b.middleware = middleware
|
|
}
|
|
|
|
// GetBranch is used by the Router, it finds and returns the correct branch for a path
|
|
func (b *Branch) GetBranch(path string, _params PathParameters) (middleware Middleware, params PathParameters, mustRedirect bool) {
|
|
params = _params
|
|
loop:
|
|
for {
|
|
if len(path) > len(b.part) {
|
|
if path[:len(b.part)] == b.part {
|
|
path = path[len(b.part):]
|
|
|
|
if !b.hasWildNode {
|
|
c := path[0]
|
|
for i := range b.tokens {
|
|
if c == b.tokens[i] {
|
|
b = b.nodes[i]
|
|
continue loop
|
|
}
|
|
}
|
|
|
|
mustRedirect = (path == Slash && b.middleware != nil)
|
|
return
|
|
}
|
|
|
|
b = b.nodes[0]
|
|
switch b.BranchCase {
|
|
case hasParams:
|
|
|
|
end := 0
|
|
for end < len(path) && path[end] != '/' {
|
|
end++
|
|
}
|
|
|
|
if cap(params) < int(b.paramsLen) {
|
|
params = make(PathParameters, 0, b.paramsLen)
|
|
}
|
|
i := len(params)
|
|
params = params[:i+1]
|
|
params[i].Key = b.part[1:]
|
|
params[i].Value = path[:end]
|
|
|
|
if end < len(path) {
|
|
if len(b.nodes) > 0 {
|
|
path = path[end:]
|
|
b = b.nodes[0]
|
|
continue loop
|
|
}
|
|
|
|
mustRedirect = (len(path) == end+1)
|
|
return
|
|
}
|
|
|
|
if middleware = b.middleware; middleware != nil {
|
|
return
|
|
} else if len(b.nodes) == 1 {
|
|
b = b.nodes[0]
|
|
mustRedirect = (b.part == Slash && b.middleware != nil)
|
|
}
|
|
|
|
return
|
|
|
|
case matchEverything:
|
|
if cap(params) < int(b.paramsLen) {
|
|
params = make(PathParameters, 0, b.paramsLen)
|
|
}
|
|
i := len(params)
|
|
params = params[:i+1]
|
|
params[i].Key = b.part[2:]
|
|
params[i].Value = path
|
|
|
|
middleware = b.middleware
|
|
return
|
|
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
} else if path == b.part {
|
|
if middleware = b.middleware; middleware != nil {
|
|
return
|
|
}
|
|
|
|
if path == Slash && b.hasWildNode && b.BranchCase != isRoot {
|
|
mustRedirect = true
|
|
return
|
|
}
|
|
|
|
for i := range b.tokens {
|
|
if b.tokens[i] == '/' {
|
|
b = b.nodes[i]
|
|
mustRedirect = (len(b.part) == 1 && b.middleware != nil) ||
|
|
(b.BranchCase == matchEverything && b.nodes[0].middleware != nil)
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
mustRedirect = (path == Slash) ||
|
|
(len(b.part) == len(path)+1 && b.part[len(path)] == '/' &&
|
|
path == b.part[:len(b.part)-1] && b.middleware != nil)
|
|
return
|
|
}
|
|
}
|
|
|
|
// GivePrecedenceTo just adds the priority of this branch by an index
|
|
func (b *Branch) GivePrecedenceTo(index int) int {
|
|
b.nodes[index].precedence++
|
|
_precedence := b.nodes[index].precedence
|
|
|
|
newindex := index
|
|
for newindex > 0 && b.nodes[newindex-1].precedence < _precedence {
|
|
tmpN := b.nodes[newindex-1]
|
|
b.nodes[newindex-1] = b.nodes[newindex]
|
|
b.nodes[newindex] = tmpN
|
|
|
|
newindex--
|
|
}
|
|
|
|
if newindex != index {
|
|
b.tokens = b.tokens[:newindex] +
|
|
b.tokens[index:index+1] +
|
|
b.tokens[newindex:index] + b.tokens[index+1:]
|
|
}
|
|
|
|
return newindex
|
|
}
|