1. 概要

通常、WebアプリケーションでHTTP呼び出しを操作する場合、要求と応答に関するある種のメトリックをキャプチャする方法が必要になります。 通常、これは、アプリケーションが行うHTTP呼び出しのサイズと頻度を監視するためのものです。

OkHttp は、AndroidおよびJavaアプリケーション用の効率的なHTTPおよびHTTP/2クライアントです。 前のチュートリアルでは、OkHttpの操作方法の基本について説明しました。

このチュートリアルでは、イベントを使用してこれらのタイプのメトリックをキャプチャする方法についてすべて学習します。

2. イベント

名前が示すように、イベントは、HTTP呼び出しのライフサイクル全体に関連するアプリケーションメトリックを記録するための強力なメカニズムを提供します。

関心のあるすべてのイベントをサブスクライブするには、 EventListener を定義し、キャプチャするイベントのメソッドをオーバーライドする必要があります。

これは、たとえば、失敗した呼び出しと成功した呼び出しのみを監視する場合に特に役立ちます。 その場合、イベントリスナークラス内のそれらのイベントに対応する特定のメソッドをオーバーライドするだけです。 これについては後で詳しく説明します。

アプリケーションでイベントを使用することには、少なくとも2つの利点があります。

  • イベントを使用して、アプリケーションが行うHTTP呼び出しのサイズと頻度を監視できます
  • これは、アプリケーションのどこにボトルネックがあるかをすばやく判断するのに役立ちます

最後に、イベントを使用して、ネットワークに根本的な問題があるかどうかを判断することもできます。

3. 依存関係

もちろん、標準のokhttp依存関係pom.xmlに追加する必要があります。

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.1</version>
</dependency>

また、テスト専用の別の依存関係も必要になります。 OkHttp mockwebserverアーティファクトを追加しましょう:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>mockwebserver</artifactId>
    <version>4.9.1</version>
    <scope>test</scope>
</dependency>

必要な依存関係がすべて構成されたので、先に進んで最初のイベントリスナーを作成できます。

4. イベントの方法と順序

ただし、独自のイベントリスナーの定義を開始する前に、一歩下がって、使用可能なイベントメソッドと、でイベントが到着すると予想される順序について簡単に説明します。 これは、後でいくつかの実際の例に飛び込むときに役立ちます。

リダイレクトや再試行なしで成功したHTTP呼び出しを処理していると仮定しましょう。 次に、この典型的なメソッド呼び出しのフローを期待できます。

4.1. callStart()

このメソッドはエントリポイントであり、呼び出しをキューに入れるか、クライアントが実行するとすぐに呼び出されます。

4.2. proxySelectStart()および proxySelectEnd()

最初のメソッドは、プロキシの選択前、および同様にプロキシの選択後に呼び出されます。これには、試行される順序でのプロキシのリストが含まれます。 もちろん、プロキシが設定されていない場合、このリストは空になる可能性があります。

4.3. dnsStart()および dnsEnd()

これらのメソッドは、DNSルックアップの直前、およびDNSが解決された直後に呼び出されます。

4.4. connectStart()および connectEnd()

これらのメソッドは、ソケット接続を確立して閉じる前に呼び出されます。

4.5. secureConnectStart()および secureConnectEnd()

呼び出しでHTTPSを使用し、connectStartconnectEndの間に散在している場合、これらの安全な接続のバリエーションがあります。

4.6. connectionAcquired()および connectionReleased()

接続が取得または解放された後に呼び出されます。

4.7. requestHeadersStart()および requestHeadersEnd()

これらのメソッドは、リクエストヘッダーを送信する直前と直後に呼び出されます。

4.8. requestBodyStart()および requestBodyEnd()

名前が示すように、リクエスト本文を送信する前に呼び出されます。 もちろん、これは本文を含むリクエストにのみ適用されます。

4.9. responseHeadersStart()および responseHeadersEnd()

これらのメソッドは、応答ヘッダーがサーバーから最初に返されるときと、受信された直後に呼び出されます。

4.10. responseBodyStart()および responseBodyEnd()

同様に、応答本文がサーバーから最初に返されるとき、および本文が受信された直後に呼び出されます。

これらの方法に加えて、障害をキャプチャするために使用できる3つの追加の方法もあります。

4.11. callFailed() responseFailed()、および requestFailed()

呼び出しが永続的に失敗した場合、要求に書き込み失敗があるか、応答に読み取り失敗があります。

5. 単純なイベントリスナーの定義

まず、独自の偶数リスナーを定義することから始めましょう。 物事を本当に単純にするために、イベントリスナーは、呼び出しが開始および終了したときに、いくつかの要求および応答ヘッダー情報とともにログに記録します

public class SimpleLogEventsListener extends EventListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleLogEventsListener.class);

    @Override
    public void callStart(Call call) {
        LOGGER.info("callStart at {}", LocalDateTime.now());
    }

    @Override
    public void requestHeadersEnd(Call call, Request request) {
        LOGGER.info("requestHeadersEnd at {} with headers {}", LocalDateTime.now(), request.headers());
    }

    @Override
    public void responseHeadersEnd(Call call, Response response) {
        LOGGER.info("responseHeadersEnd at {} with headers {}", LocalDateTime.now(), response.headers());
    }

    @Override
    public void callEnd(Call call) {
        LOGGER.info("callEnd at {}", LocalDateTime.now());
    }
}

ご覧のとおり、リスナーを作成するには、EventListenerクラスから拡張するだけです。次に、関心のあるイベントのメソッドをオーバーライドできます。

単純なリスナーでは、呼び出しの開始時刻と終了時刻を、到着時の要求ヘッダーと応答ヘッダーとともにログに記録します。

5.1. 一緒に差し込む

このリスナーを実際に利用するには、OkHttpClientインスタンスをビルドするときにeventListenerメソッドを呼び出すだけで、次のように機能するはずです。

OkHttpClient client = new OkHttpClient.Builder() 
  .eventListener(new SimpleLogEventsListener())
  .build();

次のセクションでは、新しいリスナーをテストする方法を見ていきます。

5.2. イベントリスナーのテスト

これで、最初のイベントリスナーを定義しました。 先に進んで、最初の統合テストを書いてみましょう。

@Rule
public MockWebServer server = new MockWebServer();

@Test
public void givenSimpleEventLogger_whenRequestSent_thenCallsLogged() throws IOException {
    server.enqueue(new MockResponse().setBody("Hello Baeldung Readers!"));
        
    OkHttpClient client = new OkHttpClient.Builder()
      .eventListener(new SimpleLogEventsListener())
      .build();

    Request request = new Request.Builder()
      .url(server.url("/"))
      .build();

    try (Response response = client.newCall(request).execute()) {
        assertEquals("Response code should be: ", 200, response.code());
        assertEquals("Body should be: ", "Hello Baeldung Readers!", response.body().string());
    }
 }

まず、OkHttp MockWebServer JUnitルールを使用しています。

これは、イベントリスナーのテストに使用するHTTPクライアントをテストするための軽量でスクリプト可能なWebサーバーです。 このルールを使用して、統合テストごとにサーバーのクリーンなインスタンスを作成します。

それを念頭に置いて、テストの重要な部分を見ていきましょう。

  • まず、本文に簡単なメッセージを含む模擬応答を設定します
  • 次に、 OkHttpClient をビルドし、SimpleLogEventsListenerを構成します。
  • 最後に、リクエストを送信し、アサーションを使用して受信したレスポンスコードと本文を確認します

5.3. テストの実行

テストを実行すると、イベントがログに記録されます。

callStart at 2021-05-04T17:51:33.024
...
requestHeadersEnd at 2021-05-04T17:51:33.046 with headers User-Agent: A Baeldung Reader
Host: localhost:51748
Connection: Keep-Alive
Accept-Encoding: gzip
...
responseHeadersEnd at 2021-05-04T17:51:33.053 with headers Content-Length: 23
callEnd at 2021-05-04T17:51:33.055

6. すべてを一緒に入れて

ここで、単純なログの例に基づいて、コールチェーンの各ステップの経過時間を記録したいとします。

public class EventTimer extends EventListener {

    private long start;

    private void logTimedEvent(String name) {
        long now = System.nanoTime();
        if (name.equals("callStart")) {
            start = now;
        }
        long elapsedNanos = now - start;
        System.out.printf("%.3f %s%n", elapsedNanos / 1000000000d, name);
    }

    @Override
    public void callStart(Call call) {
        logTimedEvent("callStart");
    }

    // More event listener methods
}

これは最初の例と非常に似ていますが、今回は各イベントの呼び出しが開始されてからの経過時間をキャプチャします。 通常、これはネットワーク遅延を検出するのに非常に興味深い場合があります

私たち自身のhttps://www.baeldung.com/のような実際のサイトに対してこれを実行するかどうかを見てみましょう:

0.000 callStart
0.012 proxySelectStart
0.012 proxySelectEnd
0.012 dnsStart
0.175 dnsEnd
0.183 connectStart
0.248 secureConnectStart
0.608 secureConnectEnd
0.608 connectEnd
0.609 connectionAcquired
0.612 requestHeadersStart
0.613 requestHeadersEnd
0.706 responseHeadersStart
0.707 responseHeadersEnd
0.765 responseBodyStart
0.765 responseBodyEnd
0.765 connectionReleased
0.765 callEnd

この呼び出しはHTTPSを介して行われるため、secureConnectStartおよびsecureConnectStartイベントも表示されます。

7. 失敗した通話の監視

これまで、HTTPリクエストの成功に焦点を当ててきましたが、失敗したイベントをキャプチャすることもできます。

@Test (expected = SocketTimeoutException.class)
public void givenConnectionError_whenRequestSent_thenFailedCallsLogged() throws IOException {
    OkHttpClient client = new OkHttpClient.Builder()
      .eventListener(new EventTimer())
      .build();

    Request request = new Request.Builder()
      .url(server.url("/"))
      .build();

    client.newCall(request).execute();
}

この例では、モックWebサーバーのセットアップを意図的に回避しました。これは、もちろん、SocketTimeoutExceptionの形で壊滅的な障害が発生することを意味します。

ここでテストを実行したときの出力を見てみましょう。

0.000 callStart
...
10.008 responseFailed
10.009 connectionReleased
10.009 callFailed

予想どおり、通話が開始され、10秒後に接続タイムアウトが発生し、その結果、responseFailedおよびcallFailedイベントがログに記録されます。

8. 並行性に関する簡単な説明

これまでのところ、を同時に実行する複数の呼び出しはないと想定しています。 このシナリオに対応する場合は、OkHttpClientを構成するときにeventListenerFactoryメソッドを使用する必要があります。

ファクトリを使用して、HTTP呼び出しごとに新しいEventListenerインスタンスを作成できます。 このアプローチを使用すると、リスナーで呼び出し固有の状態を維持することができます。

9. 結論

この記事では、OkHttpを使用してイベントをキャプチャする方法についてすべて学びました。 まず、イベントとは何かを説明し、使用可能なイベントの種類と、HTTP呼び出しを処理するときにそれらが到着する順序を理解することから始めました。

次に、HTTP呼び出しの一部をキャプチャするための単純なイベントロガーを定義する方法と、統合テストを作成する方法を確認しました。

いつものように、記事の完全なソースコードは、GitHubから入手できます。