What is the correct reactive pattern in Java Spring WebFlux to return a (large) file from the REST controller?

Viewed 72

Situation:

  • I have a non-reactive legacy library which expects an OutputStream and writes its output into it, the method signature is something like void produceData(OutputStream stream)
  • I want to expose the output from the method via a web service as a PDF file
  • The file can be sometimes very large
  • I am using the reactive framework Spring WebFlux

Motivation:

  • I want my code to stay "as reactive as possible"
  • I do not want to allocate a byte array for all the data at once, I want to maximally exploit the fact that the legacy method writes the output to OutputStream

Question:

  • What is the correct reactive pattern in WebFlux to return a large file in general?
  • What is the correct reactive pattern in WebFlux to return a large file when the source of the data is a non-reactive method writing to a given OutputStream?
  • I should also be able to let the client understand that the output from the web service is a PDF file with a proper name.

What I am considering:

  • Mono<ByteArrayResource> getData() - I don't want to use it because I would have to allocate the byte array for the whole content.
  • In the non-reactive world, I would just inject the ServletResponse object into the controller method and passed its output stream ServletResponse.getOutputStream() to my legacy method, but I don't know how to do this in the reactive world and if it is even recommended at all.
  • Mono<DataBuffer> getData() - In the controller code, I can easily obtain a new DataBuffer from a DefaultDataBufferFactory and use its asOutputStream() method, but then I don't know how to handle properly so the DataBuffer is released after the data are consumed by the client.

What is the correct reactive pattern to handle situations like this?

1 Answers

Its completely ok to call a no reactive method . Its only important to keep the reactive flow reactive instead of blocking it

for example

someRepository.findById( id )
               .map( entity -> entity.setValue( value ) )
               .filter( entity -> entity.someCondition > other condition )
               .map( entity -> someNonReactiveLibrary.function(entity.getAttribute())) //non-reactive method
                .collect( Collectors.toList());

If you see the this entire block executes in one thread and it is going to be sequential. In this particular flow, you can including non-reactive method and it is completely normal.

Related