Spring Webflux 415 with MultipartFile 中文翻译:Spring Webflux 使用 MultipartFile 时出现 415 错误

12 浏览
0 Comments

Spring Webflux 415 with MultipartFile 中文翻译:Spring Webflux 使用 MultipartFile 时出现 415 错误

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

UploadController

@PostMapping( consumes = MediaType.MULTIPART_FORM_DATA_VALUE )
public Mono 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: 响应状态码为415,原因为"Content type 'image/png'不被支持"
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忽略写入该响应的内容。

0
0 Comments

Spring Webflux 415 with MultipartFile问题的出现原因是必须使用Flux或Mono作为多部分上传的类型。下面是解决该问题的方法。

可以通过以下步骤解决Spring Webflux 415 with MultipartFile问题:

1. 确保引入了spring-webflux的依赖:

   
       org.springframework.boot
       spring-boot-starter-webflux
   
   

2. 在控制器中使用Flux或Mono作为方法参数类型,以处理多部分上传的请求:

   import org.springframework.http.codec.multipart.FilePart;
   import org.springframework.http.codec.multipart.Part;
   import org.springframework.web.bind.annotation.PostMapping;
   import org.springframework.web.bind.annotation.RequestPart;
   import org.springframework.web.bind.annotation.RestController;
   import reactor.core.publisher.Flux;
   import reactor.core.publisher.Mono;
   
   @RestController
   public class MultipartController {
   
       @PostMapping("/upload")
       public Mono uploadFile(@RequestPart Flux parts) {
           // 处理上传的文件
           return parts
                   .ofType(FilePart.class)
                   .flatMap(filePart -> {
                       // 处理文件
                       return filePart.transferTo(new File("路径/文件名"));
                   })
                   .then(Mono.just("文件上传成功"));
       }
   }
   

3. 创建一个包含multipart上传请求的测试类:

   import org.junit.jupiter.api.Test;
   import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
   import org.springframework.http.HttpHeaders;
   import org.springframework.http.MediaType;
   import org.springframework.test.web.reactive.server.WebTestClient;
   import org.springframework.web.reactive.function.BodyInserters;
   
   @WebFluxTest
   public class MultipartControllerTest {
   
       @Autowired
       private WebTestClient webTestClient;
   
       @Test
       public void testUploadFile() {
           webTestClient.post()
                   .uri("/upload")
                   .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
                   .body(BodyInserters.fromMultipartData("file", new ClassPathResource("文件路径/文件名")))
                   .exchange()
                   .expectStatus().isOk()
                   .expectBody(String.class).isEqualTo("文件上传成功");
       }
   }
   

4. 运行测试类,确保多部分上传功能正常工作。

通过上述步骤,可以解决Spring Webflux 415 with MultipartFile问题,并成功处理多部分上传的请求。

0
0 Comments

Spring Webflux 415 with MultipartFile问题的出现的原因是使用了不支持的MultipartFile类型作为方法的参数。解决方法是使用FilePart类型的参数替代MultipartFile类型。

在上述代码中,使用了("file") Mono<FilePart> file("file") Flux<FilePart>来替代("file") MultipartFile file。在方法的注解中,指定了consumes为MediaType.MULTIPART_FORM_DATA_VALUE,表示接受的请求内容类型为multipart/form-data。

这样修改之后,可以通过Swagger或Postman来调用API进行测试。然而,使用Swagger时无法看到上传文件的按钮,需要参考下面的链接中的方法来在Postman中测试multipart示例。

参考链接:stackoverflow.com/a/16022213/2453985

0