调用bodyToMono AFTER exchange()后,block()/ blockFirst()/ blockLast()发生阻塞错误

时间:2018-07-20 20:26:33

标签: java reactive-programming spring-webflux project-reactor

我正在尝试使用Webflux将生成的文件流式传输到另一个位置,但是,如果文件的生成遇到错误,则api返回成功,但DTO会在生成文件时详细说明错误,而不是文件本身。这使用的是非常古老且设计不佳的api,因此请原谅post和api设计的使用。

api调用(exchange())的响应是ClientResponse。从这里,我可以使用bodyToMono转换为ByteArrayResource并将其传输到文件,或者,如果在创建文件时出错,那么我也可以使用bodyToMono转换为DTO。但是,我似乎既不执行任何操作,也不依赖于ClientResponse标头的内容。

在运行时我收到由

引起的IllegalStateException
  

block()/ blockFirst()/ blockLast()正在阻止,这在线程反应器-http-client-epoll-12中不支持

我认为我的问题是我无法在同一功能链中两次调用block()。

我的代码段如下:

webClient.post()
        .uri(uriBuilder -> uriBuilder.path("/file/")
                                      .queryParams(params).build())
        .exchange()
        .doOnSuccess(cr -> {
                if (MediaType.APPLICATION_JSON_UTF8.equals(cr.headers().contentType().get())) {
                    NoPayloadResponseDto dto = cr.bodyToMono(NoPayloadResponseDto.class).block();
                    createErrorFile(dto);
                }
                else {
                    ByteArrayResource bAr = cr.bodyToMono(ByteArrayResource.class).block();
                    createSpreadsheet(bAr);
                }
            }
        )
        .block();

基本上我想根据标头中定义的MediaType来不同地处理ClientResponse。

这可能吗?

5 个答案:

答案 0 :(得分:6)

要在服务器请求池之外执行客户端请求,请使用 myWebClientMono.share().block();

答案 1 :(得分:5)

首先,几件事将帮助您了解解决此用例的代码段。

  1. 永远不要在返回反应类型的方法中调用阻塞方法;您将阻塞应用程序的几个线程之一,这对应用程序来说是非常糟糕的
  2. 从反应堆3.2开始,blocking within a reactive pipeline throws an error
  3. 按照注释中的建议致电subscribe也不是一个好主意。这或多或少就像在一个单独的线程中以一项任务开始该工作。完成后,您将获得一个回调(可以为subscribe方法指定lambda),但实际上您正在将当前管道与该任务分离。在这种情况下,在您有机会读取完整的响应正文并将其写入文件之前,可以关闭客户端HTTP响应并清理资源。
  4. 如果您不想将整个响应缓冲在内存中,Spring会提供DataBuffer(认为可以缓冲的ByteBuffer实例)。
  5. 例如,在测试用例中,如果要实现的方法本身是阻塞的(例如返回void),则可以调用阻塞。

以下是您可以用来执行此操作的代码段:

Mono<Void> fileWritten = WebClient.create().post()
        .uri(uriBuilder -> uriBuilder.path("/file/").build())
        .exchange()
        .flatMap(response -> {
            if (MediaType.APPLICATION_JSON_UTF8.equals(response.headers().contentType().get())) {
                Mono<NoPayloadResponseDto> dto = response.bodyToMono(NoPayloadResponseDto.class);
                return createErrorFile(dto);
            }
            else {
                Flux<DataBuffer> body = response.bodyToFlux(DataBuffer.class);
                return createSpreadsheet(body);
            }
        });
// Once you get that Mono, you should give plug it into an existing
// reactive pipeline, or call block on it, depending on the situation

如您所见,我们在任何地方都没有阻塞,并且处理I / O的方法返回Mono<Void>,这与done(error)回调的反应性等效项,表示何时完成操作以及是否发出信号错误发生了。

由于我不确定createErrorFile方法应该做什么,因此我为createSpreadsheet提供了一个示例,该示例只是将正文字节写入文件中。请注意,由于数据缓冲区可能被回收/池化,因此我们需要在完成后释放它们。

private Mono<Void> createSpreadsheet(Flux<DataBuffer> body) {
    try {
        Path file = //...
        WritableByteChannel channel = Files.newByteChannel(file, StandardOpenOption.WRITE);
        return DataBufferUtils.write(body, channel).map(DataBufferUtils::release).then();
    } catch (IOException exc) {
        return Mono.error(exc);
    }
}

通过此实现,您的应用程序将在给定的时间在内存中保存几个DataBuffer实例(反应性运算符出于性能原因正在预取值),并将以反应性方式写入字节。

答案 2 :(得分:2)

正如在投票最多的答案中所述,永远不应该阻止。就我而言,这是唯一的选择,因为我们在命令式代码中使用了反应式库。可以通过wrapping the mono in a processor进行屏蔽:

myMono.toProcessor().block()

答案 3 :(得分:-2)

对我来说,添加 web 依赖解决了这个问题。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

答案 4 :(得分:-5)

RestResultMessage message= createWebClient()
                .get()
                .uri(uri)
                .exchange()
                .map(clientResponse -> {
                    //delegation
                    ClientResponseWrapper wrapper = new 
                                 ClientResponseWrapper(clientResponse);
                    return Mono.just(wrapper);
                })
                .block() //wait until request is not done
                .map(result -> {  
                    //convert to any data
                    if (!result.statusCode().isError()){
                       //extract the result from request
                        return create(RestResultMessage.Result.success, result.bodyToMono(String.class).block());}
                    } else {
                        return create(RestResultMessage.Result.error, result.statusCode().name());
                    }
                })
                .block();