Query Params (`request.params()`) being decoded while URI (`request.uri()`) is not when before calling downstream services
Describe the bug
When passing certain query parameters with encoded characters (like %26) I am expecting that they are passed encoded like this to the downstream services but what is happening is that they are being decoded.
For example, when using the example https://github.com/spencergibb/spring-cloud-gateway-mvc-sample src/main/java/com/example/gatewaymvcsample/Route01FirstRoute.java endpoint passing some encoded characters, we can see that they are being decoded in the parameters
GET localhost:8080/anything/first?company=H%26M&search=one%20two
The other weird part is that the URI is not encoded.
with the & in the parameters, when it calls the downstream services it does not behave well as it thinks is a parameter separation character and not part of the company parameter.
I was able to make it work by creating a before filter and encoding the query parameters before calling the downstream services but for me, it seems a bit of a workaround.
public static Function<ServerRequest, ServerRequest> encodeRequestParameters() {
return request -> ServerRequest.from(request)
.params(queryParams -> queryParams.forEach((key, values) -> {
List<String> modifiedValues = values.stream()
.map(value -> UriUtils.encodeQueryParam(value, StandardCharsets.UTF_8))
.toList();
queryParams.put(key, modifiedValues);
}))
.build();
}
I am not sure which behavior is correct (I think it should remain encoded) but the weird thing is that the parameters and URI are different (one encoded, the other not)
Is this the expected behavior?
Versions: org.springframework.boot:3.3.2 org.springframework.cloud:spring-cloud-dependencies:2023.0.3 org.springframework.cloud:spring-cloud-gateway-server-mvc:4.1.5
Sample
You can just call the GET localhost:8080/anything/first?company=H%26M&search=one%20two for Route01FirstRoute in the sample project https://github.com/spencergibb/spring-cloud-gateway-mvc-sample
Possible fix:
public class ProxyExchangeHandlerFunction
implements HandlerFunction<ServerResponse>, ApplicationListener<ContextRefreshedEvent> {
[...]
@Override
public ServerResponse handle(ServerRequest serverRequest) {
URI uri = uriResolver.apply(serverRequest);
boolean encoded = containsEncodedQueryOrPath(serverRequest.uri());
// @formatter:off
URI url = UriComponentsBuilder.fromUri(serverRequest.uri())
.scheme(uri.getScheme())
.host(uri.getHost())
.port(uri.getPort())
.replaceQueryParams(encoded ? UriUtils.encodeQueryParams(serverRequest.params()) : serverRequest.params())
.build(encoded)
.toUri();
// @formatter:on
[...]
private static boolean containsEncodedQueryOrPath(URI uri) {
String rawQuery = uri.getRawQuery();
return (rawQuery != null && rawQuery.contains("%")) || (uri.getRawPath() != null && uri.getRawPath().contains("%"));
}
[...]
Of course, @jluiz20 would need to remove his workaround afterwards.
I'm having quite the same issue with a parameters filters=%5B%5D which is propagated with filters=[] to the downstream services.
@jluiz20 Are you using spring gateway mvc or the reactive spring gateway? Where do you put the filter? I'm not sure this workaround can work for me, since I (unfortunately) have legacy endpoint directly on the gateway, where parameters can't be "double encoded" before.
The proposed fix by @mdaepp look right to me, based on my local debug
Versions:
org.springframework.boot:3.3.6 org.springframework.cloud:spring-cloud-dependencies:2023.0.5 org.springframework.cloud:spring-cloud-gateway-server-mvc:4.1.6