pki/vendor/github.com/labstack/echo/v4/router.go

492 lines
13 KiB
Go
Raw Normal View History

2020-11-25 20:36:07 +01:00
package echo
import (
"net/http"
)
type (
// Router is the registry of all registered routes for an `Echo` instance for
// request matching and URL path parameter parsing.
Router struct {
tree *node
routes map[string]*Route
echo *Echo
}
node struct {
2021-04-07 10:58:21 +02:00
kind kind
label byte
prefix string
parent *node
staticChildren children
ppath string
pnames []string
methodHandler *methodHandler
paramChild *node
anyChild *node
2020-11-25 20:36:07 +01:00
}
kind uint8
children []*node
methodHandler struct {
connect HandlerFunc
delete HandlerFunc
get HandlerFunc
head HandlerFunc
options HandlerFunc
patch HandlerFunc
post HandlerFunc
propfind HandlerFunc
put HandlerFunc
trace HandlerFunc
report HandlerFunc
}
)
const (
2021-04-07 10:58:21 +02:00
staticKind kind = iota
paramKind
anyKind
paramLabel = byte(':')
anyLabel = byte('*')
2020-11-25 20:36:07 +01:00
)
// NewRouter returns a new Router instance.
func NewRouter(e *Echo) *Router {
return &Router{
tree: &node{
methodHandler: new(methodHandler),
},
routes: map[string]*Route{},
echo: e,
}
}
// Add registers a new route for method and path with matching handler.
func (r *Router) Add(method, path string, h HandlerFunc) {
// Validate path
if path == "" {
path = "/"
}
if path[0] != '/' {
path = "/" + path
}
pnames := []string{} // Param names
ppath := path // Pristine path
2021-04-07 10:58:21 +02:00
for i, lcpIndex := 0, len(path); i < lcpIndex; i++ {
2020-11-25 20:36:07 +01:00
if path[i] == ':' {
j := i + 1
2021-04-07 10:58:21 +02:00
r.insert(method, path[:i], nil, staticKind, "", nil)
for ; i < lcpIndex && path[i] != '/'; i++ {
2020-11-25 20:36:07 +01:00
}
pnames = append(pnames, path[j:i])
path = path[:j] + path[i:]
2021-04-07 10:58:21 +02:00
i, lcpIndex = j, len(path)
2020-11-25 20:36:07 +01:00
2021-04-07 10:58:21 +02:00
if i == lcpIndex {
r.insert(method, path[:i], h, paramKind, ppath, pnames)
2020-11-25 20:36:07 +01:00
} else {
2021-04-07 10:58:21 +02:00
r.insert(method, path[:i], nil, paramKind, "", nil)
2020-11-25 20:36:07 +01:00
}
} else if path[i] == '*' {
2021-04-07 10:58:21 +02:00
r.insert(method, path[:i], nil, staticKind, "", nil)
2020-11-25 20:36:07 +01:00
pnames = append(pnames, "*")
2021-04-07 10:58:21 +02:00
r.insert(method, path[:i+1], h, anyKind, ppath, pnames)
2020-11-25 20:36:07 +01:00
}
}
2021-04-07 10:58:21 +02:00
r.insert(method, path, h, staticKind, ppath, pnames)
2020-11-25 20:36:07 +01:00
}
func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string) {
// Adjust max param
2021-04-07 10:58:21 +02:00
paramLen := len(pnames)
if *r.echo.maxParam < paramLen {
*r.echo.maxParam = paramLen
2020-11-25 20:36:07 +01:00
}
2021-04-07 10:58:21 +02:00
currentNode := r.tree // Current node as root
if currentNode == nil {
2020-11-25 20:36:07 +01:00
panic("echo: invalid method")
}
search := path
for {
2021-04-07 10:58:21 +02:00
searchLen := len(search)
prefixLen := len(currentNode.prefix)
lcpLen := 0
// LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
max := prefixLen
if searchLen < max {
max = searchLen
2020-11-25 20:36:07 +01:00
}
2021-04-07 10:58:21 +02:00
for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {
2020-11-25 20:36:07 +01:00
}
2021-04-07 10:58:21 +02:00
if lcpLen == 0 {
2020-11-25 20:36:07 +01:00
// At root node
2021-04-07 10:58:21 +02:00
currentNode.label = search[0]
currentNode.prefix = search
2020-11-25 20:36:07 +01:00
if h != nil {
2021-04-07 10:58:21 +02:00
currentNode.kind = t
currentNode.addHandler(method, h)
currentNode.ppath = ppath
currentNode.pnames = pnames
2020-11-25 20:36:07 +01:00
}
2021-04-07 10:58:21 +02:00
} else if lcpLen < prefixLen {
2020-11-25 20:36:07 +01:00
// Split node
2021-04-07 10:58:21 +02:00
n := newNode(
currentNode.kind,
currentNode.prefix[lcpLen:],
currentNode,
currentNode.staticChildren,
currentNode.methodHandler,
currentNode.ppath,
currentNode.pnames,
currentNode.paramChild,
currentNode.anyChild,
)
2020-11-25 20:36:07 +01:00
// Update parent path for all children to new node
2021-04-07 10:58:21 +02:00
for _, child := range currentNode.staticChildren {
2020-11-25 20:36:07 +01:00
child.parent = n
}
2021-04-07 10:58:21 +02:00
if currentNode.paramChild != nil {
currentNode.paramChild.parent = n
}
if currentNode.anyChild != nil {
currentNode.anyChild.parent = n
}
2020-11-25 20:36:07 +01:00
// Reset parent node
2021-04-07 10:58:21 +02:00
currentNode.kind = staticKind
currentNode.label = currentNode.prefix[0]
currentNode.prefix = currentNode.prefix[:lcpLen]
currentNode.staticChildren = nil
currentNode.methodHandler = new(methodHandler)
currentNode.ppath = ""
currentNode.pnames = nil
currentNode.paramChild = nil
currentNode.anyChild = nil
// Only Static children could reach here
currentNode.addStaticChild(n)
if lcpLen == searchLen {
2020-11-25 20:36:07 +01:00
// At parent node
2021-04-07 10:58:21 +02:00
currentNode.kind = t
currentNode.addHandler(method, h)
currentNode.ppath = ppath
currentNode.pnames = pnames
2020-11-25 20:36:07 +01:00
} else {
// Create child node
2021-04-07 10:58:21 +02:00
n = newNode(t, search[lcpLen:], currentNode, nil, new(methodHandler), ppath, pnames, nil, nil)
2020-11-25 20:36:07 +01:00
n.addHandler(method, h)
2021-04-07 10:58:21 +02:00
// Only Static children could reach here
currentNode.addStaticChild(n)
2020-11-25 20:36:07 +01:00
}
2021-04-07 10:58:21 +02:00
} else if lcpLen < searchLen {
search = search[lcpLen:]
c := currentNode.findChildWithLabel(search[0])
2020-11-25 20:36:07 +01:00
if c != nil {
// Go deeper
2021-04-07 10:58:21 +02:00
currentNode = c
2020-11-25 20:36:07 +01:00
continue
}
// Create child node
2021-04-07 10:58:21 +02:00
n := newNode(t, search, currentNode, nil, new(methodHandler), ppath, pnames, nil, nil)
2020-11-25 20:36:07 +01:00
n.addHandler(method, h)
2021-04-07 10:58:21 +02:00
switch t {
case staticKind:
currentNode.addStaticChild(n)
case paramKind:
currentNode.paramChild = n
case anyKind:
currentNode.anyChild = n
}
2020-11-25 20:36:07 +01:00
} else {
// Node already exists
if h != nil {
2021-04-07 10:58:21 +02:00
currentNode.addHandler(method, h)
currentNode.ppath = ppath
if len(currentNode.pnames) == 0 { // Issue #729
currentNode.pnames = pnames
2020-11-25 20:36:07 +01:00
}
}
}
return
}
}
2021-04-07 10:58:21 +02:00
func newNode(t kind, pre string, p *node, sc children, mh *methodHandler, ppath string, pnames []string, paramChildren, anyChildren *node) *node {
2020-11-25 20:36:07 +01:00
return &node{
2021-04-07 10:58:21 +02:00
kind: t,
label: pre[0],
prefix: pre,
parent: p,
staticChildren: sc,
ppath: ppath,
pnames: pnames,
methodHandler: mh,
paramChild: paramChildren,
anyChild: anyChildren,
2020-11-25 20:36:07 +01:00
}
}
2021-04-07 10:58:21 +02:00
func (n *node) addStaticChild(c *node) {
n.staticChildren = append(n.staticChildren, c)
2020-11-25 20:36:07 +01:00
}
2021-04-07 10:58:21 +02:00
func (n *node) findStaticChild(l byte) *node {
for _, c := range n.staticChildren {
if c.label == l {
2020-11-25 20:36:07 +01:00
return c
}
}
return nil
}
func (n *node) findChildWithLabel(l byte) *node {
2021-04-07 10:58:21 +02:00
for _, c := range n.staticChildren {
2020-11-25 20:36:07 +01:00
if c.label == l {
return c
}
}
2021-04-07 10:58:21 +02:00
if l == paramLabel {
return n.paramChild
}
if l == anyLabel {
return n.anyChild
2020-11-25 20:36:07 +01:00
}
return nil
}
func (n *node) addHandler(method string, h HandlerFunc) {
switch method {
case http.MethodConnect:
n.methodHandler.connect = h
case http.MethodDelete:
n.methodHandler.delete = h
case http.MethodGet:
n.methodHandler.get = h
case http.MethodHead:
n.methodHandler.head = h
case http.MethodOptions:
n.methodHandler.options = h
case http.MethodPatch:
n.methodHandler.patch = h
case http.MethodPost:
n.methodHandler.post = h
case PROPFIND:
n.methodHandler.propfind = h
case http.MethodPut:
n.methodHandler.put = h
case http.MethodTrace:
n.methodHandler.trace = h
case REPORT:
n.methodHandler.report = h
}
}
func (n *node) findHandler(method string) HandlerFunc {
switch method {
case http.MethodConnect:
return n.methodHandler.connect
case http.MethodDelete:
return n.methodHandler.delete
case http.MethodGet:
return n.methodHandler.get
case http.MethodHead:
return n.methodHandler.head
case http.MethodOptions:
return n.methodHandler.options
case http.MethodPatch:
return n.methodHandler.patch
case http.MethodPost:
return n.methodHandler.post
case PROPFIND:
return n.methodHandler.propfind
case http.MethodPut:
return n.methodHandler.put
case http.MethodTrace:
return n.methodHandler.trace
case REPORT:
return n.methodHandler.report
default:
return nil
}
}
func (n *node) checkMethodNotAllowed() HandlerFunc {
for _, m := range methods {
if h := n.findHandler(m); h != nil {
return MethodNotAllowedHandler
}
}
return NotFoundHandler
}
// Find lookup a handler registered for method and path. It also parses URL for path
// parameters and load them into context.
//
// For performance:
//
// - Get context from `Echo#AcquireContext()`
// - Reset it `Context#Reset()`
// - Return it `Echo#ReleaseContext()`.
func (r *Router) Find(method, path string, c Context) {
ctx := c.(*context)
ctx.path = path
2021-04-07 10:58:21 +02:00
currentNode := r.tree // Current node as root
2020-11-25 20:36:07 +01:00
var (
2021-04-07 10:58:21 +02:00
// search stores the remaining path to check for match. By each iteration we move from start of path to end of the path
// and search value gets shorter and shorter.
search = path
searchIndex = 0
paramIndex int // Param counter
paramValues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
2020-11-25 20:36:07 +01:00
)
2021-04-07 10:58:21 +02:00
// Backtracking is needed when a dead end (leaf node) is reached in the router tree.
// To backtrack the current node will be changed to the parent node and the next kind for the
// router logic will be returned based on fromKind or kind of the dead end node (static > param > any).
// For example if there is no static node match we should check parent next sibling by kind (param).
// Backtracking itself does not check if there is a next sibling, this is done by the router logic.
backtrackToNextNodeKind := func(fromKind kind) (nextNodeKind kind, valid bool) {
previous := currentNode
currentNode = previous.parent
valid = currentNode != nil
// Next node type by priority
// NOTE: With the current implementation we never backtrack from an `any` route, so `previous.kind` is
// always `static` or `any`
// If this is changed then for any route next kind would be `static` and this statement should be changed
nextNodeKind = previous.kind + 1
if fromKind == staticKind {
// when backtracking is done from static kind block we did not change search so nothing to restore
return
2020-11-25 20:36:07 +01:00
}
2021-04-07 10:58:21 +02:00
// restore search to value it was before we move to current node we are backtracking from.
if previous.kind == staticKind {
searchIndex -= len(previous.prefix)
} else {
paramIndex--
// for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue
// for that index as it would also contain part of path we cut off before moving into node we are backtracking from
searchIndex -= len(paramValues[paramIndex])
}
search = path[searchIndex:]
return
}
2020-11-25 20:36:07 +01:00
2021-04-07 10:58:21 +02:00
// Router tree is implemented by longest common prefix array (LCP array) https://en.wikipedia.org/wiki/LCP_array
// Tree search is implemented as for loop where one loop iteration is divided into 3 separate blocks
// Each of these blocks checks specific kind of node (static/param/any). Order of blocks reflex their priority in routing.
// Search order/priority is: static > param > any.
//
// Note: backtracking in tree is implemented by replacing/switching currentNode to previous node
// and hoping to (goto statement) next block by priority to check if it is the match.
for {
prefixLen := 0 // Prefix length
lcpLen := 0 // LCP (longest common prefix) length
if currentNode.kind == staticKind {
searchLen := len(search)
prefixLen = len(currentNode.prefix)
2020-11-25 20:36:07 +01:00
2021-04-07 10:58:21 +02:00
// LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
max := prefixLen
if searchLen < max {
max = searchLen
2020-11-25 20:36:07 +01:00
}
2021-04-07 10:58:21 +02:00
for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {
2020-11-25 20:36:07 +01:00
}
}
2021-04-07 10:58:21 +02:00
if lcpLen != prefixLen {
// No matching prefix, let's backtrack to the first possible alternative node of the decision path
nk, ok := backtrackToNextNodeKind(staticKind)
if !ok {
return // No other possibilities on the decision path
} else if nk == paramKind {
goto Param
// NOTE: this case (backtracking from static node to previous any node) can not happen by current any matching logic. Any node is end of search currently
//} else if nk == anyKind {
// goto Any
} else {
// Not found (this should never be possible for static node we are looking currently)
return
2020-11-25 20:36:07 +01:00
}
}
2021-04-07 10:58:21 +02:00
// The full prefix has matched, remove the prefix from the remaining search
search = search[lcpLen:]
searchIndex = searchIndex + lcpLen
// Finish routing if no remaining search and we are on an leaf node
if search == "" && currentNode.ppath != "" {
break
2020-11-25 20:36:07 +01:00
}
// Static node
2021-04-07 10:58:21 +02:00
if search != "" {
if child := currentNode.findStaticChild(search[0]); child != nil {
currentNode = child
continue
2020-11-25 20:36:07 +01:00
}
}
Param:
// Param node
2021-04-07 10:58:21 +02:00
if child := currentNode.paramChild; search != "" && child != nil {
currentNode = child
// FIXME: when param node does not have any children then param node should act similarly to any node - consider all remaining search as match
2020-11-25 20:36:07 +01:00
i, l := 0, len(search)
for ; i < l && search[i] != '/'; i++ {
}
2021-04-07 10:58:21 +02:00
paramValues[paramIndex] = search[:i]
paramIndex++
2020-11-25 20:36:07 +01:00
search = search[i:]
2021-04-07 10:58:21 +02:00
searchIndex = searchIndex + i
2020-11-25 20:36:07 +01:00
continue
}
Any:
// Any node
2021-04-07 10:58:21 +02:00
if child := currentNode.anyChild; child != nil {
// If any node is found, use remaining path for paramValues
currentNode = child
paramValues[len(currentNode.pnames)-1] = search
2020-11-25 20:36:07 +01:00
break
}
2021-04-07 10:58:21 +02:00
// Let's backtrack to the first possible alternative node of the decision path
nk, ok := backtrackToNextNodeKind(anyKind)
if !ok {
return // No other possibilities on the decision path
} else if nk == paramKind {
goto Param
} else if nk == anyKind {
goto Any
} else {
// Not found
return
2020-11-25 20:36:07 +01:00
}
}
2021-04-07 10:58:21 +02:00
ctx.handler = currentNode.findHandler(method)
ctx.path = currentNode.ppath
ctx.pnames = currentNode.pnames
2020-11-25 20:36:07 +01:00
if ctx.handler == nil {
2021-04-07 10:58:21 +02:00
ctx.handler = currentNode.checkMethodNotAllowed()
2020-11-25 20:36:07 +01:00
}
return
}