1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-24 20:14:31 +02:00

Enhanced router priority

Signed-off-by: Vishal Rana <vr@labstack.com>
This commit is contained in:
Vishal Rana 2015-05-09 22:06:13 -07:00
parent b416efc71c
commit c58ec742a9
4 changed files with 200 additions and 231 deletions

View File

@ -3,8 +3,7 @@ Echo is a fast HTTP router (zero memory allocation) and micro web framework in G
## Features ## Features
- Fast :rocket: HTTP router which smartly resolves conflicting routes. - Fast :rocket: HTTP router which smartly prioritize routes.
- Fast router which smartly resolves conflicting routes.
- Extensible middleware/handler, supports: - Extensible middleware/handler, supports:
- Middleware - Middleware
- `func(*echo.Context)` - `func(*echo.Context)`

View File

@ -204,8 +204,14 @@ func lcp(a, b string) (i int) {
func (r *router) Find(method, path string, ctx *Context) (h HandlerFunc, echo *Echo) { func (r *router) Find(method, path string, ctx *Context) (h HandlerFunc, echo *Echo) {
cn := r.trees[method] // Current node as root cn := r.trees[method] // Current node as root
search := path search := path
c := new(node) // Child node
n := 0 // Param counter var (
c *node // Child node
n int // Param counter
nt ntype // Next type
nn *node // Next node
ns string // Next search
)
// TODO: Check empty path??? // TODO: Check empty path???
@ -219,14 +225,28 @@ func (r *router) Find(method, path string, ctx *Context) (h HandlerFunc, echo *E
return return
} }
pl := len(cn.prefix) pl := 0 // Prefix length
l := lcp(search, cn.prefix) l := 0 // LCP length
if cn.label != ':' {
pl = len(cn.prefix)
l = lcp(search, cn.prefix)
}
if l == pl { if l == pl {
// Continue search // Continue search
search = search[l:] search = search[l:]
} else if l < pl && cn.label != ':' { } else {
goto Up cn = nn
search = ns
if nt == ptype {
goto Param
} else if nt == mtype {
goto MatchAny
} else {
// Not found
return
}
} }
if search == "" { if search == "" {
@ -241,6 +261,12 @@ func (r *router) Find(method, path string, ctx *Context) (h HandlerFunc, echo *E
// Static node // Static node
c = cn.findSchild(search[0]) c = cn.findSchild(search[0])
if c != nil { if c != nil {
// Save next
if cn.label == '/' {
nt = ptype
nn = cn
ns = search
}
cn = c cn = c
continue continue
} }
@ -249,6 +275,12 @@ func (r *router) Find(method, path string, ctx *Context) (h HandlerFunc, echo *E
Param: Param:
c = cn.findPchild() c = cn.findPchild()
if c != nil { if c != nil {
// Save next
if cn.label == '/' {
nt = mtype
nn = cn
ns = search
}
cn = c cn = c
i, l := 0, len(search) i, l := 0, len(search)
for ; i < l && search[i] != '/'; i++ { for ; i < l && search[i] != '/'; i++ {
@ -266,22 +298,10 @@ func (r *router) Find(method, path string, ctx *Context) (h HandlerFunc, echo *E
cn = c cn = c
ctx.pvalues[n] = search ctx.pvalues[n] = search
search = "" // End search search = "" // End search
continue } else {
}
Up:
tn := cn // Save current node
cn = cn.parent
if cn == nil {
// Not found // Not found
return return
} }
// Search upwards
if l == pl {
// Reset search
search = tn.prefix + search
}
goto Param
} }
} }

View File

@ -14,7 +14,7 @@ type route struct {
} }
var ( var (
context = &Context{pvalues: make([]string, 5)} context = NewContext(nil, nil, New())
api = []route{ api = []route{
// OAuth Authorizations // OAuth Authorizations
{"GET", "/authorizations"}, {"GET", "/authorizations"},
@ -289,11 +289,12 @@ func TestRouterStatic(t *testing.T) {
}, nil) }, nil)
h, _ := r.Find(GET, path, context) h, _ := r.Find(GET, path, context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
h(nil) h(nil)
if b.String() != path { if b.String() != path {
t.Errorf("buffer should %s", path) t.Errorf("buffer should %s", path)
}
} }
} }
@ -304,10 +305,11 @@ func TestRouterParam(t *testing.T) {
}, nil) }, nil)
h, _ := r.Find(GET, "/users/1", context) h, _ := r.Find(GET, "/users/1", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
if context.pvalues[0] != "1" { if context.P(0) != "1" {
t.Error("param id should be 1") t.Error("param id should be 1")
}
} }
} }
@ -319,13 +321,14 @@ func TestRouterTwoParam(t *testing.T) {
h, _ := r.Find(GET, "/users/1/files/1", context) h, _ := r.Find(GET, "/users/1/files/1", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
if context.pvalues[0] != "1" { if context.P(0) != "1" {
t.Error("param uid should be 1") t.Error("param uid should be 1")
} }
if context.pvalues[1] != "1" { if context.P(1) != "1" {
t.Error("param fid should be 1") t.Error("param fid should be 1")
}
} }
h, _ = r.Find(GET, "/users/1", context) h, _ = r.Find(GET, "/users/1", context)
@ -342,18 +345,20 @@ func TestRouterMatchAny(t *testing.T) {
h, _ := r.Find(GET, "/users/", context) h, _ := r.Find(GET, "/users/", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("should match empty value")
} } else {
if context.pvalues[0] != "" { if context.P(0) != "" {
t.Error("value should be empty") t.Error("value should be empty")
}
} }
h, _ = r.Find(GET, "/users/joe", context) h, _ = r.Find(GET, "/users/joe", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("should match non-empty value")
} } else {
if context.pvalues[0] != "joe" { if context.P(0) != "joe" {
t.Error("value should be joe") t.Error("value should be joe")
}
} }
} }
@ -364,16 +369,17 @@ func TestRouterMicroParam(t *testing.T) {
}, nil) }, nil)
h, _ := r.Find(GET, "/1/2/3", context) h, _ := r.Find(GET, "/1/2/3", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
if context.pvalues[0] != "1" { if context.P(0) != "1" {
t.Error("param a should be 1") t.Error("param a should be 1")
} }
if context.pvalues[1] != "2" { if context.P(1) != "2" {
t.Error("param b should be 2") t.Error("param b should be 2")
} }
if context.pvalues[2] != "3" { if context.P(2) != "3" {
t.Error("param c should be 3") t.Error("param c should be 3")
}
} }
} }
@ -393,140 +399,143 @@ func TestRouterMultiRoute(t *testing.T) {
// Route > /users // Route > /users
h, _ := r.Find(GET, "/users", context) h, _ := r.Find(GET, "/users", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
h(nil) h(nil)
if b.String() != "/users" { if b.String() != "/users" {
t.Errorf("buffer should be /users") t.Errorf("buffer should be /users")
}
} }
// Route > /users/:id > /users/1 // Route > /users/:id
h, _ = r.Find(GET, "/users/1", context) h, _ = r.Find(GET, "/users/1", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
if context.pvalues[0] != "1" { if context.P(0) != "1" {
t.Error("param id should be 1") t.Error("param id should be 1")
}
} }
// Route > /user // Route > /user
h, _ = r.Find(GET, "/user", context) h, _ = r.Find(GET, "/user", context)
if h != nil { if h != nil {
t.Fatal("handler should be nil") t.Error("handler should be nil")
} }
} }
func TestRouterConflictingRoute(t *testing.T) { func TestRouterPriority(t *testing.T) {
r := New().Router r := New().Router
b := new(bytes.Buffer)
// Routes // Routes
r.Add(GET, "/users", func(*Context) *HTTPError { r.Add(GET, "/users", func(c *Context) *HTTPError {
b.WriteString("/users") c.Set("a", 1)
return nil return nil
}, nil) }, nil)
r.Add(GET, "/users/new", func(*Context) *HTTPError { r.Add(GET, "/users/new", func(c *Context) *HTTPError {
b.Reset() c.Set("b", 2)
b.WriteString("/users/new")
return nil return nil
}, nil) }, nil)
r.Add(GET, "/users/:id", func(c *Context) *HTTPError { r.Add(GET, "/users/:id", func(c *Context) *HTTPError {
c.Set("c", 3)
return nil return nil
}, nil) }, nil)
r.Add(GET, "/users/new/moon", func(*Context) *HTTPError { r.Add(GET, "/users/dew", func(c *Context) *HTTPError {
b.Reset() c.Set("d", 4)
b.WriteString("/users/new/moon")
return nil return nil
}, nil) }, nil)
r.Add(GET, "/users/new/:id", func(*Context) *HTTPError { r.Add(GET, "/users/:id/files", func(c *Context) *HTTPError {
c.Set("e", 5)
return nil
}, nil)
r.Add(GET, "/users/newsee", func(c *Context) *HTTPError {
c.Set("f", 6)
return nil
}, nil)
r.Add(GET, "/users/*", func(c *Context) *HTTPError {
c.Set("g", 7)
return nil return nil
}, nil) }, nil)
// Route > /users // Route > /users
h, _ := r.Find(GET, "/users", context) h, _ := r.Find(GET, "/users", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} else {
h(context)
if context.Get("a") != 1 {
t.Error("a should map to 1")
}
} }
// Route > /users/new // Route > /users/new
h, _ = r.Find(GET, "/users/new", context) h, _ = r.Find(GET, "/users/new", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
h(nil) h(context)
if b.String() != "/users/new" { if context.Get("b") != 2 {
t.Error("buffer should be /users/new") t.Error("b should map to 2")
}
} }
// Route > /users/:id > /users/1 // Route > /users/:id
h, _ = r.Find(GET, "/users/1", context) h, _ = r.Find(GET, "/users/1", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
if context.pvalues[0] != "1" { h(context)
t.Error("param id should be 1") if context.Get("c") != 3 {
t.Error("c should map to 3")
}
} }
// Route > /users/:id > /users/nil // Route > /users/dew
h, _ = r.Find(GET, "/users/nil", context) h, _ = r.Find(GET, "/users/dew", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
if context.pvalues[0] != "nil" { h(context)
t.Error("param id should be nil") if context.Get("d") != 4 {
t.Error("d should map to 4")
}
} }
// Route > /users/:id > /users/news // Route > /users/:id/files
h, _ = r.Find(GET, "/users/1/files", context)
if h == nil {
t.Error("handler not found")
} else {
h(context)
if context.Get("e") != 5 {
t.Error("e should map to 5")
}
}
// Route > /users/:id
h, _ = r.Find(GET, "/users/news", context) h, _ = r.Find(GET, "/users/news", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
if context.pvalues[0] != "news" { h(context)
t.Error("param id should be news") if context.Get("c") != 3 {
t.Error("c should map to 3")
}
} }
//----------- // Route > /users/*
// Two level h, _ = r.Find(GET, "/users/joe/books", context)
//-----------
// Route > /users/new/moon > /users/new/moon
h, _ = r.Find(GET, "/users/new/moon", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
h(nil) h(context)
if b.String() != "/users/new/moon" { if context.Get("g") != 7 {
t.Error("buffer should be /users/new/moon") t.Error("g should map to 7")
} }
// Route > /users/new/:id > /users/new/1
h, _ = r.Find(GET, "/users/new/1", context)
if h == nil {
t.Fatal("handler not found")
}
if context.pvalues[0] != "1" {
t.Error("param id should be 1")
}
// Route > /users/new/:id > /users/new/me
h, _ = r.Find(GET, "/users/new/me", context)
if h == nil {
t.Fatal("handler not found")
}
if context.pvalues[0] != "me" {
t.Error("param id should be me")
}
// Route > /users/new/:id > /users/new/moons
h, _ = r.Find(GET, "/users/new/moons", context)
if h == nil {
t.Fatal("handler not found")
}
if context.pvalues[0] != "moons" {
t.Error("param id should be moons")
} }
} }
func TestRouterParamNames(t *testing.T) { func TestRouterParamNames(t *testing.T) {
r := New().Router r := New().Router
b := new(bytes.Buffer) b := new(bytes.Buffer)
@ -546,41 +555,44 @@ func TestRouterParamNames(t *testing.T) {
// Route > /users // Route > /users
h, _ := r.Find(GET, "/users", context) h, _ := r.Find(GET, "/users", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
h(nil) h(context)
if b.String() != "/users" { if b.String() != "/users" {
t.Errorf("buffer should be /users") t.Errorf("buffer should be /users")
}
} }
// Route > /users/:id > /users/1 // Route > /users/:id
h, _ = r.Find(GET, "/users/1", context) h, _ = r.Find(GET, "/users/1", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
if context.pnames[0] != "id" { if context.pnames[0] != "id" {
t.Error("param name should be id") t.Error("param name should be id")
} }
if context.pvalues[0] != "1" { if context.P(0) != "1" {
t.Error("param id should be 1") t.Error("param id should be 1")
}
} }
// Route > /users/:uid/files/:fid > /users/1/files/1 // Route > /users/:uid/files/:fid
h, _ = r.Find(GET, "/users/1/files/1", context) h, _ = r.Find(GET, "/users/1/files/1", context)
if h == nil { if h == nil {
t.Fatal("handler not found") t.Error("handler not found")
} } else {
if context.pnames[0] != "uid" { if context.pnames[0] != "uid" {
t.Error("param name should be id") t.Error("param name should be id")
} }
if context.pvalues[0] != "1" { if context.P(0) != "1" {
t.Error("param id should be 1") t.Error("param id should be 1")
} }
if context.pnames[1] != "fid" { if context.pnames[1] != "fid" {
t.Error("param name should be id") t.Error("param name should be id")
} }
if context.pvalues[1] != "1" { if context.P(1) != "1" {
t.Error("param id should be 1") t.Error("param id should be 1")
}
} }
} }
@ -590,7 +602,7 @@ func TestRouterAPI(t *testing.T) {
r.Add(route.method, route.path, func(c *Context) *HTTPError { r.Add(route.method, route.path, func(c *Context) *HTTPError {
for i, n := range c.pnames { for i, n := range c.pnames {
if n != "" { if n != "" {
if ":"+n != c.pvalues[i] { if ":"+n != c.P(uint8(i)) {
t.Errorf("param not found, method=%s, path=%s", route.method, route.path) t.Errorf("param not found, method=%s, path=%s", route.method, route.path)
} }
} }
@ -600,8 +612,9 @@ func TestRouterAPI(t *testing.T) {
h, _ := r.Find(route.method, route.path, context) h, _ := r.Find(route.method, route.path, context)
if h == nil { if h == nil {
t.Fatalf("handler not found, method=%s, path=%s", route.method, route.path) t.Fatalf("handler not found, method=%s, path=%s", route.method, route.path)
} else {
h(context)
} }
h(context)
} }
} }
@ -622,69 +635,6 @@ func TestRouterServeHTTP(t *testing.T) {
r.ServeHTTP(w, req) r.ServeHTTP(w, req)
} }
func TestRouterExperiment(t *testing.T) {
r := New().Router
r.Add(GET, "/use", func(*Context) *HTTPError {
return nil
}, nil)
r.Add(GET, "/users/*", func(*Context) *HTTPError {
return nil
}, nil)
r.Add(GET, "/users/", func(*Context) *HTTPError {
return nil
}, nil)
// r.Add(GET, "/use", func(*Context) error {
// return nil
// }, nil)
// r.Add(GET, "/users/*", func(*Context) error {
// return nil
// }, nil)
// r.Add(GET, "/users/", func(*Context) error {
// return nil
// }, nil)
// r.Add(GET, "/users/new/*", func(*Context) error {
// return nil
// }, nil)
// r.Add(GET, "/users/new", func(*Context) error {
// return nil
// }, nil)
// r.Add(GET, "/users/:uid", func(*Context) error {
// return nil
// }, nil)
// r.Add(GET, "/users/new/:id", func(*Context) error {
// return nil
// }, nil)
// r.Add(GET, "/users/wen", func(*Context) error {
// return nil
// }, nil)
// r.Add(GET, "/users/:uid/files/:fid", func(*Context) error {
// return nil
// }, nil)
r.Add(GET, "/users/new", func(*Context) *HTTPError {
return nil
}, nil)
r.Add(GET, "/users/:uid", func(*Context) *HTTPError {
return nil
}, nil)
r.Add(GET, "/users/new/:id", func(*Context) *HTTPError {
return nil
}, nil)
r.Add(GET, "/users/wen", func(*Context) *HTTPError {
return nil
}, nil)
r.Add(GET, "/users/:uid/files/:fid", func(*Context) *HTTPError {
return nil
}, nil)
r.trees[GET].printTree("", true)
h, _ := r.Find(GET, "/users/new", context)
if h == nil {
t.Fatal("handler not found")
}
}
func (n *node) printTree(pfx string, tail bool) { func (n *node) printTree(pfx string, tail bool) {
p := prefix(tail, pfx, "└── ", "├── ") p := prefix(tail, pfx, "└── ", "├── ")
fmt.Printf("%s%s, %p: type=%d, parent=%p, handler=%v\n", p, n.prefix, n, n.typ, n.parent, n.handler) fmt.Printf("%s%s, %p: type=%d, parent=%p, handler=%v\n", p, n.prefix, n, n.typ, n.parent, n.handler)

View File

@ -10,7 +10,7 @@ Echo is a fast HTTP router (zero memory allocation) and micro web framework in G
## Features ## Features
- Fast HTTP router which smartly resolves conflicting routes. - Fast HTTP router which smartly prioritize routes.
- Extensible middleware/handler, supports: - Extensible middleware/handler, supports:
- Middleware - Middleware
- `func(*echo.Context)` - `func(*echo.Context)`