I have a Flux of DataBuffer in my Spring Controller and I want to attach it to the StreamingResponseBody as a stream. I have read many answers for similar approaches, but nothing quite matches this. I do not want to load the entire file in memory. I want it streamed.
@GetMapping(value="/attachment")
public ResponseEntity<StreamingResponseBody> get(@PathVariable long attachmentId) {
Flux<DataBuffer> dataBuffer = this.myService.getFile(attachmentId);
StreamingResponseBody stream = out -> {
// what do do here?
}
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(stream);
}
}
EDIT: myService.getFile()
public Flux<DataBuffer> gteFile(long attachmentId) {
return this.webClient.get()
.uri("https://{host}/attachments/{attachmentId}", host, attachmentId)
.attributes(clientRegistrationId("attachmentClient"))
.accept(MediaType.ALL)
.exchangeToFlux(clientResponse -> clientResponse.bodyToFlux(DataBuffer.class));
}
If you're using WebFlux there's no need to return StreamingResponseBody
, you can just return Flux<DataBuffer>
directly, so it will be streaming and also non-blocking.
If you want to add some headers/customize your response, the you can return Mono<ResponseEntity<Flux<DataBuffer>>>
:
@GetMapping("/attachment/{attachmentId}")
public Mono<ResponseEntity<Flux<DataBuffer>>> get(@PathVariable long attachmentId) {
Flux<DataBuffer> dataBuffers = this.myService.getFile(attachmentId);
ContentDisposition contentDisposition = ContentDisposition.inline()
.filename("example.txt")
.build();
HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition(contentDisposition);
return Mono.just(ResponseEntity.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(dataBuffers));
}
If you want to use StreamingResponseBody
(which means you're using blocking Spring MVC on web-layer), then you can do the following:
StreamingResponseBody stream = outputStream ->
Mono.create(sink -> DataBufferUtils.write(dataBuffers, outputStream)
.subscribe(DataBufferUtils::release, sink::error, sink::success)
).block();
and then return it from your controller