iris/branch.go
2016-05-30 17:08:09 +03:00

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
}