web-dev-qa-db-ja.com

block()/ blockFirst()/ blockLast()はbodyToMono AFTER exchange()を呼び出すとエラーをブロックします

Webfluxを使用して生成されたファイルを別の場所にストリーミングしようとしていますが、ファイルの生成でエラーが発生した場合、APIは成功を返しますが、ファイル自体ではなく、ファイルの生成中にDTOがエラーの詳細を示します。これは非常に古く、設計が不十分なAPIを使用しているため、ポストおよびAPI設計の使用を許しません。

API呼び出し(exchange())からの応答はClientResponseです。ここから、ファイルにストリーミングできるbodyToMonoを使用してByteArrayResourceに変換できます。または、ファイルの作成でエラーが発生した場合は、bodyToMonoを使用してDTOに変換することもできます。ただし、ClientResponseのヘッダーの内容によっては、どちらも実行できないようです。

実行時に、次の原因によりIllegalStateExceptionが発生します。

block()/ blockFirst()/ blockLast()はブロッキングですが、スレッドreactor-http-client-epoll-12ではサポートされていません

私の問題は、同じ関数チェーンでblock()を2回呼び出せないことだと思います。

私のコードスニペットはそうです:

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を別の方法で処理したいと思います。

これは可能ですか?

7
DaithiG

最初に、この使用例を解決するコードスニペットを理解するのに役立ついくつかのこと。

  1. 反応型を返すメソッド内でブロッキングメソッドを呼び出さないでください。アプリケーションの数少ないスレッドの1つをブロックし、アプリケーションにとって非常に悪い
  2. とにかく、Reactor 3.2以降、 反応パイプライン内でブロックするとエラーがスローされます
  3. コメントで提案されているようにsubscribeを呼び出すのも良い考えではありません。それは多かれ少なかれ、別のスレッドでタスクとしてそのジョブを開始することに似ています。完了するとコールバックが発生します(subscribeメソッドにはラムダを指定できます)が、実際には現在のパイプラインとそのタスクを分離しています。この場合、応答本文全体を読み取ってファイルに書き込む機会を得る前に、クライアントHTTP応答を閉じてリソースをクリーンアップできます。
  4. 応答全体をメモリにバッファリングしたくない場合、SpringはDataBufferを提供します(プールできるByteBufferインスタンスを考えてください)。
  5. テストケースなどで、実装しているメソッド自体がブロックしている(たとえば、voidを返す)場合は、blockを呼び出すことができます。

これを行うために使用できるコードスニペットを次に示します。

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インスタンスを保持し(リアクティブオペレーターはパフォーマンス上の理由で値をプリフェッチします)、バイトがリアクティブな方法で到着するとバイトを書き込みます。

5
Brian Clozel

私の場合、exchangeblockretrieveに置き換える必要がありました。

これによりエラーが発生しました:

Mono<Boolean> booleanMono = webClient.get()
                  .exchange().block().bodyToMono(Boolean.class);

上記を次の行で置き換えると、私の問題が解決しました:

Mono<Boolean> booleanMono = webClient.get()
                 .retrieve().bodyToMono(Boolean.class);
0
KernelMode
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();
0
Nurlan Rysbaev