javaspringspring-bootspring-webfluxproject-reactor

How to convert a Flux<DataBuffer> into a StreamingResponseBody in a Spring Controller


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));
}

Solution

  • 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