Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions echo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1860,3 +1860,20 @@ func BenchmarkEchoGitHubAPIMisses(b *testing.B) {
func BenchmarkEchoParseAPI(b *testing.B) {
benchmarkEchoRoutes(b, parseAPI)
}

func TestEchoAddParamRouteAfterServing(t *testing.T) {
e := New()
e.GET("/static", func(c Context) error { return c.String(http.StatusOK, "static") })

rec := httptest.NewRecorder()
e.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/static", nil))
assert.Equal(t, "static", rec.Body.String())

// the pooled context from the first request has param values sized for routes without params
e.GET("/users/:id", func(c Context) error { return c.String(http.StatusOK, c.Param("id")) })

rec = httptest.NewRecorder()
e.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/users/42", nil))
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, "42", rec.Body.String())
}
16 changes: 16 additions & 0 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,16 @@ func optionsMethodHandler(allowMethods string) func(c Context) error {
}
}

// growParamValues replaces the context's pvalues when the context was created (and pooled) before a route with more
// path params was added, so Find can set values by index. Values already set during this Find are kept and the
// context keeps the new slice for later requests.
func (r *Router) growParamValues(ctx *context, paramValues []string) []string {
grown := make([]string, *r.echo.maxParam)
copy(grown, paramValues)
ctx.pvalues = grown
return grown
}

// Find lookup a handler registered for method and path. It also parses URL for path
// parameters and load them into context.
//
Expand Down Expand Up @@ -730,6 +740,9 @@ func (r *Router) Find(method, path string, c Context) {
i = l
}

if paramIndex >= len(paramValues) {
paramValues = r.growParamValues(ctx, paramValues)
}
paramValues[paramIndex] = search[:i]
paramIndex++
search = search[i:]
Expand All @@ -742,6 +755,9 @@ func (r *Router) Find(method, path string, c Context) {
if child := currentNode.anyChild; child != nil {
// If any node is found, use remaining path for paramValues
currentNode = child
if currentNode.paramsCount > len(paramValues) {
paramValues = r.growParamValues(ctx, paramValues)
}
paramValues[currentNode.paramsCount-1] = search

// update indexes/search in case we need to backtrack when no handler match is found
Expand Down
40 changes: 40 additions & 0 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2930,3 +2930,43 @@ func BenchmarkRouterGooglePlusAPIMisses(b *testing.B) {
func BenchmarkRouterParamsAndAnyAPI(b *testing.B) {
benchmarkRouterRoutes(b, paramAndAnyAPI, paramAndAnyAPIToFind)
}

func TestRouterFindWithContextCreatedBeforeParamRouteAdded(t *testing.T) {
var testCases = []struct {
name string
whenURL string
expectRoute string
expectParam map[string]string
}{
{
name: "ok, param route grows the context",
whenURL: "/users/1/files/a.txt",
expectRoute: "/users/:id/files/*",
expectParam: map[string]string{"id": "1", "*": "a.txt"},
},
{
name: "ok, any route grows the context",
whenURL: "/static/css/a.css",
expectRoute: "/static/*",
expectParam: map[string]string{"*": "css/a.css"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
e := New()
// the context is created while no route has path params, so it has no room for param values
c := e.NewContext(nil, nil).(*context)

r := e.router
r.Add(http.MethodGet, "/users/:id/files/*", handlerFunc)
r.Add(http.MethodGet, "/static/*", handlerFunc)

r.Find(http.MethodGet, tc.whenURL, c)

assert.Equal(t, tc.expectRoute, c.Path())
for name, value := range tc.expectParam {
assert.Equal(t, value, c.Param(name))
}
})
}
}
Loading