mirror of
https://github.com/rls-moe/nyx
synced 2024-11-14 22:12:24 +00:00
536 lines
10 KiB
Go
536 lines
10 KiB
Go
|
package chi
|
||
|
|
||
|
// Radix tree implementation below is a based on the original work by
|
||
|
// Armon Dadgar in https://github.com/armon/go-radix/blob/master/radix.go
|
||
|
// (MIT licensed). It's been heavily modified for use as a HTTP routing tree.
|
||
|
|
||
|
import (
|
||
|
"net/http"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
type methodTyp int
|
||
|
|
||
|
const (
|
||
|
mCONNECT methodTyp = 1 << iota
|
||
|
mDELETE
|
||
|
mGET
|
||
|
mHEAD
|
||
|
mOPTIONS
|
||
|
mPATCH
|
||
|
mPOST
|
||
|
mPUT
|
||
|
mTRACE
|
||
|
mSTUB
|
||
|
|
||
|
mALL methodTyp = mCONNECT | mDELETE | mGET | mHEAD | mOPTIONS |
|
||
|
mPATCH | mPOST | mPUT | mTRACE
|
||
|
)
|
||
|
|
||
|
var methodMap = map[string]methodTyp{
|
||
|
"CONNECT": mCONNECT,
|
||
|
"DELETE": mDELETE,
|
||
|
"GET": mGET,
|
||
|
"HEAD": mHEAD,
|
||
|
"OPTIONS": mOPTIONS,
|
||
|
"PATCH": mPATCH,
|
||
|
"POST": mPOST,
|
||
|
"PUT": mPUT,
|
||
|
"TRACE": mTRACE,
|
||
|
}
|
||
|
|
||
|
type nodeTyp uint8
|
||
|
|
||
|
const (
|
||
|
ntStatic nodeTyp = iota // /home
|
||
|
ntRegexp // /:id([0-9]+) or #id^[0-9]+$
|
||
|
ntParam // /:user
|
||
|
ntCatchAll // /api/v1/*
|
||
|
)
|
||
|
|
||
|
type node struct {
|
||
|
// node type
|
||
|
typ nodeTyp
|
||
|
|
||
|
// first byte of the prefix
|
||
|
label byte
|
||
|
|
||
|
// prefix is the common prefix we ignore
|
||
|
prefix string
|
||
|
|
||
|
// pattern is the computed path of prefixes
|
||
|
pattern string
|
||
|
|
||
|
// HTTP handler on the leaf node
|
||
|
handlers methodHandlers
|
||
|
|
||
|
// chi subroutes on the leaf node
|
||
|
subroutes Routes
|
||
|
|
||
|
// Child nodes should be stored in-order for iteration,
|
||
|
// in groups of the node type.
|
||
|
children [ntCatchAll + 1]nodes
|
||
|
}
|
||
|
|
||
|
func (n *node) FindRoute(rctx *Context, path string) methodHandlers {
|
||
|
// Reset the context routing pattern
|
||
|
rctx.RoutePattern = ""
|
||
|
|
||
|
// Find the routing handlers for the path
|
||
|
rn := n.findRoute(rctx, path)
|
||
|
if rn == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Record the routing pattern in the request lifecycle
|
||
|
if rn.pattern != "" {
|
||
|
rctx.RoutePattern = rn.pattern
|
||
|
rctx.RoutePatterns = append(rctx.RoutePatterns, rctx.RoutePattern)
|
||
|
}
|
||
|
|
||
|
return rn.handlers
|
||
|
}
|
||
|
|
||
|
func (n *node) InsertRoute(method methodTyp, pattern string, handler http.Handler) *node {
|
||
|
var parent *node
|
||
|
search := pattern
|
||
|
|
||
|
for {
|
||
|
// Handle key exhaustion
|
||
|
if len(search) == 0 {
|
||
|
// Insert or update the node's leaf handler
|
||
|
n.setHandler(method, handler)
|
||
|
n.pattern = pattern
|
||
|
return n
|
||
|
}
|
||
|
|
||
|
// Look for the edge
|
||
|
parent = n
|
||
|
n = n.getEdge(search[0])
|
||
|
|
||
|
// No edge, create one
|
||
|
if n == nil {
|
||
|
cn := &node{label: search[0], prefix: search, pattern: pattern}
|
||
|
cn.setHandler(method, handler)
|
||
|
parent.addChild(pattern, cn)
|
||
|
return cn
|
||
|
}
|
||
|
|
||
|
if n.typ > ntStatic {
|
||
|
// We found a wildcard node, meaning search path starts with
|
||
|
// a wild prefix. Trim off the wildcard search path and continue.
|
||
|
p := strings.Index(search, "/")
|
||
|
if p < 0 {
|
||
|
p = len(search)
|
||
|
}
|
||
|
search = search[p:]
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Static nodes fall below here.
|
||
|
// Determine longest prefix of the search key on match.
|
||
|
commonPrefix := n.longestPrefix(search, n.prefix)
|
||
|
if commonPrefix == len(n.prefix) {
|
||
|
// the common prefix is as long as the current node's prefix we're attempting to insert.
|
||
|
// keep the search going.
|
||
|
search = search[commonPrefix:]
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Split the node
|
||
|
child := &node{
|
||
|
typ: ntStatic,
|
||
|
prefix: search[:commonPrefix],
|
||
|
}
|
||
|
parent.replaceChild(search[0], child)
|
||
|
|
||
|
// Restore the existing node
|
||
|
n.label = n.prefix[commonPrefix]
|
||
|
n.prefix = n.prefix[commonPrefix:]
|
||
|
child.addChild(pattern, n)
|
||
|
|
||
|
// If the new key is a subset, add to to this node
|
||
|
search = search[commonPrefix:]
|
||
|
if len(search) == 0 {
|
||
|
child.setHandler(method, handler)
|
||
|
child.pattern = pattern
|
||
|
return child
|
||
|
}
|
||
|
|
||
|
// Create a new edge for the node
|
||
|
subchild := &node{
|
||
|
typ: ntStatic,
|
||
|
label: search[0],
|
||
|
prefix: search,
|
||
|
pattern: pattern,
|
||
|
}
|
||
|
subchild.setHandler(method, handler)
|
||
|
child.addChild(pattern, subchild)
|
||
|
return subchild
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (n *node) findPattern(pattern string) *node {
|
||
|
nn := n
|
||
|
for _, nds := range nn.children {
|
||
|
if len(nds) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
n = nn.getEdge(pattern[0])
|
||
|
if n == nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
idx := n.longestPrefix(pattern, n.prefix)
|
||
|
xpattern := pattern[idx:]
|
||
|
|
||
|
if len(xpattern) == 0 {
|
||
|
return n
|
||
|
} else if xpattern[0] == '/' && idx < len(n.prefix) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
return n.findPattern(xpattern)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (n *node) isLeaf() bool {
|
||
|
return n.handlers != nil
|
||
|
}
|
||
|
|
||
|
func (n *node) addChild(pattern string, child *node) {
|
||
|
search := child.prefix
|
||
|
|
||
|
// Find any wildcard segments
|
||
|
p := strings.IndexAny(search, ":*")
|
||
|
|
||
|
// Determine new node type
|
||
|
ntyp := child.typ
|
||
|
if p >= 0 {
|
||
|
switch search[p] {
|
||
|
case ':':
|
||
|
ntyp = ntParam
|
||
|
case '*':
|
||
|
ntyp = ntCatchAll
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if p == 0 {
|
||
|
// Path starts with a wildcard
|
||
|
|
||
|
handlers := child.handlers
|
||
|
child.typ = ntyp
|
||
|
|
||
|
if ntyp == ntCatchAll {
|
||
|
p = -1
|
||
|
} else {
|
||
|
p = strings.IndexByte(search, '/')
|
||
|
}
|
||
|
if p < 0 {
|
||
|
p = len(search)
|
||
|
}
|
||
|
child.prefix = search[:p]
|
||
|
|
||
|
if p != len(search) {
|
||
|
// add edge for the remaining part, split the end.
|
||
|
child.handlers = nil
|
||
|
|
||
|
search = search[p:]
|
||
|
|
||
|
child.addChild(pattern, &node{
|
||
|
typ: ntStatic,
|
||
|
label: search[0], // this will always start with /
|
||
|
prefix: search,
|
||
|
pattern: pattern,
|
||
|
handlers: handlers,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
} else if p > 0 {
|
||
|
// Path has some wildcard
|
||
|
|
||
|
// starts with a static segment
|
||
|
handlers := child.handlers
|
||
|
child.typ = ntStatic
|
||
|
child.prefix = search[:p]
|
||
|
child.handlers = nil
|
||
|
|
||
|
// add the wild edge node
|
||
|
search = search[p:]
|
||
|
|
||
|
child.addChild(pattern, &node{
|
||
|
typ: ntyp,
|
||
|
label: search[0],
|
||
|
prefix: search,
|
||
|
pattern: pattern,
|
||
|
handlers: handlers,
|
||
|
})
|
||
|
|
||
|
} else {
|
||
|
// Path is all static
|
||
|
child.typ = ntyp
|
||
|
|
||
|
}
|
||
|
|
||
|
n.children[child.typ] = append(n.children[child.typ], child)
|
||
|
n.children[child.typ].Sort()
|
||
|
}
|
||
|
|
||
|
func (n *node) replaceChild(label byte, child *node) {
|
||
|
for i := 0; i < len(n.children[child.typ]); i++ {
|
||
|
if n.children[child.typ][i].label == label {
|
||
|
n.children[child.typ][i] = child
|
||
|
n.children[child.typ][i].label = label
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
panic("chi: replacing missing child")
|
||
|
}
|
||
|
|
||
|
func (n *node) getEdge(label byte) *node {
|
||
|
for _, nds := range n.children {
|
||
|
num := len(nds)
|
||
|
for i := 0; i < num; i++ {
|
||
|
if nds[i].label == label {
|
||
|
return nds[i]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (n *node) findEdge(ntyp nodeTyp, label byte) *node {
|
||
|
nds := n.children[ntyp]
|
||
|
num := len(nds)
|
||
|
idx := 0
|
||
|
|
||
|
switch ntyp {
|
||
|
case ntStatic:
|
||
|
i, j := 0, num-1
|
||
|
for i <= j {
|
||
|
idx = i + (j-i)/2
|
||
|
if label > nds[idx].label {
|
||
|
i = idx + 1
|
||
|
} else if label < nds[idx].label {
|
||
|
j = idx - 1
|
||
|
} else {
|
||
|
i = num // breaks cond
|
||
|
}
|
||
|
}
|
||
|
if nds[idx].label != label {
|
||
|
return nil
|
||
|
}
|
||
|
return nds[idx]
|
||
|
|
||
|
default: // wild nodes
|
||
|
// TODO: right now we match them all.. but regexp should
|
||
|
// run through regexp matcher
|
||
|
return nds[idx]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Recursive edge traversal by checking all nodeTyp groups along the way.
|
||
|
// It's like searching through a multi-dimensional radix trie.
|
||
|
func (n *node) findRoute(rctx *Context, path string) *node {
|
||
|
nn := n
|
||
|
search := path
|
||
|
|
||
|
for t, nds := range nn.children {
|
||
|
ntyp := nodeTyp(t)
|
||
|
if len(nds) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// search subset of edges of the index for a matching node
|
||
|
var label byte
|
||
|
if search != "" {
|
||
|
label = search[0]
|
||
|
}
|
||
|
|
||
|
xn := nn.findEdge(ntyp, label) // next node
|
||
|
if xn == nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Prepare next search path by trimming prefix from requested path
|
||
|
xsearch := search
|
||
|
if xn.typ > ntStatic {
|
||
|
p := -1
|
||
|
if xn.typ < ntCatchAll {
|
||
|
p = strings.IndexByte(xsearch, '/')
|
||
|
}
|
||
|
if p < 0 {
|
||
|
p = len(xsearch)
|
||
|
}
|
||
|
|
||
|
if xn.typ == ntCatchAll {
|
||
|
rctx.URLParams.Add("*", xsearch)
|
||
|
} else {
|
||
|
rctx.URLParams.Add(xn.prefix[1:], xsearch[:p])
|
||
|
}
|
||
|
|
||
|
xsearch = xsearch[p:]
|
||
|
} else if strings.HasPrefix(xsearch, xn.prefix) {
|
||
|
xsearch = xsearch[len(xn.prefix):]
|
||
|
} else {
|
||
|
continue // no match
|
||
|
}
|
||
|
|
||
|
// did we find it yet?
|
||
|
if len(xsearch) == 0 {
|
||
|
if xn.isLeaf() {
|
||
|
return xn
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// recursively find the next node..
|
||
|
fin := xn.findRoute(rctx, xsearch)
|
||
|
if fin != nil {
|
||
|
// found a node, return it
|
||
|
return fin
|
||
|
}
|
||
|
|
||
|
// Did not found final handler, let's remove the param here if it was set
|
||
|
if xn.typ > ntStatic {
|
||
|
if xn.typ == ntCatchAll {
|
||
|
rctx.URLParams.Del("*")
|
||
|
} else {
|
||
|
rctx.URLParams.Del(xn.prefix[1:])
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// longestPrefix finds the length of the shared prefix
|
||
|
// of two strings
|
||
|
func (n *node) longestPrefix(k1, k2 string) int {
|
||
|
max := len(k1)
|
||
|
if l := len(k2); l < max {
|
||
|
max = l
|
||
|
}
|
||
|
var i int
|
||
|
for i = 0; i < max; i++ {
|
||
|
if k1[i] != k2[i] {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
return i
|
||
|
}
|
||
|
|
||
|
func (n *node) setHandler(method methodTyp, handler http.Handler) {
|
||
|
if n.handlers == nil {
|
||
|
n.handlers = make(methodHandlers, 0)
|
||
|
}
|
||
|
if method&mSTUB == mSTUB {
|
||
|
n.handlers[mSTUB] = handler
|
||
|
} else {
|
||
|
n.handlers[mSTUB] = nil
|
||
|
}
|
||
|
if method&mALL == mALL {
|
||
|
n.handlers[mALL] = handler
|
||
|
for _, m := range methodMap {
|
||
|
n.handlers[m] = handler
|
||
|
}
|
||
|
} else {
|
||
|
n.handlers[method] = handler
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (n *node) isEmpty() bool {
|
||
|
for _, nds := range n.children {
|
||
|
if len(nds) > 0 {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func (n *node) routes() []Route {
|
||
|
rts := []Route{}
|
||
|
|
||
|
n.walkRoutes(n.prefix, n, func(pattern string, handlers methodHandlers, subroutes Routes) bool {
|
||
|
if handlers[mSTUB] != nil && subroutes == nil {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if subroutes != nil && len(pattern) > 2 {
|
||
|
pattern = pattern[:len(pattern)-2]
|
||
|
}
|
||
|
|
||
|
var hs = make(map[string]http.Handler, 0)
|
||
|
if handlers[mALL] != nil {
|
||
|
hs["*"] = handlers[mALL]
|
||
|
}
|
||
|
for mt, h := range handlers {
|
||
|
if h == nil {
|
||
|
continue
|
||
|
}
|
||
|
m := methodTypString(mt)
|
||
|
if m == "" {
|
||
|
continue
|
||
|
}
|
||
|
hs[m] = h
|
||
|
}
|
||
|
|
||
|
rt := Route{pattern, hs, subroutes}
|
||
|
rts = append(rts, rt)
|
||
|
return false
|
||
|
})
|
||
|
|
||
|
return rts
|
||
|
}
|
||
|
|
||
|
func (n *node) walkRoutes(pattern string, nd *node, fn walkFn) bool {
|
||
|
pattern = nd.pattern
|
||
|
|
||
|
// Visit the leaf values if any
|
||
|
if (nd.handlers != nil || nd.subroutes != nil) && fn(pattern, nd.handlers, nd.subroutes) {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// Recurse on the children
|
||
|
for _, nds := range nd.children {
|
||
|
for _, nd := range nds {
|
||
|
if n.walkRoutes(pattern, nd, fn) {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func methodTypString(method methodTyp) string {
|
||
|
for s, t := range methodMap {
|
||
|
if method == t {
|
||
|
return s
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
type walkFn func(pattern string, handlers methodHandlers, subroutes Routes) bool
|
||
|
|
||
|
// methodHandlers is a mapping of http method constants to handlers
|
||
|
// for a given route.
|
||
|
type methodHandlers map[methodTyp]http.Handler
|
||
|
|
||
|
type nodes []*node
|
||
|
|
||
|
// Sort the list of nodes by label
|
||
|
func (ns nodes) Len() int { return len(ns) }
|
||
|
func (ns nodes) Less(i, j int) bool { return ns[i].label < ns[j].label }
|
||
|
func (ns nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
|
||
|
func (ns nodes) Sort() { sort.Sort(ns) }
|
||
|
|
||
|
type Route struct {
|
||
|
Pattern string
|
||
|
Handlers map[string]http.Handler
|
||
|
SubRoutes Routes
|
||
|
}
|