Description
Issue Description
It took me a while to realize how params actually function under the hood, and it seems very weird. Echo uses 2 slices (context.pname, context.pvalues) to keep track of params. This is already weird (why not just use a map?), but it actually gets weirder.
Basically, the router goes through all your routes, and increases e.maxParam
(initially set to 0) to the highest number of params that you have in one route. When a user makes a request to your API, pvalues
get instantiated to size of e.maxParam
(even if the current request hits an endpoint with no params in the route). However, pnames
has length set to the actual number of params in the current request - which means anytime you call c#Param()
, you have to check that you're not going out of bounds.
Weird. But here's where it gets weirder.
If you want to modify the value of a param (for any reason), you actually can't without doing something hacky. So, you have to get pnames and pvalues using c#ParamNames()
and c#ParamValues()
. To set them (after you've modified the array), you'll have to use c#SetParamNames()
and c#SetParamValues()
. The problem is that c#ParamValues
doesn't return the whole pvalues
, but only the slice up to the length of pnames
(c.pvalues[:len(c.pnames)]
). This means when you go to set pvalues, you're actually changing the length - which breaks routing if you later hit an endpoint with more params than you just reset the length to.
This results in having to either keep track of e.maxParams
yourself or setting the length of pvalues
to some very large number.
Here's a code example from some middleware that you can write that breaks it:
names, vals := c.ParamNames(), c.ParamValues() //both slices are same length n
for i, n := range names {
vals[i] = fmt.Sprintf("%s123", vals[i]) // changes a value of "asdf" to "asdf123"
}
c.SetParamValues(vals...)
If this runs on an api with an endpoint like /test/:param1
then /test/:param1/:param2
, it will break, because pvalues is now of length 1.
The "correct" way to do this would be:
names, vals, newVals := c.ParamNames(), c.ParamValues(), make([]string, n) // where n is a large number
for i, n := range names {
newVals[i] = fmt.Sprintf("%s123", vals[i]) // changes a value of "asdf" to "asdf123"
}
c.SetParamValues(newVals...)
This will work on the above scenario, but will not work if you hit an endpoint with n+1 params.
Is there a reason this isn't done with maps? Seems like it would fix this whole issue.