Spring Webflux 415 with MultipartFile

FattySalami :

I am currently trying to upload a file from an Angular 4 front-end to a Spring Webflux controller. The controller is able to read the @RequestPart value but throws a 415 UnsupportedMediaTypeStatusException.

UploadController

@PostMapping( consumes = MediaType.MULTIPART_FORM_DATA_VALUE )
public Mono<Void> save(@RequestPart("file")MultipartFile file) {
    log.info("Storing a new file. Recieved by Controller");
    this.storageService.store(file);
    return Mono.empty();
}

The log.info() method does not execute so it seems that the error is being thrown before the method is executed.

Error message

org.springframework.web.server.UnsupportedMediaTypeStatusException: Response status 415 with reason "Content type 'image/png' not supported"
at org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver.readBody(AbstractMessageReaderArgumentResolver.java:206) ~[spring-webflux-5.0.4.RELEASE.jar:5.0.4.RELEASE]
at org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver.readBody(AbstractMessageReaderArgumentResolver.java:124) ~[spring-webflux-5.0.4.RELEASE.jar:5.0.4.RELEASE]
at org.springframework.web.reactive.result.method.annotation.RequestPartMethodArgumentResolver.lambda$resolveArgument$0(RequestPartMethodArgumentResolver.java:99) ~[spring-webflux-5.0.4.RELEASE.jar:5.0.4.RELEASE]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:118) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:450) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:450) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:450) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.drainAsync(FluxFlattenIterable.java:391) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.drain(FluxFlattenIterable.java:633) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.onNext(FluxFlattenIterable.java:238) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:450) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxFilterFuseable$FilterFuseableSubscriber.onNext(FluxFilterFuseable.java:87) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:450) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxReplay$SizeBoundReplayBuffer.replayFused(FluxReplay.java:865) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxReplay$SizeBoundReplayBuffer.replay(FluxReplay.java:895) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.ReplayProcessor.onNext(ReplayProcessor.java:436) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.MonoProcessor.drainLoop(MonoProcessor.java:504) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.MonoProcessor.onNext(MonoProcessor.java:347) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:450) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:450) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:115) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:450) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1069) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:142) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onComplete(FluxOnAssembly.java:460) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxCreate$BaseSink.complete(FluxCreate.java:404) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxCreate$BufferAsyncSink.drain(FluxCreate.java:712) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxCreate$BufferAsyncSink.complete(FluxCreate.java:666) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxCreate$SerializedSink.drainLoop(FluxCreate.java:221) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxCreate$SerializedSink.drain(FluxCreate.java:192) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at reactor.core.publisher.FluxCreate$SerializedSink.complete(FluxCreate.java:187) ~[reactor-core-3.1.5.RELEASE.jar:3.1.5.RELEASE]
at org.springframework.http.codec.multipart.SynchronossPartHttpMessageReader$FluxSinkAdapterListener.onAllPartsFinished(SynchronossPartHttpMessageReader.java:215) ~[spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
at org.synchronoss.cloud.nio.multipart.NioMultipartParser.allPartsRead(NioMultipartParser.java:603) ~[nio-multipart-parser-1.1.0.jar:na]
at org.synchronoss.cloud.nio.multipart.NioMultipartParser.write(NioMultipartParser.java:449) ~[nio-multipart-parser-1.1.0.jar:na]
at org.synchronoss.cloud.nio.multipart.NioMultipartParser.write(NioMultipartParser.java:370) ~[nio-multipart-parser-1.1.0.jar:na]
at org.springframework.http.codec.multipart.SynchronossPartHttpMessageReader$SynchronossPartGenerator.lambda$accept$0(SynchronossPartHttpMessageReader.java:136) ~[spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]

The dependency Spring Webflux should use org.synchronoss.cloud.nio.multipart is loaded so I am not fully understanding why the 415 error is being thrown by Spring.

I created a test using the WebClient in Spring

WebTest

    @Test
public void sendValidFileSaveCorrectly() {
    MockMultipartFile file = new MockMultipartFile("foo", "foo.txt",
            MediaType.TEXT_PLAIN_VALUE, "Hello World".getBytes());
    MultipartBodyBuilder builder = new MultipartBodyBuilder();
    builder.part("file", file);

    webClient.post()
            .uri("/api/file")
            .syncBody(builder.build())
            .exchange()
            .expectStatus().is2xxSuccessful();
}

I got a new 500 error instead using the MockMultipartFile and this message

I/O failure: org.springframework.core.codec.CodecException: Type definition error: [simple type, class java.io.ByteArrayInputStream]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.mock.web.MockMultipartFile["inputStream"])

What I am trying to understand is why Spring is throwing an Unsupported Media Type exception and how I can direct Spring Webflux to ignore whatever is writing that response.

Update

Tried changing the controller to use @RequestParams and @RequestBody and got the same 415 error. The Multipart request is being handled but the Content-Type of the attached file is what is doing the 415.

I went ahead and added a ExceptionHandler to the Controller to try and catch the UnsupportedMediaTypeStatusException. I am not able to pass through the file to the ExceptionHandler though so this did not work. I could Override the default ExceptionHandler for the UnsupportedMediaTypeStatusException but I would prefer to avoid this if possible.

If it is useful I am posting my Angular service that is uploading the file. However because the error occurs even in tests I don't think there is a problem with the Angular.

upload.service.ts

post(file: File, fileName: string) {
const formData = new FormData();
formData.append('file', file, fileName);

let headers = new HttpHeaders();
headers = headers.delete('Content-Type');

this.http
  .post(this.API_URL, formData, { headers: headers, reportProgress: true })
  .subscribe();

}

Vikram Palakurthi :

Please use the @RequestPart("file") Mono<FilePart> file or @RequestPart("file") Flux<FilePart> instead of @RequestPart("file") MultipartFile file.

@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Mono<Void> save(@RequestPart("file") Mono<FilePart> file) {
        log.info("Storing a new file. Recieved by Controller");
        this.storageService.store(file);
        return Mono.empty();
    }

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=427232&siteId=1