我目前正在尝试从 Angular 4 前端向 Spring Webflux 控制器上传文件。控制器能够读取 @RequestPart 值,但会抛出 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();
}
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创建了一个测试。
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();
}
我使用MockMultipartFile时遇到了新的500错误,错误信息如下:
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会抛出一个不支持的媒体类型异常,以及我如何指示Spring Webflux忽略写入该响应的任何内容。更新:
尝试将控制器更改为使用@RequestParams和@RequestBody,并得到相同的415错误。多部分请求正在处理,但所附文件的Content-Type是导致415错误的原因。
我已经向控制器添加了一个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();
}