We are using Zuul to forward requests to an internal microservice. The internal service has 2 endpoints (a PUT endpoint and a POST endpoint). We have found that multipart requests are corrupted before they reach our internal PUT endpoint.
The multipart data seems to be removed from the request. This only seems to be the case with PUT requests because POST works perfectly.
If we hit the internal PUT endpoint directly with cURL, the request is handled correctly. The corresponding cURL command and the request body look like this:
curl -v -X PUT -H "Content-Type: multipart/form-data" -F "file=@/path/to/file.txt" "http://localhost:8081/file/put"
Headers:
{user-agent=[curl/7.35.0], host=[localhost:8082], accept=[/], content-length=[203], expect=[100-continue], content-type=[multipart/form-data; boundary=------------------------c1efb86a9054e387]}
Entity:
--------------------------c1efb86a9054e387 Content-Disposition: form-data; name="file"; filename="helloworld.txt" Content-Type: text/plain
this is my file content
--------------------------c1efb86a9054e387--
However, if we try to hit the PUT endpoint with cURL via Zuul, the request looks like this:
curl -X PUT -H "Content-Type: multipart/form-data" -F "file=@/path/to/file.txt" "http://localhost:8082/file/put"
Header:
{user-agent=[curl/7.35.0], accept=[/], expect=[100-continue], content-type=[multipart/form-data;boundary=hkBnDNXOcDTwkuL1qLhglF6i4NA2YREd], x-forwarded-host=[localhost:8081], x-forwarded-proto=[http], x-forwarded-prefix=[/file], x-forwarded-port=[8081], x-forwarded-for=[127.0.0.1], accept-encoding=[gzip], content-length=[38], host=[localhost:8082], connection=[Keep-Alive]}
Entity:
--hkBnDNXOcDTwkuL1qLhglF6i4NA2YREd--
Notice that the Entity is incomplete.
I have uploaded example code to this repository: https://github.com/trcodestore/zuul-put-demo. The repository contains 2 small projects used to demonstrate this issue. The readme contains build and run instructions.
I understand that requests are initially handled by Spring's DispatcherServlet and are then eventually handled by ZuulServlet. I believe it is the DispatcherServlet that is causing the issue. We are able to bypass the DispatcherServlet by prefixing all our request URIs with "/zuul" -- this allows the multipart request to go directly to ZuulServlet and then it works as expected. However this isn't an ideal fix.
Any advice would be appreciated. Thanks.
Okay, I have the solution (credit: Mohammad Zolmajd).
Spring Boot uses StandardServletMultipartResolver
to handle multipart - which assumes that all multipart requests will be submitted with POST.
To allow StandardServletMultipartResolver to handle PUT requests, we have to override the isMultiPart
method. I ended up using the following configuration:
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver() {
@Override
public boolean isMultipart(HttpServletRequest request) {
String method = request.getMethod().toLowerCase();
if (!Arrays.asList("put", "post").contains(method)) {
return false;
}
String contentType = request.getContentType();
return (contentType != null &&contentType.toLowerCase().startsWith("multipart/"));
}
};
}