Reputation: 341
I want to receive Multipart/form-data from a client (frontend for example). And then stream file content of form-data to another backend service.
For now i can read the whole file and pass it somewhere via byte[] (base64 string) like this:
@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;
}));
}
There're a few problems with this approach:
I've already tried sending file via asyncPart of WebClientBuilder:
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.asyncPart("document", document.content(), DataBuffer.class);
But i'm getting an error:
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: full code, which generates error
// 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 ...;
}
Upvotes: 3
Views: 11943
Reputation: 157
I have same issue with filename here, however as we are developing with Spring WebFlux our FilePart
is wrapped in Flux
@RequestMapping(
method = RequestMethod.POST,
value = "/api/endpoint"
produces = { "application/json" },
consumes = { "multipart/form-data" }
)
public <Mono<ResponseBody<Entity>> upload(@RequestPart("file") Flux<FilePart> file, ServerWebExchange exchange) {
Flux<DataBuffer> dataBufferFlux = file.flatMap(Part::content);
String filename = // TODO how to get it?
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.asyncPart("file", dataBufferFlux, DataBuffer.class)
.filename(filename);
...
}
Now the issue is how to set filename at MultiPartBodyBuilder
properly.
Upvotes: 0
Reputation: 341
Looks like i was managed to stream file, working code below:
// 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 ...;
}
In the original question code i've been missing:
builder.asyncPart("document", document.content(), DataBuffer.class).filename(document.filename());
Upvotes: 1