Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serve files/PDF files the reactive way in spring

I have the following endpoint code to serve PDF files.

@RequestMapping
ResponseEntity<byte[]> getPDF() {
  File file = ...;
  byte[] contents = null;
  try {
    try (FileInputStream fis = new FileInputStream(file)) {
      contents = new byte[(int) file.length()];
      fis.read(contents);
    }
  } catch(Exception e) {
    // error handling
  }
  HttpHeaders headers = new HttpHeaders();
  headers.setContentDispositionFormData(file.getName(), file.getName());
  headeres.setCacheControl("must-revalidate, post-check=0, pre-check=0");
  return new ResponseEntity<>(contents, headers, HttpStatus.OK);
}

How can I convert above into a reactive type Flux/Mono and DataBuffer.

I have check DataBufferUtils but It doesn't seem to offer what I needed. I didn't find any example either.

like image 608
Bk Santiago Avatar asked Oct 09 '17 10:10

Bk Santiago


People also ask

Are spring integration flows a good fit for Reactive Streams applications?

From here it may look like Spring Integration flows are really a good fit for writing Reactive Streams applications when we apply some reactive framework operators on endpoints, but in fact the problems is much broader and we need to keep in mind that not all endpoints (e. g. JdbcMessageHandler) can be processed in a reactive stream transparently.

What is reactive programming in spring?

Reactive programming is non blocking, asynchronous and deals with data streams not single data and also supports backpressure. Spring supports reactive programming using “Project Reactor” , a platform which supports reactive programming.

When to use reactivemessagesourceproducer?

This ReactiveMessageSourceProducercould be used for any use-case when a a polling channel adapter’s features should be turned into a reactive, on demand solution for any existing MessageSource<?>implementation. Splitter and Aggregator

How to extend from the spring crudrepository?

By extending from the Spring CrudRepository, we will have some methods for our data repository implemented, including findAll () and findOne (). This way we do not have to write a lot of boilerplate code.


2 Answers

The easiest way to achieve that would be with a Resource.

@GetMapping(path = "/pdf", produces = "application/pdf")
ResponseEntity<Resource> getPDF() {
  Resource pdfFile = ...;
  HttpHeaders headers = new HttpHeaders();
  headers.setContentDispositionFormData(file.getName(), file.getName());
  return ResponseEntity
    .ok().cacheControl(CacheControl.noCache())
    .headers(headers).body(resource);
}

Note that DataBufferUtils has some useful methods there that convert an InputStream to a Flux<DataBuffer>, like DataBufferUtils#read(). But dealing with a Resource is still superior.

like image 190
Brian Clozel Avatar answered Nov 04 '22 03:11

Brian Clozel


Below is the code to return the attachment as byte stream:

@GetMapping(
        path = "api/v1/attachment",
        produces = APPLICATION_OCTET_STREAM_VALUE
)
public Mono<byte[]> getAttachment(String url) {
    return rest.get()
            .uri(url)
            .exchange()
            .flatMap(response -> response.toEntity(byte[].class));
}

This approach is very simple but the disadvantage is it will the load the entire attachment into memory. If the file size is larger, then it will be a problem.

To overcome we can use DataBuffer which will send the data in chunks. This is an efficient solution and it will work for any large size file. Below is the modified code using DataBuffer:

@GetMapping(
        path = "api/v1/attachment",
        produces = APPLICATION_OCTET_STREAM_VALUE
)
public Flux<DataBuffer> getAttachment(String url) {
    return rest.get()
            .uri(url)
            .exchange()
            .flatMapMany(response -> response.toEntity(DataBuffer.class));
}

In this way, we can send attachments in a reactive fashion.

like image 41
Sudharsan Thumatti Avatar answered Nov 04 '22 03:11

Sudharsan Thumatti