tango/router.go
Lunny Xiao ca08083d50
Some checks reported errors
continuous-integration/drone/push Build was killed
More tests
2020-12-09 20:53:37 +08:00

556 lines
13 KiB
Go

package tango
import (
"bytes"
"fmt"
"net/http"
"reflect"
"regexp"
"sort"
"strings"
)
// RouteType defines route types
type RouteType byte
// enumerates route types
const (
FuncRoute RouteType = iota + 1 // 1 func ()
FuncHTTPRoute // 2 func (http.ResponseWriter, *http.Request)
FuncReqRoute // 3 func (*http.Request)
FuncResponseRoute // 4 func (http.ResponseWriter)
FuncCtxRoute // 5 func (*tango.Context)
StructRoute // 6 func (st) <Get>()
StructPtrRoute // 7 func (*struct) <Get>()
)
// enumerates all supported HTTP methods
var (
SupportMethods = []string{
"GET",
"POST",
"HEAD",
"DELETE",
"PUT",
"OPTIONS",
"TRACE",
"PATCH",
}
PoolSize = 10
)
// Route defines HTTP route
type Route struct {
raw interface{}
method reflect.Value
handlers []Handler
routeType RouteType
pool *pool
}
// NewRoute returns a route
func NewRoute(v interface{}, t reflect.Type,
method reflect.Value, tp RouteType, handlers []Handler) *Route {
var pool *pool
if tp == StructRoute || tp == StructPtrRoute {
pool = newPool(PoolSize, t)
}
return &Route{
raw: v,
routeType: tp,
method: method,
pool: pool,
handlers: handlers,
}
}
// Raw returns raw data to define route.
func (r *Route) Raw() interface{} {
return r.raw
}
// Method returns finalize execute method.
func (r *Route) Method() reflect.Value {
return r.method
}
// RouteType returns route type.
func (r *Route) RouteType() RouteType {
return r.routeType
}
// IsStruct returns if the execute is a struct
func (r *Route) IsStruct() bool {
return r.routeType == StructRoute || r.routeType == StructPtrRoute
}
func (r *Route) newAction() reflect.Value {
if !r.IsStruct() {
return r.method
}
return r.pool.New()
}
// Router describes the interface of route
type Router interface {
Route(methods interface{}, path string, handler interface{}, middlewares ...Handler)
Match(requestPath, method string) (*Route, Params)
}
var specialBytes = []byte(`.\+*?|[]{}^$`)
func isSpecial(ch byte) bool {
return bytes.IndexByte(specialBytes, ch) > -1
}
func isAlpha(ch byte) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
}
func isDigit(ch byte) bool {
return '0' <= ch && ch <= '9'
}
func isAlnum(ch byte) bool {
return isAlpha(ch) || isDigit(ch)
}
type (
router struct {
trees map[string]*node
}
ntype byte
node struct {
tp ntype // Type of node it contains
handle *Route // executor
regexp *regexp.Regexp // regexp if tp is rnode
content string // static content or named
edges edges // children
path string // executor path
}
edges []*node
)
func (e edges) Len() int { return len(e) }
func (e edges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
// static route will be put the first, so it will be match first.
// two static route, content longer is first.
func (e edges) Less(i, j int) bool {
if e[i].tp == snode {
if e[j].tp == snode {
return len(e[i].content) > len(e[j].content)
}
return true
}
if e[j].tp == snode {
return false
}
return i < j
}
const (
snode ntype = iota // static, should equal
nnode // named node, match a non-/ is ok
anode // catch-all node, match any
rnode // regex node, should match
)
func (n *node) equal(o *node) bool {
if n.tp != o.tp || n.content != o.content {
return false
}
return true
}
// newRouter return a new router
func newRouter() (r *router) {
r = &router{
trees: make(map[string]*node),
}
for _, m := range SupportMethods {
r.trees[m] = &node{
edges: edges{},
}
}
return
}
// /:name1/:name2 /:name1-:name2 /(:name1)sss(:name2)
// /(*name) /(:name[0-9]+) /(:name[a-z]+)
func parseNodes(path string) []*node {
var i, j int
l := len(path)
var nodes = make([]*node, 0)
var bracket int
for ; i < l; i++ {
if path[i] == ':' {
nodes = append(nodes, &node{tp: snode, content: path[j : i-bracket]})
j = i
var regex string
if bracket == 1 {
var start = -1
for ; i < l && ')' != path[i]; i++ {
if start == -1 && isSpecial(path[i]) {
start = i
}
}
if path[i] != ')' {
panic("lack of )")
}
if start > -1 {
regex = path[start:i]
}
} else {
i = i + 1
for ; i < l && isAlnum(path[i]); i++ {
}
}
if len(regex) > 0 {
nodes = append(nodes, &node{tp: rnode,
regexp: regexp.MustCompile("(" + regex + ")"),
content: path[j : i-len(regex)]})
} else {
nodes = append(nodes, &node{tp: nnode, content: path[j:i]})
}
i = i + bracket
j = i
bracket = 0
if i == l {
return nodes
}
} else if path[i] == '*' {
nodes = append(nodes, &node{tp: snode, content: path[j : i-bracket]})
j = i
if bracket == 1 {
for ; i < l && ')' != path[i]; i++ {
}
} else {
i = i + 1
for ; i < l && isAlnum(path[i]); i++ {
}
}
nodes = append(nodes, &node{tp: anode, content: path[j:i]})
i = i + bracket
bracket = 0
j = i
if i == l {
return nodes
}
} else if path[i] == '(' {
bracket = 1
} else if path[i] == '/' {
if bracket == 0 && i > j {
nodes = append(nodes, &node{tp: snode, content: path[j:i]})
j = i
}
} else {
bracket = 0
}
}
nodes = append(nodes, &node{
tp: snode,
content: path[j:i],
})
return nodes
}
func printNode(i int, node *node) {
for _, c := range node.edges {
for j := 0; j < i; j++ {
fmt.Print(" ")
}
if i > 1 {
fmt.Print("┗", " ")
}
fmt.Print(c.content)
if c.handle != nil {
fmt.Print(" ", c.handle.method.Type())
fmt.Printf(" %p", c.handle.method.Interface())
}
fmt.Println()
printNode(i+1, c)
}
}
func (r *router) printTrees() {
for _, method := range SupportMethods {
if len(r.trees[method].edges) > 0 {
fmt.Println(method)
printNode(1, r.trees[method])
fmt.Println()
}
}
}
func (r *router) addRoute(method, path string, h *Route) {
nodes := parseNodes(path)
nodes[len(nodes)-1].handle = h
nodes[len(nodes)-1].path = path
if !validNodes(nodes) {
panic(fmt.Sprintln("express", path, "is not supported"))
}
r.addnodes(method, nodes)
//r.printTrees()
}
func (r *router) matchNode(n *node, url string, params Params) (*node, Params) {
if n.tp == snode {
if strings.HasPrefix(url, n.content) {
if len(url) == len(n.content) {
return n, params
}
for _, c := range n.edges {
e, newParams := r.matchNode(c, url[len(n.content):], params)
if e != nil {
return e, newParams
}
}
}
} else if n.tp == anode {
for _, c := range n.edges {
idx := strings.LastIndex(url, c.content)
if idx > -1 {
params = append(params, param{n.content, url[:idx]})
return r.matchNode(c, url[idx:], params)
}
}
return n, append(params, param{n.content, url})
} else if n.tp == nnode {
for _, c := range n.edges {
idx := strings.Index(url, c.content)
if idx > -1 {
params = append(params, param{n.content, url[:idx]})
return r.matchNode(c, url[idx:], params)
}
}
idx := strings.IndexByte(url, '/')
if idx < 0 {
params = append(params, param{n.content, url})
return n, params
}
} else if n.tp == rnode {
idx := strings.IndexByte(url, '/')
if idx > -1 {
if n.regexp.MatchString(url[:idx]) {
for _, c := range n.edges {
h, newParams := r.matchNode(c, url[idx:], params)
if h != nil {
return h, append([]param{param{n.content, url[:idx]}}, newParams...)
}
}
}
return nil, params
}
for _, c := range n.edges {
idx := strings.Index(url, c.content)
if idx > -1 && n.regexp.MatchString(url[:idx]) {
params = append(params, param{n.content, url[:idx]})
return r.matchNode(c, url[idx:], params)
}
}
if n.regexp.MatchString(url) {
params = append(params, param{n.content, url})
return n, params
}
}
return nil, params
}
// Match for request url, match router
func (r *router) Match(url, method string) (*Route, Params) {
cn, ok := r.trees[method]
if !ok {
return nil, nil
}
var params = make(Params, 0, strings.Count(url, "/"))
for _, n := range cn.edges {
e, newParams := r.matchNode(n, url, params)
if e != nil {
return e.handle, newParams
}
}
return nil, nil
}
// addnode adds node nodes[i] to parent node p
func (r *router) addnode(p *node, nodes []*node, i int) *node {
if len(p.edges) == 0 {
p.edges = make([]*node, 0)
}
for _, pc := range p.edges {
if pc.equal(nodes[i]) {
if i == len(nodes)-1 {
pc.handle = nodes[i].handle
}
return pc
}
}
p.edges = append(p.edges, nodes[i])
sort.Sort(p.edges)
return nodes[i]
}
// validNodes validates parsed nodes, all non-static route should have static route children.
func validNodes(nodes []*node) bool {
if len(nodes) == 0 {
return false
}
var lastTp = nodes[0]
for _, node := range nodes[1:] {
if lastTp.tp != snode && node.tp != snode {
return false
}
lastTp = node
}
return true
}
// addnodes adds nodes to trees
func (r *router) addnodes(method string, nodes []*node) {
cn := r.trees[method]
var p = cn
for i := 0; i < len(nodes); i++ {
p = r.addnode(p, nodes, i)
}
}
func removeStick(uri string) string {
uri = strings.TrimRight(uri, "/")
if uri == "" {
uri = "/"
}
return uri
}
// Route adds route
func (r *router) Route(ms interface{}, url string, c interface{}, handlers ...Handler) {
vc := reflect.ValueOf(c)
if vc.Kind() == reflect.Func {
switch ms.(type) {
case string:
s := strings.Split(ms.(string), ":")
r.addFunc([]string{s[0]}, url, c, handlers)
case []string:
v := ms.([]string)
var newSlice = make([]string, 0, len(v))
for _, m := range v {
s := strings.Split(m, ":")
newSlice = append(newSlice, s[0])
}
r.addFunc(newSlice, url, c, handlers)
default:
panic("unknow methods format")
}
} else if vc.Kind() == reflect.Ptr && vc.Elem().Kind() == reflect.Struct {
if handler, ok := vc.Interface().(http.Handler); ok {
r.Route(ms, url, handler.ServeHTTP, handlers...)
return
}
var methods = make(map[string]string)
switch ms.(type) {
case string:
s := strings.Split(ms.(string), ":")
if len(s) == 1 {
methods[s[0]] = strings.Title(strings.ToLower(s[0]))
} else if len(s) == 2 {
methods[s[0]] = strings.TrimSpace(s[1])
} else {
panic("unknow methods format")
}
case []string:
for _, m := range ms.([]string) {
s := strings.Split(m, ":")
if len(s) == 1 {
methods[s[0]] = strings.Title(strings.ToLower(s[0]))
} else if len(s) == 2 {
methods[s[0]] = strings.TrimSpace(s[1])
} else {
panic("unknow format")
}
}
case map[string]string:
methods = ms.(map[string]string)
default:
panic("unsupported methods")
}
r.addStruct(methods, url, c, handlers)
} else {
panic("not support route type")
}
}
/*
Tango supports 5 form funcs
func()
func(*Context)
func(http.ResponseWriter, *http.Request)
func(http.ResponseWriter)
func(*http.Request)
it can has or has not return value
*/
func (r *router) addFunc(methods []string, url string, c interface{}, handlers []Handler) {
vc := reflect.ValueOf(c)
t := vc.Type()
var rt RouteType
if t.NumIn() == 0 {
rt = FuncRoute
} else if t.NumIn() == 1 {
if t.In(0) == reflect.TypeOf(new(Context)) {
rt = FuncCtxRoute
} else if t.In(0) == reflect.TypeOf(new(http.Request)) {
rt = FuncReqRoute
} else if t.In(0).Kind() == reflect.Interface && t.In(0).Name() == "ResponseWriter" &&
t.In(0).PkgPath() == "net/http" {
rt = FuncResponseRoute
} else {
panic(fmt.Sprintln("no support function type", methods, url, c))
}
} else if t.NumIn() == 2 &&
(t.In(0).Kind() == reflect.Interface && t.In(0).Name() == "ResponseWriter" &&
t.In(0).PkgPath() == "net/http") &&
t.In(1) == reflect.TypeOf(new(http.Request)) {
rt = FuncHTTPRoute
} else {
panic(fmt.Sprintln("no support function type", methods, url, c))
}
url = removeStick(url)
for _, m := range methods {
r.addRoute(m, url, NewRoute(c, t, vc, rt, handlers))
}
}
func (r *router) addStruct(methods map[string]string, url string, c interface{}, handlers []Handler) {
vc := reflect.ValueOf(c)
t := vc.Type().Elem()
// added a default method Get, Post
for name, method := range methods {
if m, ok := t.MethodByName(method); ok {
r.addRoute(name, removeStick(url), NewRoute(c, t, m.Func, StructPtrRoute, handlers))
} else if m, ok := vc.Type().MethodByName(method); ok {
r.addRoute(name, removeStick(url), NewRoute(c, t, m.Func, StructRoute, handlers))
} else if m, ok := t.MethodByName("Any"); ok {
r.addRoute(name, removeStick(url), NewRoute(c, t, m.Func, StructPtrRoute, handlers))
} else if m, ok := vc.Type().MethodByName("Any"); ok {
r.addRoute(name, removeStick(url), NewRoute(c, t, m.Func, StructRoute, handlers))
}
}
}