Skip to content

Commit f1cf1ec

Browse files
committed
Fix adding route with host overwrites default host route with same method+path in list of routes.
1 parent 895121d commit f1cf1ec

File tree

4 files changed

+232
-82
lines changed

4 files changed

+232
-82
lines changed

echo.go

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ Learn more at https://echo.labstack.com
3737
package echo
3838

3939
import (
40-
"bytes"
4140
stdContext "context"
4241
"crypto/tls"
4342
"errors"
@@ -528,20 +527,13 @@ func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route {
528527
}
529528

530529
func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
531-
name := handlerName(handler)
532530
router := e.findRouter(host)
533-
// FIXME: when handler+middleware are both nil ... make it behave like handler removal
534-
router.Add(method, path, func(c Context) error {
531+
//FIXME: when handler+middleware are both nil ... make it behave like handler removal
532+
name := handlerName(handler)
533+
return router.add(method, path, name, func(c Context) error {
535534
h := applyMiddleware(handler, middleware...)
536535
return h(c)
537536
})
538-
r := &Route{
539-
Method: method,
540-
Path: path,
541-
Name: name,
542-
}
543-
e.router.routes[method+path] = r
544-
return r
545537
}
546538

547539
// Add registers a new route for an HTTP method and path with matching handler
@@ -578,35 +570,13 @@ func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
578570

579571
// Reverse generates an URL from route name and provided parameters.
580572
func (e *Echo) Reverse(name string, params ...interface{}) string {
581-
uri := new(bytes.Buffer)
582-
ln := len(params)
583-
n := 0
584-
for _, r := range e.router.routes {
585-
if r.Name == name {
586-
for i, l := 0, len(r.Path); i < l; i++ {
587-
if (r.Path[i] == ':' || r.Path[i] == '*') && n < ln {
588-
for ; i < l && r.Path[i] != '/'; i++ {
589-
}
590-
uri.WriteString(fmt.Sprintf("%v", params[n]))
591-
n++
592-
}
593-
if i < l {
594-
uri.WriteByte(r.Path[i])
595-
}
596-
}
597-
break
598-
}
599-
}
600-
return uri.String()
573+
return e.router.Reverse(name, params...)
601574
}
602575

603-
// Routes returns the registered routes.
576+
// Routes returns the registered routes for default router.
577+
// In case when Echo serves multiple hosts/domains use `e.Routers()["domain2.site"].Routes()` to get specific host routes.
604578
func (e *Echo) Routes() []*Route {
605-
routes := make([]*Route, 0, len(e.router.routes))
606-
for _, v := range e.router.routes {
607-
routes = append(routes, v)
608-
}
609-
return routes
579+
return e.router.Routes()
610580
}
611581

612582
// AcquireContext returns an empty `Context` instance from the pool.

echo_test.go

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -530,34 +530,71 @@ func TestEchoRoutes(t *testing.T) {
530530
}
531531
}
532532

533-
func TestEchoRoutesHandleHostsProperly(t *testing.T) {
533+
func TestEchoRoutesHandleAdditionalHosts(t *testing.T) {
534534
e := New()
535-
h := e.Host("route.com")
535+
domain2Router := e.Host("domain2.router.com")
536536
routes := []*Route{
537537
{http.MethodGet, "/users/:user/events", ""},
538538
{http.MethodGet, "/users/:user/events/public", ""},
539539
{http.MethodPost, "/repos/:owner/:repo/git/refs", ""},
540540
{http.MethodPost, "/repos/:owner/:repo/git/tags", ""},
541541
}
542542
for _, r := range routes {
543-
h.Add(r.Method, r.Path, func(c Context) error {
543+
domain2Router.Add(r.Method, r.Path, func(c Context) error {
544544
return c.String(http.StatusOK, "OK")
545545
})
546546
}
547+
e.Add(http.MethodGet, "/api", func(c Context) error {
548+
return c.String(http.StatusOK, "OK")
549+
})
547550

548-
if assert.Equal(t, len(routes), len(e.Routes())) {
549-
for _, r := range e.Routes() {
550-
found := false
551-
for _, rr := range routes {
552-
if r.Method == rr.Method && r.Path == rr.Path {
553-
found = true
554-
break
555-
}
551+
domain2Routes := e.Routers()["domain2.router.com"].Routes()
552+
553+
assert.Len(t, domain2Routes, len(routes))
554+
for _, r := range domain2Routes {
555+
found := false
556+
for _, rr := range routes {
557+
if r.Method == rr.Method && r.Path == rr.Path {
558+
found = true
559+
break
556560
}
557-
if !found {
558-
t.Errorf("Route %s %s not found", r.Method, r.Path)
561+
}
562+
if !found {
563+
t.Errorf("Route %s %s not found", r.Method, r.Path)
564+
}
565+
}
566+
}
567+
568+
func TestEchoRoutesHandleDefaultHost(t *testing.T) {
569+
e := New()
570+
routes := []*Route{
571+
{http.MethodGet, "/users/:user/events", ""},
572+
{http.MethodGet, "/users/:user/events/public", ""},
573+
{http.MethodPost, "/repos/:owner/:repo/git/refs", ""},
574+
{http.MethodPost, "/repos/:owner/:repo/git/tags", ""},
575+
}
576+
for _, r := range routes {
577+
e.Add(r.Method, r.Path, func(c Context) error {
578+
return c.String(http.StatusOK, "OK")
579+
})
580+
}
581+
e.Host("subdomain.mysite.site").Add(http.MethodGet, "/api", func(c Context) error {
582+
return c.String(http.StatusOK, "OK")
583+
})
584+
585+
defaultRouterRoutes := e.Routes()
586+
assert.Len(t, defaultRouterRoutes, len(routes))
587+
for _, r := range defaultRouterRoutes {
588+
found := false
589+
for _, rr := range routes {
590+
if r.Method == rr.Method && r.Path == rr.Path {
591+
found = true
592+
break
559593
}
560594
}
595+
if !found {
596+
t.Errorf("Route %s %s not found", r.Method, r.Path)
597+
}
561598
}
562599
}
563600

@@ -1468,14 +1505,27 @@ func TestEchoReverseHandleHostProperly(t *testing.T) {
14681505
dummyHandler := func(Context) error { return nil }
14691506

14701507
e := New()
1508+
1509+
// routes added to the default router are different form different hosts
1510+
e.GET("/static", dummyHandler).Name = "default-host /static"
1511+
e.GET("/static/*", dummyHandler).Name = "xxx"
1512+
1513+
// different host
14711514
h := e.Host("the_host")
1472-
h.GET("/static", dummyHandler).Name = "/static"
1473-
h.GET("/static/*", dummyHandler).Name = "/static/*"
1515+
h.GET("/static", dummyHandler).Name = "host2 /static"
1516+
h.GET("/static/v2/*", dummyHandler).Name = "xxx"
1517+
1518+
assert.Equal(t, "/static", e.Reverse("default-host /static"))
1519+
// when actual route does not have params and we provide some to Reverse we should get that route url back
1520+
assert.Equal(t, "/static", e.Reverse("default-host /static", "missing param"))
1521+
1522+
host2Router := e.Routers()["the_host"]
1523+
assert.Equal(t, "/static", host2Router.Reverse("host2 /static"))
1524+
assert.Equal(t, "/static", host2Router.Reverse("host2 /static", "missing param"))
1525+
1526+
assert.Equal(t, "/static/v2/*", host2Router.Reverse("xxx"))
1527+
assert.Equal(t, "/static/v2/foo.txt", host2Router.Reverse("xxx", "foo.txt"))
14741528

1475-
assert.Equal(t, "/static", e.Reverse("/static"))
1476-
assert.Equal(t, "/static", e.Reverse("/static", "missing param"))
1477-
assert.Equal(t, "/static/*", e.Reverse("/static/*"))
1478-
assert.Equal(t, "/static/foo.txt", e.Reverse("/static/*", "foo.txt"))
14791529
}
14801530

14811531
func TestEcho_ListenerAddr(t *testing.T) {

router.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package echo
22

33
import (
44
"bytes"
5+
"fmt"
56
"net/http"
67
)
78

@@ -141,6 +142,51 @@ func NewRouter(e *Echo) *Router {
141142
}
142143
}
143144

145+
// Routes returns the registered routes.
146+
func (r *Router) Routes() []*Route {
147+
routes := make([]*Route, 0, len(r.routes))
148+
for _, v := range r.routes {
149+
routes = append(routes, v)
150+
}
151+
return routes
152+
}
153+
154+
// Reverse generates an URL from route name and provided parameters.
155+
func (r *Router) Reverse(name string, params ...interface{}) string {
156+
uri := new(bytes.Buffer)
157+
ln := len(params)
158+
n := 0
159+
for _, route := range r.routes {
160+
if route.Name == name {
161+
for i, l := 0, len(route.Path); i < l; i++ {
162+
if (route.Path[i] == ':' || route.Path[i] == '*') && n < ln {
163+
for ; i < l && route.Path[i] != '/'; i++ {
164+
}
165+
uri.WriteString(fmt.Sprintf("%v", params[n]))
166+
n++
167+
}
168+
if i < l {
169+
uri.WriteByte(route.Path[i])
170+
}
171+
}
172+
break
173+
}
174+
}
175+
return uri.String()
176+
}
177+
178+
func (r *Router) add(method, path, name string, h HandlerFunc) *Route {
179+
r.Add(method, path, h)
180+
181+
route := &Route{
182+
Method: method,
183+
Path: path,
184+
Name: name,
185+
}
186+
r.routes[method+path] = route
187+
return route
188+
}
189+
144190
// Add registers a new route for method and path with matching handler.
145191
func (r *Router) Add(method, path string, h HandlerFunc) {
146192
// Validate path

0 commit comments

Comments
 (0)