Affects: 5.3.3


My use case is that I need to proxy some multipart form to another service:

  @PostMapping(
      consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
      produces = MediaType.APPLICATION_JSON_VALUE)
  @ResponseStatus(HttpStatus.CREATED)
  public Mono<ResponseEntity<SomeEntityId>> upload(
      @RequestBody Mono<MultiValueMap<String, Part>> parts) {
    return someServiceClient.upload(parts);
  }

  public Mono<ResponseEntity<SomeEntityId>> upload(
      Mono<MultiValueMap<String, Part>> partsMono) {
    return partsMono.flatMap(
        parts ->
            webClient
                .post()
                .uri("/api/someservice/v2")
                .contentType(MediaType.MULTIPART_FORM_DATA)
                .accept(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromMultipartData(parts))
                .retrieve()
                .toEntity(SomeEntityId.class)
                .publishOn(Schedulers.boundedElastic())
                .doOnError(e -> LOGGER.error("Couldn't upload media", e)));
  }

Problem here is that BodyInserters.fromMultipartData swallows content type from Part.headers().

As a workaround I convert MultiValueMap before passing it to BodyInserters.fromMultipartData:

  private MultiValueMap<String, org.springframework.http.HttpEntity<?>> mapForm(
      MultiValueMap<String, Part> parts) {
    MultipartBodyBuilder builder = new MultipartBodyBuilder();
    parts.forEach(
        (key, valueList) -> {
          for (Part value : valueList) {
            builder.part(key, value, value.headers().getContentType());
          }
        });

    return builder.build();
  }

I'm not sure how it's actually supposed to work, but perhaps here, apart from context(), is it worth it to copy headers() as well?

Comment From: AbdelHedhili

Hi @poutsma , I think this commit (e537844) breaks my server. I'm almost in the same case as the code described above except that I take one FilePart from the incoming request and I rename it in the outgoing request. Something like that:

  public Mono<ResponseEntity<SomeEntityId>> upload(
      @RequestBody Mono<MultiValueMap<String, Part>> parts) {
    return someServiceClient.upload(parts.map(m -> (FilePart) m.get("foo").get(0));    //here I take the foo part 

  }

  public Mono<ResponseEntity<SomeEntityId>> upload(
      Mono<MultiValueMap<String, Part>> partsMono) {
    return multipartFile.flatMap(file -> {
            MultipartBodyBuilder multipartBodyBuilder = new MultipartBodyBuilder();
            multipartBodyBuilder.part("bar", file); //here I rename it to bar
            return webClient.post()
                    .uri("uri")
                    .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA.toString())
                    .body(BodyInserters.fromMultipartData(multipartBodyBuilder.build()))
        });
    }

I came across this issue when upgrading my spring version : when I do this, in the outgoing request, the part that I wanted to rename "bar" is still named "foo". It worked in spring 5.2.8 (it renamed the part) but in 5.3.6 the file is not renamed anymore.

Do you have any advice ? Thanks.

Comment From: poutsma

@AbdelHedhili Please file a separate issue for this problem. Feel free to ping me on said issue.