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

人走茶凉 提交于 2020-06-25 03:26:12

问题


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.


回答1:


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.




回答2:


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.




回答3:


Same Problem with me.

I use Webflux Spring WebClient

I write style RouterFunction

My solution below,

ETaxServiceClient.java

final WebClient defaultWebClient;


public Mono<byte[]> eTaxPdf(String id) {
    return defaultWebClient
            .get()
            .uri("-- URL PDF File --")
            .accept(MediaType.APPLICATION_OCTET_STREAM)
            .exchange()
            .log("eTaxPdf -> call other service")
            .flatMap(response -> response.toEntity(byte[].class))
            .flatMap(responseEntity -> Mono.just(Objects.requireNonNull(responseEntity.getBody())));
}

ETaxHandle.java

@NotNull
public Mono<ServerResponse> eTaxPdf(ServerRequest sr) {
    Consumer<HttpHeaders> headers = httpHeaders -> {
        httpHeaders.setCacheControl(CacheControl.noCache());
        httpHeaders.setContentDisposition(
                ContentDisposition.builder("inline")
                        .filename(sr.pathVariable("id") + ".pdf")
                        .build()
        );
    };
    return successPDF(eTaxServiceClient
            .eTaxPdf(sr.pathVariable("id"))
            .switchIfEmpty(Mono.empty()), headers);
}

ETaxRouter.java

@Bean
public RouterFunction<ServerResponse> routerFunctionV1(ETaxHandle handler) {
    return route()
            .path("/api/v1/e-tax-invoices", builder -> builder
                    .GET("/{id}", handler::eTaxPdf)
            )
            .build();
}

CommonHandler.java

Mono<ServerResponse> successPDF(Mono<?> mono, Consumer<HttpHeaders> headers) {
    return ServerResponse.ok()
            .headers(headers)
            .contentType(APPLICATION_PDF)
            .body(mono.map(m -> m)
                    .subscribeOn(Schedulers.elastic()), byte[].class);
}

Result: Successfully displayed on the browser.

Work for me.



来源:https://stackoverflow.com/questions/46643858/how-to-serve-files-pdf-files-the-reactive-way-in-spring

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!