【问题标题】:How to stream file from Multipart/form-data in Spring WebFlux如何在 Spring WebFlux 中从 Multipart/form-data 流式传输文件
【发布时间】:2022-01-21 06:13:10
【问题描述】:

我想从客户端(例如前端)接收 Multipart/form-data。然后将表单数据的文件内容流式传输到另一个后端服务。

现在我可以读取整个文件并通过 byte[](base64 字符串)将其传递到某处,如下所示:

@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<ResponseType> upload(@RequestPart("document") FilePart document, 
                                 @RequestPart("stringParam") String stringParam) {
    return service.upload(document, stringParam);
}

// Casually convert to single byte array...
private Mono<byte[]> convertFilePartToByteArray(FilePart filePart) {
    return Mono.from(filePart
            .content()
            .map(dataBuffer -> {
                byte[] bytes = new byte[dataBuffer.readableByteCount()];
                dataBuffer.read(bytes);
                DataBufferUtils.release(dataBuffer);

                return bytes;
            }));
}

这种方法存在一些问题:

  1. 我不想将整个文件读入内存;
  2. 数组大小限制为 Integer.MAX_VALUE;
  3. 数组编码为base64字符串,占用额外内存;
  4. 由于我将整个数组放入 Mono - “spring.codec.max-in-memory-size” 必须大于数组大小。

我已经尝试通过 WebClientBuilder 的 asyncPart 发送文件:

MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.asyncPart("document", document.content(), DataBuffer.class);

但我收到一个错误:

java.lang.IllegalStateException: argument type mismatch
Method [public reactor.core.publisher.Mono<> upload(**org.springframework.http.codec.multipart.FilePart**,java.lang.String)] with argument values:
[0] [type=**org.springframework.http.codec.multipart.DefaultParts$DefaultFormFieldPart**]

UPD:完整代码,会产生错误

// External controller for client.
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/v2")
public Mono<DocumentUploadResponse> uploadV2(@RequestPart("document") FilePart document,
                                             @RequestPart("stringParam") String stringParam) {
    MultipartBodyBuilder builder = new MultipartBodyBuilder();
    builder.asyncPart("document", document.content(), DataBuffer.class);
    builder.part("stringParam", stringParam);

    WebClient webClient = webClientBuilder.build();
    return webClient.post()
            .uri("URL_TO_ANOTHER_SERVICE")
            .contentType(MediaType.MULTIPART_FORM_DATA)
            .body(BodyInserters.fromMultipartData(builder.build()))
            .retrieve()
            .bodyToMono(FileMetaDto.class)
            .map(DocumentUploadResponse::new);
}

// Internal service controller.
@PostMapping(path = "/upload/v2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<FileMetaDto> upload(@RequestPart("document") FilePart document,
                                @RequestPart("stringParam") String stringParam) {
    return ...;
}

【问题讨论】:

  • 您发布的错误,与上面的代码不匹配。生成错误的不是上面的代码。
  • @Toerktumlare,感谢您的评论!我已经用 WebClient 方法的完整示例更新了问题。
  • 能否请您删除BodyInserters.fromMultipartData 并将builder.build() 传递给body 运算符
  • @Toerktumlare,谢谢,但现在body 只需要BodyInserter 接口。根据文档bodyValue 可能会使用方法。我试过了,但也有同样的异常。
  • 那不正确,body也可以取Publisher等多种类型。请查看文档docs.spring.io/spring-framework/docs/current/reference/html/… 文档清楚地说明Once a MultiValueMap is prepared, the easiest way to pass it to the WebClient is through the body method, 包含示例'

标签: java spring spring-webflux


【解决方案1】:

看起来我设法流式传输文件,工作代码如下:

// External controller for client.
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/v2")
public Mono<DocumentUploadResponse> uploadV2(@RequestPart("document") FilePart document,
                                             @RequestPart("stringParam") String stringParam) {
    MultipartBodyBuilder builder = new MultipartBodyBuilder();
    builder.asyncPart("document", document.content(), DataBuffer.class).filename(document.filename());
    builder.part("stringParam", stringParam);

    WebClient webClient = webClientBuilder.build();
    return webClient.post()
            .uri("URL_TO_ANOTHER_SERVICE")
            .contentType(MediaType.MULTIPART_FORM_DATA)
            .body(BodyInserters.fromMultipartData(builder.build()))
            .retrieve()
            .bodyToMono(FileMetaDto.class)
            .map(DocumentUploadResponse::new);
}

// Internal service controller.
@PostMapping(path = "/upload/v2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<FileMetaDto> upload(@RequestPart("document") FilePart document,
                                @RequestPart("stringParam") String stringParam) {
    return ...;
}

在我丢失的原始问题代码中:
builder.asyncPart("document", document.content(), DataBuffer.class).filename(document.filename());

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-13
    • 2017-06-09
    • 2011-11-19
    • 2022-09-22
    • 1970-01-01
    相关资源
    最近更新 更多