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

demo.zip

  • 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