Open florentm35 opened 7 months ago
I'm experiencing the same issue with spring cloud gateway mvc.
@spencergibb kindly help to check this issue
There have been a number of fixes in this are recently, can either of you try with 2023.0.1-SNAPSHOT?
i will try tomorrow
After test, the problem is the same :
Hi, after removing below codes in GatewayMultipartHttpServletRequest of file GatewayMvcMultipartResolver.java ,
the query string can work now. why do we have below logics, for performane?
static class GatewayMultipartHttpServletRequest extends StandardMultipartHttpServletRequest { @Override protected void initializeMultipart() { //if (!isGatewayRequest(getRequest())) { //super.initializeMultipart(); //} } @Override public Map<String, String[]> getParameterMap() { //if (isGatewayRequest(getRequest())) { //return Collections.emptyMap(); //} return super.getParameterMap(); }
Run the full build with that removed to see tests fail
I have the same problem! How should this problem be solved? I use Spring Cloud Gateway MVC 4.1.2 and Spring Boot 3.2.4
I am having the same issue, I use Spring Cloud Gateway MVC 4.1.2 and Spring 3.2.3
@spencergibb Any leads on this?
Hi, after removing below codes in GatewayMultipartHttpServletRequest of file GatewayMvcMultipartResolver.java , the query string can work now. why do we have below logics, for performane?
static class GatewayMultipartHttpServletRequest extends StandardMultipartHttpServletRequest { @OverRide protected void initializeMultipart() { //if (!isGatewayRequest(getRequest())) { //super.initializeMultipart(); //} } @OverRide public Map<String, String[]> getParameterMap() { //if (isGatewayRequest(getRequest())) { //return Collections.emptyMap(); //} return super.getParameterMap(); }
How do I edit this? @lohoso
@lohoso @spencergibb The project is on hold for a long time now, I need to get this done. Can somebody help me with the same?
same issue with Spring Cloud Gateway MVC 4.1.4 and Spring Boot 3.2.5, any update or workaround on this? Thanks
I had the same issue, i ended up overriding the ProxyExchangeHandlerFunction, `package com.apigateway;
import lombok.extern.Slf4j.Slf4j; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter; import org.springframework.cloud.gateway.server.mvc.handler.ProxyExchange; import org.springframework.cloud.gateway.server.mvc.handler.ProxyExchangeHandlerFunction; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.servlet.function.ServerRequest; import org.springframework.web.servlet.function.ServerResponse; import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI; import java.util.Set; import java.util.function.Function; import java.util.stream.Stream;
@Slf4j @Component public class ProxyExchangeHandler extends ProxyExchangeHandlerFunction {
private static final String X_METHOD = "X-METHOD";
private final Set<HttpMethod> httpMethodSet = Set.of(HttpMethod.GET, HttpMethod.OPTIONS, HttpMethod.HEAD, HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH, HttpMethod.DELETE);
private final ProxyExchange proxyExchange;
private final ObjectProvider<HttpHeadersFilter.RequestHttpHeadersFilter> requestHttpHeadersFilters;
private final ObjectProvider<HttpHeadersFilter.ResponseHttpHeadersFilter> responseHttpHeadersFilters;
private final ProxyExchangeHandlerFunction.URIResolver uriResolver;
public ProxyExchangeHandler(ProxyExchange proxyExchange, ObjectProvider<HttpHeadersFilter.RequestHttpHeadersFilter> requestHttpHeadersFilters, ObjectProvider<HttpHeadersFilter.ResponseHttpHeadersFilter> responseHttpHeadersFilters) {
super(proxyExchange, requestHttpHeadersFilters, responseHttpHeadersFilters);
this.proxyExchange = proxyExchange;
this.requestHttpHeadersFilters = requestHttpHeadersFilters;
this.responseHttpHeadersFilters = responseHttpHeadersFilters;
uriResolver = (request) -> {
return (URI) request.attribute(MvcUtils.GATEWAY_REQUEST_URL_ATTR).orElseThrow(() -> {
return new IllegalStateException("No route resolved");
});
};
}
public ServerResponse hanfle(ServerRequest serverRequest) {
URI uri = uriResolver.apply(serverRequest);
String method= serverRequest.headers().firstHeader(X_METHOD);
boolean encoded = containsEncodedQuery(uri);
if(method==null){
method=serverRequest.method().name();
}
HttpMethod httpMethod = HttpMethod.valueOf(method.toUpperCase());
if(!httpMethodSet.contains(httpMethod)){
throw new HttpClientErrorException(HttpStatus.BAD_REQUEST,"Invalid method value: "+method+" in "+X_METHOD+" header");
}
URI url = UriComponentsBuilder.fromUri(uri)
.scheme(uri.getScheme())
.host(uri.getHost())
.port(uri.getPort())
//.replaceQueryParams(serverRequest.params())
.build(encoded)
.toUri();
HttpHeaders filteredRequestHeaders = filterHeaders(this.requestHttpHeadersFilters.orderedStream().map(Function.identity()), serverRequest.headers().asHttpHeaders(), serverRequest);
boolean preserveHost = (boolean) serverRequest.attributes()
.getOrDefault(MvcUtils.PRESERVE_HOST_HEADER_ATTRIBUTE, false);
if (preserveHost) {
filteredRequestHeaders.set(HttpHeaders.HOST, serverRequest.headers().firstHeader(HttpHeaders.HOST));
} else {
filteredRequestHeaders.remove(HttpHeaders.HOST);
}
ProxyExchange.Request proxyRequest = proxyExchange.request(serverRequest).uri(uri)
.headers(filteredRequestHeaders)
.responseConsumer((response, serverResponse) -> {
HttpHeaders headers = filterHeaders(this.responseHttpHeadersFilters.orderedStream().map(Function.identity()), response.getHeaders(), serverResponse);
serverResponse.headers().putAll(headers);
}).build();
return proxyExchange.exchange(proxyRequest);
}
private <TYPE> HttpHeaders filterHeaders(Stream<HttpHeadersFilter<TYPE>> filters, HttpHeaders headers, TYPE t) {
HttpHeaders filtered = headers;
for (var filter : filters.toList()) {
filtered = filter.apply(filtered, t);
}
return filtered;
}
private static boolean containsEncodedQuery(URI uri) {
boolean encoded = (uri.getRawQuery() != null && uri.getRawQuery().contains("%"))
|| (uri.getRawPath() != null && uri.getRawPath().contains("%"));
if (encoded) {
try {
UriComponentsBuilder.fromUri(uri).build(true);
return true;
} catch (IllegalArgumentException ignored) {
if (log.isTraceEnabled()) {
log.trace("Error in containsEncodedParts", ignored);
}
}
return false;
}
return encoded;
}
} `
@lohoso @spencergibb Any leads around the flow?
I worked around this by providing my own MultipartResolver bean.
@Component
public class PassthroughMultiPartResolver implements MultipartResolver {
@Override
public boolean isMultipart(HttpServletRequest request) {
// Always pass through multipart requests without parsing
return false;
}
@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
throw new UnsupportedOperationException();
}
@Override
public void cleanupMultipart(MultipartHttpServletRequest request) {
throw new UnsupportedOperationException();
}
}
Describe the bug
With spring cloud gateway mvc 2023.0.0 when we perform an post request with multi part and query string, the query string was loose.
Exemple with an test server on port 9008 and a gateway on port 9007 if i point on the server directly :
if i use the gateway :
![image](https://github.com/spring-cloud/spring-cloud-gateway/assets/31475579/6613cd15-f960-4223-b1aa-7730d7618523)
Sample http-interceptor : (it's juste a test server to print the query param)
gateway : (spring initializr)
EDIT:
Moreover the header Content-Length are delete by the gateway