Use case is: send files via Streaming using InputStream, since spring webclient is not working with InputStreamResource, made some modifications created MultiPartInputStreamFileResource (code pasted below) Using ByteArrayInputStreamResource is not an option for us, it will load everything into memory (causing memory issues) instead of streaming. Works till 2.1.4.RELEASE of spring-boot-starter-parent using (MultiPartInputStreamFileResource ), 1) 2.1.5.RELEASE of spring-boot-starter-parent -> throws unsupported operation exception 2) 2.1.6.RELEASE of spring-boot-starter-parent -> unsupported operation exception is resolved, but Getting "Unexpected end of multipart data" from server, while sending files.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
public class MultipartInputStreamFileResource extends InputStreamResource {
private final String filename;
public MultipartInputStreamFileResource(InputStream inputStream, String filename) {
super(inputStream);
this.filename = filename;
}
@Override
public String getFilename() {
return this.filename;
}
@Override
public long contentLength() {
return -1; // we do not want to generally read the whole stream into memory ...
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
MultipartInputStreamFileResource that = (MultipartInputStreamFileResource) o;
return Objects.equals(filename, that.filename);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), filename);
}
}
public class ResourceHttpMessageConverterHandlingInputStreams extends ResourceHttpMessageConverter {
@Override
protected Long getContentLength(Resource resource, MediaType contentType) throws IOException {
Long contentLength = super.getContentLength(resource, contentType);
return contentLength == null || contentLength < 0 ? null : contentLength;
}
}
private BodyInserter<?, ? super ClientHttpRequest> getMultipartBodyInserter(
InputStream inputStream, String fileName, String version, String messageDigest) {
MultipartInputStreamFileResource multipartInputStreamFileResource =
new MultipartInputStreamFileResource(inputStream, fileName);
MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
addHashToMap(multiValueMap, messageDigest);
multiValueMap.put("version", Arrays.asList(version));
multiValueMap.put("file", Arrays.asList(multipartInputStreamFileResource));
return BodyInserters.fromMultipartData(multiValueMap);
}
and use the body inserter in webclient as below,
WebClient webClient = webClientBuilder.baseUrl(url).defaultUriVariables(uriVariables)
.build();
WebClient.RequestBodySpec requestBodySpec = webClient.method(httpMethod);
Mono<ResponseEntity> responseEntityMono = requestBodySpec.body(bodyInserter).exchange()
.flatMap(clientResponse -> clientResponse.toEntity(responseClassType));
Comment From: rstoyanchev
First I don't think you need all that code. It should be possible to replace with just a few lines:
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("file", resource).filename("MyFile.txt");
// ...
webClient.post()
.syncBody(builder.build())
.exchange()
...
2.1.6.RELEASE of spring-boot-starter-parent -> unsupported operation exception is resolved, but Getting "Unexpected end of multipart data" from server, while sending files.
Secondly, if you're sending files, should that not be a FileSystemResource
?
In any case I think I will need a sample to reproduce the issue.
Comment From: gurudatta11
@rstoyanchev
I have attached demo.zip (contains sample code which reproduces the issue),
use "http://localhost:8080/hello" to trigger the api ,
in the pom.xml, please change version of spring boot starter parent accordingly (2.1.4, 2.1.6), works for 2.1.4, not for 2.1.5, 2.1.6
- Couldn't find filename method in MultipartBodyBuilder/ MultipartBodyBuilder.part
- We have file/s stored in a separate smb (samba server), so we get input stream, not FileSystemResource
Also from the stack trace from the demo application, thought it's tomcat issue (2.1.4 uses 9.0.17 tomcat core), (2.1.6 uses 9.0.21 tomcat) ,
- tried overriding 2.1.4 with 9.0.21 tomcat, it works
- tried overriding 2.1.6 with 9.0.17 tomcat, still same issue occurs
So the issue is not related to tomcat.
org.apache.tomcat.util.http.fileupload.MultipartStream$MalformedStreamException: Stream ended unexpectedly
at org.apache.tomcat.util.http.fileupload.MultipartStream$ItemInputStream.makeAvailable(MultipartStream.java:983) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.tomcat.util.http.fileupload.MultipartStream$ItemInputStream.read(MultipartStream.java:881) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at java.io.FilterInputStream.read(FilterInputStream.java:133) ~[na:1.8.0_211]
at org.apache.tomcat.util.http.fileupload.util.LimitedInputStream.read(LimitedInputStream.java:132) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at java.io.FilterInputStream.read(FilterInputStream.java:107) ~[na:1.8.0_211]
at org.apache.tomcat.util.http.fileupload.util.Streams.copy(Streams.java:98) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:294) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.connector.Request.parseParts(Request.java:2881) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.connector.Request.parseParameters(Request.java:3214) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.connector.Request.getParameter(Request.java:1116) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:381) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:84) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853) [tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587) [tomcat-embed-core-9.0.21.jar:9.0.21]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.21.jar:9.0.21]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_211]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_211]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.21.jar:9.0.21]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_211]
looks like it's not working from reactor netty 0.8.7.RELEASE (tried till 0.8.10.RELEASE , not working), works till 0.8.6.RELEASE
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
<version>0.8.6.RELEASE</version>
<!-- <scope>compile</scope>-->
</dependency>
Comment From: gurudatta11
created https://github.com/reactor/reactor-netty/issues/820
Comment From: gurudatta11
resolved by https://github.com/reactor/reactor-netty/issues/820
Comment From: ShubhaKV
Hi,
I need to send the content of zip to post method. Here byte[] is the binary file i uploaded in postman
public String publishZipFile(byte[] data) {
System.out.println(data.length);
String channelResp =null;
webClientBuilder
.build()
.post()
.uri("/appCatalogs/teamsApps")
.headers(httpsHeader -> httpsHeader.addAll(headerMap))
.syncBody(data)
.retrieve()
.bodyToMono(byte[].class)
.block();
return channelResp;
}
Anything wrong am doing here. Pls help mw how i can send zip file content to body of webclient