如何以响应式方式通过 Mono 返回应用程序/pdf

Posted

技术标签:

【中文标题】如何以响应式方式通过 Mono 返回应用程序/pdf【英文标题】:How to return application/pdf through Mono in a Reactive way 【发布时间】:2021-10-11 23:25:35 【问题描述】:

我目前正在使用 Spring WebFlux 尝试构建一个异步端点,该端点通过 Web 客户端从第三方端点获取 PDF,然后将 PDF 返回给我们的 API 使用者。但是,由于以下异常,我正在努力返回内容类型为application/pdfMono<ResponseEntity>

Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class reactor.core.publisher.MonoMapFuseable] with preset Content-Type 'application/pdf']

这里是控制器的实现。我的问题是:

我的实现方向是否正确,还是需要创建某种转换器? Mono<ResponseEntity> 是否甚至支持返回 PDF 作为响应正文?
    @RequestMapping(value="/get-pdf", method = RequestMethod.GET)
    public Mono<ResponseEntity> getPDFAsync() 

        String url = "http://some-end-point";
        WebClient client = WebClient.create(url);

        return client.get()
                .accept(MediaType.APPLICATION_PDF)
                .exchangeToMono(response ->
                        Mono.just(ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF)
                            .body(response.bodyToMono(ByteArrayResource.class)
                                    .map(byteArrayResource -> byteArrayResource.getByteArray())
                        )));
    

【问题讨论】:

这能回答你的问题吗? Return generated pdf using spring MVC @Toerktumlare 这不是一种非常被动的方法,请参阅我的答案以获得一种可能的解决方案。 我并没有声称它是一个完整的服务答案,但它为您提供了如何完成的指南,然后您可以将其调整为响应式。例如,您在我发布的链接中查看的第二个问题,它们传输字节数组,而 webflux 支持传输字节流。 【参考方案1】:

要响应式下载文件,您可以将文件作为Flux&lt;DataBuffer&gt; 提供,其中DataBufferorg.springframework.core.io.buffer.DataBuffer,如下所示:

    // some shared buffer factory.
    private final DataBufferFactory dataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);



    @RequestMapping(value = "/download",
            method = RequestMethod.GET,
            produces = MediaType.APPLICATION_PDF_VALUE
    )
    public Mono<ResponseEntity<Flux<DataBuffer>>> downloadDocument(
            ...
    ) 
        return Mono.fromCallable(() -> 
           return ResponseEntity.ok(
             DataBufferUtils.read(
               new File("somepdf.pdf").toPath(), 
               dataBufferFactory, 
               8096
           ))
        );
    

或者更具体地说,由于您似乎在使用 WebFlux WebClient,因此您可以将响应主体通量直接转发到您自己的响应中,而不必先缓冲完整的响应:

    @RequestMapping(value = "/download",
            method = RequestMethod.GET,
            produces = MediaType.APPLICATION_PDF_VALUE
    )
    public Mono<ResponseEntity<Flux<DataBuffer>>> downloadDocument(
            ...
    ) 
          String url = "http://some-end-point";
          WebClient client = WebClient.create(url);

          return client.get()
                  .accept(MediaType.APPLICATION_PDF)
                  .exchange()
                  .map(response -> response.bodyToFlux(DataBuffer.class))
                  .map(ResponseEntity::ok);
    

提示:我希望您重用 WebClient 实例,而不是在每个请求上都实例化一个新实例。

【讨论】:

这仍然给出错误“无转换器”等。 @user3067860 你确定你有正确的 WebFlux 依赖项吗?这应该在任何 webflux 项目上开箱即用,但不适用于具有反应器依赖项的 spring MVC。我有一个带有反应器库的弹簧 MVC 并且必须编写自定义转换器的情况。如果您遇到这种情况,请随时发布新问题并告诉我,我会将转换器发送给您。【参考方案2】:

我找到了答案!简而言之,返回Mono&lt;byte[]&gt;,并将produces = MediaType.APPLICATION_PDF_VALUE 添加到@RequestMapping 有效。请参见下面的示例。

    @RequestMapping(value="/get-pdf",  produces = MediaType.APPLICATION_PDF_VALUE,  method = RequestMethod.GET)
public Mono<byte[]> getPdf() 
    
    String url = "some-end-point";
    WebClient client = WebClient.create(url);

    return client.get()
            .accept(MediaType.APPLICATION_PDF)
            .exchangeToMono(response -> response
                    .bodyToMono(ByteArrayResource.class))
            .map(byteArrayResource -> byteArrayResource.getByteArray());

【讨论】:

如果您的 PDF 变得稍微不小,这最终将停止工作。

以上是关于如何以响应式方式通过 Mono 返回应用程序/pdf的主要内容,如果未能解决你的问题,请参考以下文章

Java中Mono的条件响应式执行[关闭]

如何对具有 Mono 返回类型的方法进行 junit 测试

学习响应式编程 Reactor - reactor 基础

如何在 Spring Boot 中修改 Mono 对象的属性而不阻塞它

Spring 5 新特性:函数式Web框架

webflux响应式编程中访问Post方法json RequestBody参数