如何在 spring 中以反应方式提供 files/PDF 文件
How to serve files/PDF files the reactive way in spring
我有以下端点代码来提供 PDF 文件。
@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);
}
如何将上面的内容转换为响应式 Flux/Mono
和 DataBuffer
.
我检查过 DataBufferUtils
但它似乎没有提供我需要的东西。我也没有找到任何例子。
最简单的方法是使用 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);
}
请注意 DataBufferUtils
有一些有用的方法可以将 InputStream
转换为 Flux<DataBuffer>
,例如 DataBufferUtils#read()
。但是对付Resource
还是有优势的。
下面是 return 附件作为字节流的代码:
@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));
}
这种方法很简单,但缺点是会将整个附件加载到内存中。如果文件比较大,那就麻烦了。
为了解决这个问题,我们可以使用 DataBuffer
来分块发送数据。这是一个有效的解决方案,适用于任何大文件。下面是使用 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));
}
通过这种方式,我们可以以响应方式发送附件。
和我一样的问题。
我用Webflux Spring WebClient
我写的风格RouterFunction
下面是我的解决方案,
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);
}
结果:成功显示在浏览器上
为我工作。
我有以下端点代码来提供 PDF 文件。
@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);
}
如何将上面的内容转换为响应式 Flux/Mono
和 DataBuffer
.
我检查过 DataBufferUtils
但它似乎没有提供我需要的东西。我也没有找到任何例子。
最简单的方法是使用 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);
}
请注意 DataBufferUtils
有一些有用的方法可以将 InputStream
转换为 Flux<DataBuffer>
,例如 DataBufferUtils#read()
。但是对付Resource
还是有优势的。
下面是 return 附件作为字节流的代码:
@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));
}
这种方法很简单,但缺点是会将整个附件加载到内存中。如果文件比较大,那就麻烦了。
为了解决这个问题,我们可以使用 DataBuffer
来分块发送数据。这是一个有效的解决方案,适用于任何大文件。下面是使用 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));
}
通过这种方式,我们可以以响应方式发送附件。
和我一样的问题。
我用Webflux Spring WebClient
我写的风格RouterFunction
下面是我的解决方案,
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);
}
结果:成功显示在浏览器上
为我工作。