带有 MultipartFile 的 Spring Webflux 415

Posted

技术标签:

【中文标题】带有 MultipartFile 的 Spring Webflux 415【英文标题】:Spring Webflux 415 with MultipartFile 【发布时间】:2018-09-02 14:36:29 【问题描述】:

我目前正在尝试将文件从 Angular 4 前端上传到 Spring Webflux 控制器。控制器能够读取 @RequestPart 值,但会抛出 415 UnsupportedMediaTypeStatusException。

上传控制器

@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();

log.info() 方法没有执行,所以似乎在方法执行之前就抛出了错误。

错误信息

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]

依赖 Spring Webflux 应该使用 org.synchronoss.cloud.nio.multipart 已加载,所以我不完全理解 Spring 抛出 415 错误的原因。

我在 Spring 中使用 WebClient 创建了一个测试

网络测试

    @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();

我收到了一个新的 500 错误,而不是使用 MockMultipartFile 和此消息

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"])

我想了解的是为什么 Spring 会抛出 Unsupported Media Type 异常,以及我如何指示 Spring Webflux 忽略正在编写该响应的任何内容。

更新

尝试将控制器更改为使用 @RequestParams 和 @RequestBody 并得到相同的 415 错误。正在处理 Multipart 请求,但附件的 Content-Type 正在执行 415。

我继续向 Controller 添加了一个 ExceptionHandler 以尝试捕获 UnsupportedMediaTypeStatusException。我无法将文件传递给 ExceptionHandler,所以这不起作用。我可以为 UnsupportedMediaTypeStatusException 覆盖默认的 ExceptionHandler,但我希望尽可能避免这种情况。

如果有用,我将发布我正在上传文件的 Angular 服务。但是,由于即使在测试中也会发生错误,所以我认为 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();

【问题讨论】:

也许试试@RequestParam?不确定您的用例,但也许这会有所帮助:***.com/a/38156711/8160553 继续尝试并尝试使用 @RequestBody 只是为了尝试仍然得到 415。我将尝试执行 @ControllerAdvice 来捕获 CodecException 并看看我是否可以只需手动保存即可 为了更好的可见性,我会编辑帖子以添加您已经尝试过的方法。 看起来你必须使用@RequestPart("file") Mono 部分而不是@RequestPart("file")MultipartFile 文件,请参阅此线程***.com/questions/47703924/…。如果可行,请告诉我,我会写下领取赏金的答案;-) 看起来像重复: 【参考方案1】:

请使用@RequestPart("file") Mono&lt;FilePart&gt; file@RequestPart("file") Flux&lt;FilePart&gt; 而不是@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. Received by Controller");
        this.storageService.store(file);
        return Mono.empty();
    

【讨论】:

我在我使用反应弹簧的一个项目中做了同样的事情。但我无法测试它,我如何通过 Swagger 或邮递员调用 API。 Swagger 不显示上传文件按钮。当我使用邮递员时,它会根据我们上传的文件更改内容类型,这将再次抛出不受支持的内容类型 您可能想参考下面的链接,了解如何使用邮递员中的文件测试多部分示例。 ***.com/a/16022213/2453985【参考方案2】:

您必须使用 Flux 或 Mono 作为分段上传的类型。 https://github.com/hantsy/spring-reactive-sample/blob/master/multipart/src/main/java/com/example/demo/MultipartController.java

【讨论】:

以上是关于带有 MultipartFile 的 Spring Webflux 415的主要内容,如果未能解决你的问题,请参考以下文章

利用spring的MultipartFile实现文件上传原

在 Spring 中使用 RestTemplate 作为请求参数的 MultipartFile + String

在 Spring Boot 中更改上传的 MultipartFile 的编码

[转]spring MultipartFile 转 File

无法在 Spring 中将 MultipartFile 转换为 Blob

Spring boot - 如何在 rest 应用程序中验证 Multipartfile