web-dev-qa-db-ja.com

リアクティブWebクライアントが応答を発行しない

Spring ReactiveWebClientについて質問があります...数日前にSpringFrameworkの新しいリアクティブなもので遊ぶことにし、個人的な目的でのみデータをスクレイピングするための1つの小さなプロジェクトを作成しました。 (1つのWebページに複数のリクエストを行い、結果を結合します)。

リクエストを行うために新しいリアクティブWebClientを使い始めましたが、私が見つけた問題は、クライアントがすべてのリクエストに対して応答を発行しないことです。奇妙に聞こえます。これが私がデータをフェッチするためにしたことです:

_private Mono<String> fetchData(String uri) {
    return this.client
            .get()
            .uri(uri)
            .header("X-Fsign","SW9D1eZo")
            .retrieve()
            .bodyToMono(String.class)
            .timeout(Duration.ofSeconds(35))
            .log("category", Level.ALL, SignalType.ON_ERROR, SignalType.ON_COMPLETE, SignalType.CANCEL, SignalType.REQUEST);
}
_

そして、fetchDataを呼び出す関数:

_public Mono<List<Stat>> fetch() {
    return fetchData(URL)
            .map(this::extractUrls)
            .doOnNext(System.out::println)
            .doOnNext(s-> System.out.println("all ids are "+s.size()))
            .flatMapIterable(q->q)
            .map(s -> s.substring(7, 15))
            .map(s -> "http://d.flashscore.com/x/feed/d_hh_" + s + "_en_1") // list of N-length urls
            .flatMap(this::fetchData)
            .map(this::extractHeadToHead)
            .collectList();
}
_

および加入者:

_    FlashScoreService bean = ctx.getBean(FlashScoreService.class);
    bean.fetch().subscribe(s->{
        System.out.println("finished !!! " + s.size()); //expecting same N-length list size
    },Throwable::printStackTrace);
_

問題は、100を超えるリクエストをもう少し行った場合です。すべてのリクエストに対して応答が得られなかった、エラーがスローされなかった、またはエラーレスポンスコードが返され、リクエストの数とは異なるサイズでsubscribeメソッドが呼び出されました。

私が行ったリクエストは文字列のリスト(URL)に基づいており、すべての応答が送信された後、collectList()を使用しているため、すべてをリストとして受け取る必要があります。 100個のリクエストを実行すると、100個の応答のリストを受け取ることを期待していますが、実際には100個、96個などを受信することがあります...何かが静かに失敗する可能性があります。これは簡単に再現できます。これが私のgithubプロジェクトです リンク

サンプル出力:

_all ids are 176
finished !!! 171
_

デバッグ方法や間違っていることを教えてください。助けていただければ幸いです。

更新:

ログには、たとえば126のURLを渡した場合が表示されます。

_onNext(ReactorClientHttpResponse{request=[GET/some_url],status=200}) is called 121 times. May be here is the problem.
onComplete() is called 126 times which is the exact same length of the passed list of urls
_

しかし、onNext()またはonError()を呼び出さずに一部のリクエストを完了することができるのはなぜですか? (Monoでの成功とエラー)

問題はWebClientではなく、どこかにあると思います。環境またはサーバーがリクエストをブロックしていますが、エラーログを受け取る必要があるかもしれません。

ps。助けてくれてありがとう !

8
Nikolay Rusev

これはトリッキーなものです。受信した実際のHTTPフレームをデバッグすると、一部のリクエストに対する応答が実際に得られていないようです。 Wiresharkでもう少しデバッグすると、リモートサーバーがFIN, ACK TCPパケットで接続の終了を要求しており、クライアントがそれを確認しているようです。問題はこの接続です。最初のFIN, ACK TCPパケットの後に、別のGET要求を送信するためにプールから引き続き取得されます。

リモートサーバーが多数の要求を処理した後、接続を閉じている可能性があります。いずれにせよ、それは完全に合法的な行動です。私はこれを一貫して再現していないことに注意してください。

Workaround

クライアントで接続プールを無効にすることができます。これは遅くなり、明らかにこの問題を引き起こしません。そのためには、以下を使用します。

this.client = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(new Consumer<HttpClientOptions.Builder>() {
                    @Override
                    public void accept(HttpClientOptions.Builder builder) {
                        builder.disablePool();
                    }
                }))
                .build();

根本的な問題

根本的な問題は、TCP接続が応答を送信せずに閉じられたときにHTTPクライアントがonCompleteしてはならないことです。さらに良いことに、HTTPクライアントは接続が行われている間は接続を再利用しないでください詳細がわかり次第、ここに報告します。

3
Brian Clozel