如何在 Spring WebFlux 中从 Multipart/form-data 流式传输文件
How to stream file from Multipart/form-data in Spring WebFlux
我想从客户端(例如前端)接收 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;
}));
}
这种方法存在一些问题:
- 我不想将整个文件读入内存;
- 数组大小限制为 Integer.MAX_VALUE;
- 数组编码为base64字符串,占用额外内存;
- 由于我将整个数组放入 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 ...;
}
看起来我成功地传输了文件,工作代码如下:
// 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.content(), DataBuffer.class).filename(document.filename());
我想从客户端(例如前端)接收 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;
}));
}
这种方法存在一些问题:
- 我不想将整个文件读入内存;
- 数组大小限制为 Integer.MAX_VALUE;
- 数组编码为base64字符串,占用额外内存;
- 由于我将整个数组放入 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 ...;
}
看起来我成功地传输了文件,工作代码如下:
// 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.content(), DataBuffer.class).filename(document.filename());