【问题标题】:How to return application/pdf through Mono in a Reactive way如何以响应式方式通过 Mono 返回应用程序/pdf
【发布时间】: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 支持传输字节流。

标签: spring-boot spring-webflux project-reactor reactor spring-webclient


【解决方案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 变得稍微不小,这最终将停止工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-11
  • 1970-01-01
  • 2013-12-29
  • 1970-01-01
  • 2012-10-04
相关资源
最近更新 更多