diff --git a/README.md b/README.md index b2483858..77e77b59 100644 --- a/README.md +++ b/README.md @@ -105,13 +105,23 @@ func main() { e.Get("/users", getUsers) e.Get("/users/:id", getUser) - // Sub router - a := e.Sub("/admin") - a.Use(func(c *echo.Context) { - // Security check + //****************// + // Sub router // + //****************// + // Sub - inherits parent middleware + sub := e.Sub("/sub") + sub.Use(func(c *echo.Context) { // Middleware }) - a.Get("", func(c *echo.Context) { - c.String(200, "Welcome to the secured area!") + sub.Get("/home", func(c *echo.Context) { + c.String(200, "Sub route /sub/welcome") + }) + + // Group - doesn't inherit parent middleware + grp := e.Group("/group") + grp.Use(func(c *echo.Context) { // Middleware + }) + grp.Get("/home", func(c *echo.Context) { + c.String(200, "Group route /group/welcome") }) // Start server @@ -125,29 +135,29 @@ Based on [julienschmidt/go-http-routing-benchmark] (https://github.com/vishr/go- > Echo: 43700 ns/op, 0 B/op, 0 allocs/op ``` -BenchmarkAce_GithubAll 20000 69126 ns/op 13792 B/op 167 allocs/op -BenchmarkBear_GithubAll 10000 252699 ns/op 79952 B/op 943 allocs/op -BenchmarkBeego_GithubAll 3000 485692 ns/op 146272 B/op 2092 allocs/op -BenchmarkEcho_GithubAll 30000 43700 ns/op 0 B/op 0 allocs/op -BenchmarkBone_GithubAll 1000 2158467 ns/op 648016 B/op 8119 allocs/op -BenchmarkDenco_GithubAll 20000 83022 ns/op 20224 B/op 167 allocs/op -BenchmarkGin_GithubAll 20000 72317 ns/op 13792 B/op 167 allocs/op -BenchmarkGocraftWeb_GithubAll 5000 381554 ns/op 133280 B/op 1889 allocs/op -BenchmarkGoji_GithubAll 3000 605232 ns/op 56113 B/op 334 allocs/op -BenchmarkGoJsonRest_GithubAll 5000 467810 ns/op 135995 B/op 2940 allocs/op -BenchmarkGoRestful_GithubAll 200 9345441 ns/op 707604 B/op 7558 allocs/op -BenchmarkGorillaMux_GithubAll 200 7043040 ns/op 153136 B/op 1791 allocs/op -BenchmarkHttpRouter_GithubAll 30000 52251 ns/op 13792 B/op 167 allocs/op -BenchmarkHttpTreeMux_GithubAll 10000 145114 ns/op 56112 B/op 334 allocs/op -BenchmarkKocha_GithubAll 10000 145061 ns/op 23304 B/op 843 allocs/op -BenchmarkMacaron_GithubAll 2000 697957 ns/op 224960 B/op 2315 allocs/op -BenchmarkMartini_GithubAll 100 11651997 ns/op 237953 B/op 2686 allocs/op -BenchmarkPat_GithubAll 300 3951799 ns/op 1504101 B/op 32222 allocs/op -BenchmarkRevel_GithubAll 2000 1129370 ns/op 345553 B/op 5918 allocs/op -BenchmarkRivet_GithubAll 10000 246564 ns/op 84272 B/op 1079 allocs/op -BenchmarkTango_GithubAll 500 3544850 ns/op 1338664 B/op 27736 allocs/op -BenchmarkTigerTonic_GithubAll 2000 979370 ns/op 241088 B/op 6052 allocs/op -BenchmarkTraffic_GithubAll 200 7508743 ns/op 2664762 B/op 22390 allocs/op -BenchmarkVulcan_GithubAll 5000 286727 ns/op 19894 B/op 609 allocs/op -BenchmarkZeus_GithubAll 2000 798335 ns/op 300688 B/op 2648 allocs/op +BenchmarkAce_GithubAll 20000 65328 ns/op 13792 B/op 167 allocs/op +BenchmarkBear_GithubAll 10000 241852 ns/op 79952 B/op 943 allocs/op +BenchmarkBeego_GithubAll 3000 458234 ns/op 146272 B/op 2092 allocs/op +BenchmarkBone_GithubAll 1000 1923508 ns/op 648016 B/op 8119 allocs/op +BenchmarkDenco_GithubAll 20000 81294 ns/op 20224 B/op 167 allocs/op +BenchmarkEcho_GithubAll 30000 42728 ns/op 0 B/op 0 allocs/op +BenchmarkGin_GithubAll 20000 69373 ns/op 13792 B/op 167 allocs/op +BenchmarkGocraftWeb_GithubAll 10000 370978 ns/op 133280 B/op 1889 allocs/op +BenchmarkGoji_GithubAll 3000 542766 ns/op 56113 B/op 334 allocs/op +BenchmarkGoJsonRest_GithubAll 5000 452551 ns/op 135995 B/op 2940 allocs/op +BenchmarkGoRestful_GithubAll 200 9500204 ns/op 707604 B/op 7558 allocs/op +BenchmarkGorillaMux_GithubAll 200 6770545 ns/op 153137 B/op 1791 allocs/op +BenchmarkHttpRouter_GithubAll 30000 56097 ns/op 13792 B/op 167 allocs/op +BenchmarkHttpTreeMux_GithubAll 10000 143175 ns/op 56112 B/op 334 allocs/op +BenchmarkKocha_GithubAll 10000 147959 ns/op 23304 B/op 843 allocs/op +BenchmarkMacaron_GithubAll 2000 724650 ns/op 224960 B/op 2315 allocs/op +BenchmarkMartini_GithubAll 100 10926021 ns/op 237953 B/op 2686 allocs/op +BenchmarkPat_GithubAll 300 4525114 ns/op 1504101 B/op 32222 allocs/op +BenchmarkRevel_GithubAll 2000 1172963 ns/op 345553 B/op 5918 allocs/op +BenchmarkRivet_GithubAll 10000 249104 ns/op 84272 B/op 1079 allocs/op +BenchmarkTango_GithubAll 300 4012826 ns/op 1368581 B/op 29157 allocs/op +BenchmarkTigerTonic_GithubAll 2000 975450 ns/op 241088 B/op 6052 allocs/op +BenchmarkTraffic_GithubAll 200 7540377 ns/op 2664762 B/op 22390 allocs/op +BenchmarkVulcan_GithubAll 5000 307241 ns/op 19894 B/op 609 allocs/op +BenchmarkZeus_GithubAll 2000 752907 ns/op 300688 B/op 2648 allocs/op ``` diff --git a/echo.go b/echo.go index 580ff7ef..3e443e14 100644 --- a/echo.go +++ b/echo.go @@ -8,7 +8,6 @@ import ( type ( Echo struct { - id uint8 Router *router prefix string middleware []MiddlewareFunc @@ -45,8 +44,6 @@ const ( ) var ( - subs = [128]*Echo{} // Sub routers - methods = [...]string{ MethodCONNECT, MethodDELETE, @@ -80,7 +77,7 @@ func New() (e *Echo) { Response: &response{}, params: make(Params, e.maxParam), store: make(store), - echo: e, // TODO: Do we need it? + echo: e, // TODO: Do we need this? } } return @@ -90,13 +87,20 @@ func New() (e *Echo) { func (h HandlerFunc) ServeHTTP(http.ResponseWriter, *http.Request) { } -// Sub creates a new sub router, inherits all properties from the parent router -// including middleware. +// Sub creates a new sub router. It inherits all properties from the parent +// router, including middleware. func (e *Echo) Sub(pfx string) *Echo { s := *e - s.id++ s.prefix = pfx - subs[s.id] = &s + return &s +} + +// Group is simmilar to Sub but excludes inheriting middleware from the parent +// router. +func (e *Echo) Group(pfx string) *Echo { + s := *e + s.prefix = pfx + s.middleware = nil return &s } @@ -174,7 +178,7 @@ func (e *Echo) Trace(path string, h Handler) { } func (e *Echo) add(method, path string, h Handler) { - e.Router.Add(method, e.prefix+path, wrapH(h), e.id) + e.Router.Add(method, e.prefix+path, wrapH(h), e) } // Static serves static files. @@ -198,14 +202,10 @@ func (e *Echo) Index(file string) { } func (e *Echo) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - h, c, eid := e.Router.Find(r.Method, r.URL.Path) + h, c, e := e.Router.Find(r.Method, r.URL.Path) if h == nil { h = e.notFoundHandler } - if eid != 0 { - // It's a sub router - e = subs[eid] - } c.reset(rw, r, e) // Middleware for i := len(e.middleware) - 1; i >= 0; i-- { diff --git a/echo_test.go b/echo_test.go index fe9c2b71..7b35b524 100644 --- a/echo_test.go +++ b/echo_test.go @@ -145,7 +145,7 @@ func TestEchoHandler(t *testing.T) { } } -func TestEchoSub(t *testing.T) { +func TestEchoSubGroup(t *testing.T) { b := new(bytes.Buffer) e := New() @@ -158,7 +158,13 @@ func TestEchoSub(t *testing.T) { s.Use(func(*Context) { b.WriteString("2") }) - s.Get("", func(*Context) {}) + s.Get("/home", func(*Context) {}) + + g := e.Group("/group") + g.Use(func(*Context) { + b.WriteString("3") + }) + g.Get("/home", func(*Context) {}) w := httptest.NewRecorder() r, _ := http.NewRequest(MethodGET, "/users", nil) @@ -169,11 +175,19 @@ func TestEchoSub(t *testing.T) { b.Reset() w = httptest.NewRecorder() - r, _ = http.NewRequest(MethodGET, "/sub", nil) + r, _ = http.NewRequest(MethodGET, "/sub/home", nil) e.ServeHTTP(w, r) if b.String() != "12" { t.Errorf("should execute middleware 1 & 2, executed %s", b.String()) } + + b.Reset() + w = httptest.NewRecorder() + r, _ = http.NewRequest(MethodGET, "/group/home", nil) + e.ServeHTTP(w, r) + if b.String() != "3" { + t.Errorf("should execute middleware 3, executed %s", b.String()) + } } func TestEchoMethod(t *testing.T) { diff --git a/example/main.go b/example/main.go index eb787410..58e47468 100644 --- a/example/main.go +++ b/example/main.go @@ -76,13 +76,23 @@ func main() { e.Get("/users", getUsers) e.Get("/users/:id", getUser) - // Sub router - a := e.Sub("/admin") - a.Use(func(c *echo.Context) { - // Security check + //****************// + // Sub router // + //****************// + // Sub - inherits parent middleware + sub := e.Sub("/sub") + sub.Use(func(c *echo.Context) { // Middleware }) - a.Get("", func(c *echo.Context) { - c.String(200, "Welcome to the secured area!") + sub.Get("/home", func(c *echo.Context) { + c.String(200, "Sub route /sub/welcome") + }) + + // Group - doesn't inherit parent middleware + grp := e.Group("/group") + grp.Use(func(c *echo.Context) { // Middleware + }) + grp.Get("/home", func(c *echo.Context) { + c.String(200, "Group route /group/welcome") }) // Start server diff --git a/router.go b/router.go index 1e478bff..bc7c0666 100644 --- a/router.go +++ b/router.go @@ -12,7 +12,7 @@ type ( prefix string has ntype // Type of node it contains handler HandlerFunc - eid uint8 // Echo id + echo *Echo edges edges } edges []*node @@ -44,27 +44,27 @@ func NewRouter(e *Echo) (r *router) { return } -func (r *router) Add(method, path string, h HandlerFunc, eid uint8) { +func (r *router) Add(method, path string, h HandlerFunc, echo *Echo) { i := 0 l := len(path) for ; i < l; i++ { if path[i] == ':' { - r.insert(method, path[:i], nil, eid, pnode) + r.insert(method, path[:i], nil, echo, pnode) for ; i < l && path[i] != '/'; i++ { } if i == l { - r.insert(method, path[:i], h, eid, snode) + r.insert(method, path[:i], h, echo, snode) return } - r.insert(method, path[:i], nil, eid, snode) + r.insert(method, path[:i], nil, echo, snode) } else if path[i] == '*' { - r.insert(method, path[:i], h, eid, anode) + r.insert(method, path[:i], h, echo, anode) } } - r.insert(method, path, h, eid, snode) + r.insert(method, path, h, echo, snode) } -func (r *router) insert(method, path string, h HandlerFunc, eid uint8, has ntype) { +func (r *router) insert(method, path string, h HandlerFunc, echo *Echo, has ntype) { cn := r.trees[method] // Current node as root search := path @@ -80,12 +80,12 @@ func (r *router) insert(method, path string, h HandlerFunc, eid uint8, has ntype cn.has = has if h != nil { cn.handler = h - cn.eid = eid + cn.echo = echo } return } else if l < pl { // Split the node - n := newNode(cn.prefix[l:], cn.has, cn.handler, cn.eid, cn.edges) + n := newNode(cn.prefix[l:], cn.has, cn.handler, cn.echo, cn.edges) cn.edges = edges{n} // Add to parent // Reset parent node @@ -93,15 +93,15 @@ func (r *router) insert(method, path string, h HandlerFunc, eid uint8, has ntype cn.prefix = cn.prefix[:l] cn.has = snode cn.handler = nil - cn.eid = 0 + cn.echo = nil if l == sl { // At parent node cn.handler = h - cn.eid = eid + cn.echo = echo } else { // Need to fork a node - n = newNode(search[l:], has, h, eid, edges{}) + n = newNode(search[l:], has, h, echo, edges{}) cn.edges = append(cn.edges, n) } break @@ -109,7 +109,7 @@ func (r *router) insert(method, path string, h HandlerFunc, eid uint8, has ntype search = search[l:] e := cn.findEdge(search[0]) if e == nil { - n := newNode(search, has, h, eid, edges{}) + n := newNode(search, has, h, echo, edges{}) cn.edges = append(cn.edges, n) break } else { @@ -119,20 +119,20 @@ func (r *router) insert(method, path string, h HandlerFunc, eid uint8, has ntype // Node already exists if h != nil { cn.handler = h - cn.eid = eid + cn.echo = echo } break } } } -func newNode(pfx string, has ntype, h HandlerFunc, eid uint8, e edges) (n *node) { +func newNode(pfx string, has ntype, h HandlerFunc, echo *Echo, e edges) (n *node) { n = &node{ label: pfx[0], prefix: pfx, has: has, handler: h, - eid: eid, + echo: echo, edges: e, } return @@ -159,7 +159,7 @@ func lcp(a, b string) (i int) { return } -func (r *router) Find(method, path string) (h HandlerFunc, c *Context, eid uint8) { +func (r *router) Find(method, path string) (h HandlerFunc, c *Context, echo *Echo) { c = r.echo.pool.Get().(*Context) cn := r.trees[method] // Current node as root search := path @@ -168,7 +168,7 @@ func (r *router) Find(method, path string) (h HandlerFunc, c *Context, eid uint8 for { if search == "" || search == cn.prefix { h = cn.handler - eid = cn.eid + echo = cn.echo return } diff --git a/router_test.go b/router_test.go index 271a615d..82bf3a3a 100644 --- a/router_test.go +++ b/router_test.go @@ -7,7 +7,7 @@ import ( func TestRouterStatic(t *testing.T) { r := New().Router - r.Add(MethodGET, "/folders/files/echo.gif", func(c *Context) {}, 0) + r.Add(MethodGET, "/folders/files/echo.gif", func(c *Context) {}, nil) h, _, _ := r.Find(MethodGET, "/folders/files/echo.gif") if h == nil { t.Fatal("handle not found") @@ -16,7 +16,7 @@ func TestRouterStatic(t *testing.T) { func TestRouterParam(t *testing.T) { r := New().Router - r.Add(MethodGET, "/users/:id", func(c *Context) {}, 0) + r.Add(MethodGET, "/users/:id", func(c *Context) {}, nil) h, c, _ := r.Find(MethodGET, "/users/1") if h == nil { t.Fatal("handle not found") @@ -29,7 +29,7 @@ func TestRouterParam(t *testing.T) { func TestRouterCatchAll(t *testing.T) { r := New().Router - r.Add(MethodGET, "/static/*", func(c *Context) {}, 0) + r.Add(MethodGET, "/static/*", func(c *Context) {}, nil) h, _, _ := r.Find(MethodGET, "/static/*") if h == nil { t.Fatal("handle not found") @@ -38,7 +38,7 @@ func TestRouterCatchAll(t *testing.T) { func TestRouterMicroParam(t *testing.T) { r := New().Router - r.Add(MethodGET, "/:a/:b/:c", func(c *Context) {}, 0) + r.Add(MethodGET, "/:a/:b/:c", func(c *Context) {}, nil) h, c, _ := r.Find(MethodGET, "/a/b/c") if h == nil { t.Fatal("handle not found") @@ -59,7 +59,7 @@ func TestRouterMicroParam(t *testing.T) { func (n *node) printTree(pfx string, tail bool) { p := prefix(tail, pfx, "└── ", "├── ") - fmt.Printf("%s%s has=%d, h=%v, eid=%d\n", p, n.prefix, n.has, n.handler, n.eid) + fmt.Printf("%s%s has=%d, h=%v, eid=%d\n", p, n.prefix, n.has, n.handler, n.echo) nodes := n.edges l := len(nodes)