-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Honor parameters order when parsing query and form parameters #7599
Conversation
When parsing the query or form parameters in Request, the values are stored in a MultiMap. This class extends HashMap which does not preserve the order of insertion so a request with parameters "first=1&second=2" might end up in a map where "second" will come first when iterating on the entry set. The order is necessary in some case where the request is signed off the body and/or the query parameters. When the order is not preserved, it is impossible to reconstruct the original request sent, unless using the Request::getInputStream which consumes the stream and makes subsequent calls to Request::getParameters to don't return the form parameters which can be misleading. The same behavior applied to query parameters, by using Request::getQueryString, you get the correct order but Request::getParameters will not. Moreoever, if the application is behind a reverse proxy using Jetty that is proxying using Request::getParameters which consume the request InputStream, it will be completely impossible to reconstruct the original request.
Digest is not handled at this level. There is no query parameter order with the Servlet spec (which is what Jetty 9.x/10.x/11.x are built against, we are changing this with Jetty 12, the core server will be disconnected from the servlet spec and behaviors). @lorban what do you think about this change from a performance point of view? can you test this branch against our performance baseline? |
Thanks for your input. To give you some additional context, we have a reverse proxy using Jetty that uses, for hard to change historical reasons, the I understand that the spec doesn't specify ordering and that digest is hard/impossible to do so based solely on the Servlet spec. However, the "happy path" of a request that is not forwarded and has no special characters that can be decoded to For what it's worth (absolutely not trying to start a finger pointing fight), Tomcat seems to preserve ordering 1 2. |
jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
Outdated
Show resolved
Hide resolved
Tomcat still has the same servlet spec issues with regards to RequestDispatcher and parameter merging, that order is transient in the extreme in the tomcat case, don't trust it. Also, with your change, what happens if the query is If you have a combination of query and form params, merging occurs again on the parameters map. |
What would your expected param order be for the following request?
|
String responses = _connector.getResponse(request); | ||
Map<String, String[]> returnedMap = reference.get(); | ||
assertTrue(responses.startsWith("HTTP/1.1 200")); | ||
assertThat(new ArrayList<>(returnedMap.keySet()), is(Arrays.asList("a", "b", "c"))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't make sense.
The params are filled in by logic in Request.getParameters(), which does the form content first, then the query params. Which means this this should be "c", "b", "a"
as the form sets the order, not the query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are parsed before but when getting the map, they are injected in the order defined by the servlet spec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That block is concerned with the value order when merging between query/content (what the Servlet Section 3.1 is about), the key order is different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Data from the query string and the post body are aggregated into the request parameter set. Query string data is presented before post body data. For example, if a request is made with a query string of a=hello and a post body of a=goodbye&a=world, the resulting parameter set would be ordered a=(hello, goodbye, world).
I wish the spec would give another example with different keys than a
... You're right that it could be read as the order for the data (data as in the values, not the keys) but I personally read it as data == key+value. So I'd expect the key+values to be ordered with query parameters first, then form parameters.
It will end up like As for combinations, this is defined in the spec AFAICT in section 3.1 :
So for
I've added an additional test to show this behavior if you want to look at it. I agree that this PR is not perfect but I believe that for simple requests that don't have duplicate parameters, the returned variable is better when it's ordered. |
We missed a few ECA requirements on this PR. |
3dba283
to
9ff06aa
Compare
Should be fixed, sorry I commited the last test with my work email. |
No problem, it happens. Thanks for the quick fix. |
Seems like I'm not the only one to worry about parameters ordering : jakartaee/servlet#393 Here is the bug when Tomcat changed from |
@joakime from a performance point of view, I tested this change against our baseline and it makes no measurable difference. |
Yep, I was down that rabbit hole already. :-) Here's the commit btw, apache/tomcat@90556a9 |
Hahaha good find, that commit looks a lot like this PR, minus the tests 😄 |
* Honor parameters order when parsing query and form parameters When parsing the query or form parameters in Request, the values are stored in a MultiMap. This class extends HashMap which does not preserve the order of insertion so a request with parameters "first=1&second=2" might end up in a map where "second" will come first when iterating on the entry set. The order is necessary in some case where the request is signed off the body and/or the query parameters. When the order is not preserved, it is impossible to reconstruct the original request sent, unless using the Request::getInputStream which consumes the stream and makes subsequent calls to Request::getParameters to don't return the form parameters which can be misleading. The same behavior applied to query parameters, by using Request::getQueryString, you get the correct order but Request::getParameters will not. Moreoever, if the application is behind a reverse proxy using Jetty that is proxying using Request::getParameters which consume the request InputStream, it will be completely impossible to reconstruct the original request. * Added a test with parameter merging
Merged into |
…#7605) * Honor parameters order when parsing query and form parameters When parsing the query or form parameters in Request, the values are stored in a MultiMap. This class extends HashMap which does not preserve the order of insertion so a request with parameters "first=1&second=2" might end up in a map where "second" will come first when iterating on the entry set. The order is necessary in some case where the request is signed off the body and/or the query parameters. When the order is not preserved, it is impossible to reconstruct the original request sent, unless using the Request::getInputStream which consumes the stream and makes subsequent calls to Request::getParameters to don't return the form parameters which can be misleading. The same behavior applied to query parameters, by using Request::getQueryString, you get the correct order but Request::getParameters will not. Moreoever, if the application is behind a reverse proxy using Jetty that is proxying using Request::getParameters which consume the request InputStream, it will be completely impossible to reconstruct the original request. * Added a test with parameter merging Co-authored-by: Jacques-Etienne Beaudet <[email protected]>
…#7605) * Honor parameters order when parsing query and form parameters When parsing the query or form parameters in Request, the values are stored in a MultiMap. This class extends HashMap which does not preserve the order of insertion so a request with parameters "first=1&second=2" might end up in a map where "second" will come first when iterating on the entry set. The order is necessary in some case where the request is signed off the body and/or the query parameters. When the order is not preserved, it is impossible to reconstruct the original request sent, unless using the Request::getInputStream which consumes the stream and makes subsequent calls to Request::getParameters to don't return the form parameters which can be misleading. The same behavior applied to query parameters, by using Request::getQueryString, you get the correct order but Request::getParameters will not. Moreoever, if the application is behind a reverse proxy using Jetty that is proxying using Request::getParameters which consume the request InputStream, it will be completely impossible to reconstruct the original request. * Added a test with parameter merging Co-authored-by: Jacques-Etienne Beaudet <[email protected]>
…#7605) * Honor parameters order when parsing query and form parameters When parsing the query or form parameters in Request, the values are stored in a MultiMap. This class extends HashMap which does not preserve the order of insertion so a request with parameters "first=1&second=2" might end up in a map where "second" will come first when iterating on the entry set. The order is necessary in some case where the request is signed off the body and/or the query parameters. When the order is not preserved, it is impossible to reconstruct the original request sent, unless using the Request::getInputStream which consumes the stream and makes subsequent calls to Request::getParameters to don't return the form parameters which can be misleading. The same behavior applied to query parameters, by using Request::getQueryString, you get the correct order but Request::getParameters will not. Moreoever, if the application is behind a reverse proxy using Jetty that is proxying using Request::getParameters which consume the request InputStream, it will be completely impossible to reconstruct the original request. * Added a test with parameter merging Co-authored-by: Jacques-Etienne Beaudet <[email protected]>
A change was made in Jetty 9.4.46.v20220331 to guarantee the order of request parameter insertion, but the change is jetty-specific, so the test is not dependent on that behavior. jetty/jetty.project#7599
* Update jetty-servlet, jetty-webapp to 9.4.46.v20220331 * Removed tests that depended on the order of params A change was made in Jetty 9.4.46.v20220331 to guarantee the order of request parameter insertion, but the change is jetty-specific, so the test is not dependent on that behavior. jetty/jetty.project#7599 Co-authored-by: Magnolia.K <[email protected]>
When parsing the query or form parameters in
Request
, the values are stored in aMultiMap
. This class extendsHashMap
which does not preserve the order of insertion so a request with parametersfirst=1&second=2
might end up in a map wheresecond
will come first when iterating on the entry set.The order is necessary in some case where the request is signed off the body and/or the query parameters. When the order is not preserved, it is impossible to reconstruct the original request sent to form a digest, unless using the
Request::getInputStream
which consumes the stream and makes subsequent calls toRequest::getParameters
to don't return the form parameters which can be misleading. The same behavior applied to query parameters, by usingRequest::getQueryString
, you get the correct order butRequest::getParameters
will not.Moreoever, if the application is behind a reverse proxy using Jetty that is proxying using
Request::getParameters
which consume the request InputStream, it will be completely impossible to reconstruct the original request.I believe changing
MultiMap
to extendsLinkedHashMap
should have no performance impact and a minimal additional memory footprint. I'm of course open to alternatives if you have any.I've based off this PR against the 9.4 branch which is still widely used by Spring Boot notably. I believe it can also be easily merged in other branch since changes are quite trivial.
Thanks!