HttpClient接続管理

HttpClient接続管理

1. 概要

この記事では、HttpClient 4内の接続管理の基本について説明します。

BasichttpClientConnectionManagerPoolingHttpClientConnectionManagerの使用について説明し、HTTP接続の安全でプロトコルに準拠した効率的な使用を強制します。

2. 低レベルのシングルスレッド接続のBasicHttpClientConnectionManager

BasicHttpClientConnectionManagerは、HTTP接続マネージャーの最も単純な実装としてHttpClient4.3.3以降で使用できます。 一度に1つのスレッドのみが使用できる単一の接続を作成および管理するために使用されます。

参考文献:

高度なHttpClient設定

高度なユースケース向けのHttpClient構成。

Apache HttpClient 4でカスタムCookieを送信する方法。

SSLを使用したHttpClient

SSLを使用してHttpClientを構成する方法の例。

例2.1 Getting a Connection Request for a Low Level Connection (HttpClientConnection)

BasicHttpClientConnectionManager connManager
 = new BasicHttpClientConnectionManager();
HttpRoute route = new HttpRoute(new HttpHost("www.example.com", 80));
ConnectionRequest connRequest = connManager.requestConnection(route, null);

requestConnectionメソッドは、特定のrouteが接続するための接続のプールをマネージャーから取得します。 routeパラメータは、ターゲットホストまたはターゲットホスト自体への「プロキシホップ」のルートを指定します。

HttpClientConnectionを直接使用してリクエストを実行することは可能ですが、この低レベルのアプローチは冗長であり、管理が難しいことに注意してください。 低レベルの接続は、タイムアウトやターゲットホスト情報などのソケットおよび接続データにアクセスするのに役立ちますが、標準の実行では、HttpClientの方がはるかに簡単に操作できます。

3. PoolingHttpClientConnectionManagerを使用してマルチスレッド接続のプールを取得および管理する

PoolingHttpClientConnectionManagerは、使用するルートまたはターゲットホストごとに接続のプールを作成および管理します。 マネージャーが開くことができる同時connectionsのプールのデフォルトのサイズは、2 for each route or target hostであり、20 for totalは接続を開きます。 まず、単純なHttpClientでこの接続マネージャーを設定する方法を見てみましょう。

例3.1。 Setting the PoolingHttpClientConnectionManager on a HttpClient **

HttpClientConnectionManager poolingConnManager
  = new PoolingHttpClientConnectionManager();
CloseableHttpClient client
 = HttpClients.custom().setConnectionManager(poolingConnManager)
 .build();
client.execute(new HttpGet("/"));
assertTrue(poolingConnManager.getTotalStats().getLeased() == 1);

次へ–2つの異なるスレッドで実行されている2つのHttpClientで同じ接続マネージャーを使用する方法を見てみましょう。

例3.2 Using Two HttpClients to Connect to One Target Host Each

HttpGet get1 = new HttpGet("/");
HttpGet get2 = new HttpGet("http://google.com");
PoolingHttpClientConnectionManager connManager
  = new PoolingHttpClientConnectionManager();
CloseableHttpClient client1
  = HttpClients.custom().setConnectionManager(connManager).build();
CloseableHttpClient client2
  = HttpClients.custom().setConnectionManager(connManager).build();

MultiHttpClientConnThread thread1
 = new MultiHttpClientConnThread(client1, get1);
MultiHttpClientConnThread thread2
 = new MultiHttpClientConnThread(client2, get2);
thread1.start();
thread2.start();
thread1.join();
thread2.join();

a very simple custom thread implementationを使用していることに注意してください–ここにあります:

例3.3。 Custom Thread Executing a GET Request

public class MultiHttpClientConnThread extends Thread {
    private CloseableHttpClient client;
    private HttpGet get;

    // standard constructors
    public void run(){
        try {
            HttpResponse response = client.execute(get);
            EntityUtils.consume(response.getEntity());
        } catch (ClientProtocolException ex) {
        } catch (IOException ex) {
        }
    }
}

EntityUtils.consume(response.getEntity)呼び出しに注意してください–マネージャーがrelease the connection back to the poolできるように、応答(エンティティ)のコンテンツ全体を消費するために必要です。

4. 接続マネージャーを構成する

プール接続マネージャーのデフォルトは適切に選択されていますが、ユースケースによっては小さすぎる場合があります。 それでは、構成方法を見てみましょう。

  • 接続の総数

  • (任意の)ルートごとの最大接続数

  • 単一の特定のルートごとの最大接続数

例4.1。 Increasing the Number of Connections that Can be Open and Managed Beyond the default Limits

PoolingHttpClientConnectionManager connManager
  = new PoolingHttpClientConnectionManager();
connManager.setMaxTotal(5);
connManager.setDefaultMaxPerRoute(4);
HttpHost host = new HttpHost("www.example.com", 80);
connManager.setMaxPerRoute(new HttpRoute(host), 5);

APIを要約してみましょう。

  • setMaxTotal(int max):開いている接続の合計の最大数を設定します。

  • setDefaultMaxPerRoute(int max):ルートごとの同時接続の最大数を設定します。デフォルトでは2です。

  • setMaxPerRoute(int max):特定のルートへの同時接続の総数を設定します。デフォルトでは2です。

したがって、デフォルトを変更しなくても、we’re going to reach the limits of the connection managerは非常に簡単です。次のようになります。

例4.2。 Using Threads to Execute Connections

HttpGet get = new HttpGet("http://www.example.com");
PoolingHttpClientConnectionManager connManager
  = new PoolingHttpClientConnectionManager();
CloseableHttpClient client = HttpClients.custom().
    setConnectionManager(connManager).build();
MultiHttpClientConnThread thread1
  = new MultiHttpClientConnThread(client, get);
MultiHttpClientConnThread thread2
  = new MultiHttpClientConnThread(client, get);
MultiHttpClientConnThread thread3
  = new MultiHttpClientConnThread(client, get);
thread1.start();
thread2.start();
thread3.start();
thread1.join();
thread2.join();
thread3.join();

すでに説明したように、デフォルトではthe per host connection limit is 2です。 したがって、この例では、3つのスレッドで3 requests to the same hostを作成しようとしていますが、並列に割り当てられるのは2つの接続のみです。

ログを見てみましょう。3つのスレッドが実行されていますが、リースされている接続は2つだけです。

[Thread-0] INFO  o.b.h.c.MultiHttpClientConnThread
 - Before - Leased Connections = 0
[Thread-1] INFO  o.b.h.c.MultiHttpClientConnThread
 - Before - Leased Connections = 0
[Thread-2] INFO  o.b.h.c.MultiHttpClientConnThread
 - Before - Leased Connections = 0
[Thread-2] INFO  o.b.h.c.MultiHttpClientConnThread
 - After - Leased Connections = 2
[Thread-0] INFO  o.b.h.c.MultiHttpClientConnThread
 - After - Leased Connections = 2

5. 接続キープアライブ戦略

HttpClientの引用4.3.3。 参照:「Keep-Aliveヘッダーが応答に存在しない場合、HttpClientは、接続が無期限に存続できると想定します。」 (See the HttpClient Reference)。

これを回避し、切断された接続を管理できるようにするには、カスタマイズされた戦略の実装を必要とし、それをHttpClientに組み込みます。

例5.1。 A Custom Keep Alive Strategy

ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
    @Override
    public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
        HeaderElementIterator it = new BasicHeaderElementIterator
            (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
        while (it.hasNext()) {
            HeaderElement he = it.nextElement();
            String param = he.getName();
            String value = he.getValue();
            if (value != null && param.equalsIgnoreCase
               ("timeout")) {
                return Long.parseLong(value) * 1000;
            }
        }
        return 5 * 1000;
    }
};

この戦略では、最初にヘッダーに記載されているホストのKeep-Aliveポリシーを適用しようとします。 その情報が応答ヘッダーに存在しない場合、5秒間接続を維持します。

では、a client with this custom strategyを作成しましょう。

PoolingHttpClientConnectionManager connManager
  = new PoolingHttpClientConnectionManager();
CloseableHttpClient client = HttpClients.custom()
  .setKeepAliveStrategy(myStrategy)
  .setConnectionManager(connManager)
  .build();

6. 接続の持続性/再利用

HTTP / 1.1仕様では、接続が閉じられていない場合、接続を再利用できると規定されています。これは接続の永続性として知られています。

マネージャーによって接続が解放されると、再使用のために開いたままになります。 単一の接続しか管理できないBasicHttpClientConnectionManager,を使用する場合、接続を再度リースバックする前に、接続を解放する必要があります。

例6.1。 BasicHttpClientConnectionManagerConnection Reuse

BasicHttpClientConnectionManager basicConnManager =
    new BasicHttpClientConnectionManager();
HttpClientContext context = HttpClientContext.create();

// low level
HttpRoute route = new HttpRoute(new HttpHost("www.example.com", 80));
ConnectionRequest connRequest = basicConnManager.requestConnection(route, null);
HttpClientConnection conn = connRequest.get(10, TimeUnit.SECONDS);
basicConnManager.connect(conn, route, 1000, context);
basicConnManager.routeComplete(conn, route, context);

HttpRequestExecutor exeRequest = new HttpRequestExecutor();
context.setTargetHost((new HttpHost("www.example.com", 80)));
HttpGet get = new HttpGet("http://www.example.com");
exeRequest.execute(get, conn, context);

basicConnManager.releaseConnection(conn, null, 1, TimeUnit.SECONDS);

// high level
CloseableHttpClient client = HttpClients.custom()
  .setConnectionManager(basicConnManager)
  .build();
client.execute(get);

何が起こるか見てみましょう。

まず、最初に低レベルの接続を使用していることに注意してください。これは、接続がいつ解放されるかを完全に制御できるようにするためであり、次にHttpClientを使用した通常の高レベルの接続です。 ここでは、複雑な低レベルロジックはあまり関係ありません。私たちが気にするのは、releaseConnection呼び出しだけです。 これにより、使用可能な唯一の接続が解放され、再利用できるようになります。

次に、クライアントはGETリクエストを再度実行して成功します。 接続の解放をスキップすると、HttpClientからIllegalStateExceptionを取得します。

java.lang.IllegalStateException: Connection is still allocated
  at o.a.h.u.Asserts.check(Asserts.java:34)
  at o.a.h.i.c.BasicHttpClientConnectionManager.getConnection
    (BasicHttpClientConnectionManager.java:248)

既存の接続は閉じられておらず、解放されてから2番目のリクエストで再利用されることに注意してください。

上記の例とは対照的に、PoolingHttpClientConnectionManagerを使用すると、接続を暗黙的に解放することなく、接続を透過的に再利用できます。

例6.2 *PoolingHttpClientConnectionManager: Threadを使ったコネクションの再利用*

HttpGet get = new HttpGet("http://echo.200please.com");
PoolingHttpClientConnectionManager connManager
  = new PoolingHttpClientConnectionManager();
connManager.setDefaultMaxPerRoute(5);
connManager.setMaxTotal(5);
CloseableHttpClient client = HttpClients.custom()
  .setConnectionManager(connManager)
  .build();
MultiHttpClientConnThread[] threads
  = new  MultiHttpClientConnThread[10];
for(int i = 0; i < threads.length; i++){
    threads[i] = new MultiHttpClientConnThread(client, get, connManager);
}
for (MultiHttpClientConnThread thread: threads) {
     thread.start();
}
for (MultiHttpClientConnThread thread: threads) {
     thread.join(1000);
}

上記の例には10個のスレッドがあり、10個の要求を実行しますが、5つの接続のみを共有します。

もちろん、この例はサーバーのKeep-Aliveタイムアウトに依存しています。 再利用する前に接続が切断されないようにするには、clientKeep-Alive戦略で構成することをお勧めします(例5.1を参照)。

7. タイムアウトの構成–接続マネージャーを使用したソケットタイムアウト

接続マネージャーが構成されているときに設定できる唯一のタイムアウトは、ソケットタイムアウトです。

例7.1。 Setting Socket Timeout to 5 Seconds

HttpRoute route = new HttpRoute(new HttpHost("www.example.com", 80));
PoolingHttpClientConnectionManager connManager
  = new PoolingHttpClientConnectionManager();
connManager.setSocketConfig(route.getTargetHost(),SocketConfig.custom().
    setSoTimeout(5000).build());

HttpClientのタイムアウトの詳細については、see thisを参照してください。

8. 接続エビクション

接続エビクションはdetect idle and expired connections and close themに使用されます。これを行うには2つのオプションがあります。

  1. リクエストを実行する前に、HttpClientに依存して接続が古くなっているかどうかを確認します。 これは高価なオプションであり、常に信頼できるとは限りません。

  2. モニタースレッドを作成して、アイドル接続や閉じた接続を閉じます。

例8.1。 Setting the HttpClient to Check for Stale Connections

PoolingHttpClientConnectionManager connManager
  = new PoolingHttpClientConnectionManager();
CloseableHttpClient client = HttpClients.custom().setDefaultRequestConfig(
    RequestConfig.custom().setStaleConnectionCheckEnabled(true).build()
).setConnectionManager(connManager).build();

例8.2。 Using a Stale Connection Monitor Thread

PoolingHttpClientConnectionManager connManager
  = new PoolingHttpClientConnectionManager();
CloseableHttpClient client = HttpClients.custom()
  .setConnectionManager(connManager).build();
IdleConnectionMonitorThread staleMonitor
 = new IdleConnectionMonitorThread(connManager);
staleMonitor.start();
staleMonitor.join(1000);

IdleConnectionMonitorThreadクラスを以下に示します。

public class IdleConnectionMonitorThread extends Thread {
    private final HttpClientConnectionManager connMgr;
    private volatile boolean shutdown;

    public IdleConnectionMonitorThread(
      PoolingHttpClientConnectionManager connMgr) {
        super();
        this.connMgr = connMgr;
    }
    @Override
    public void run() {
        try {
            while (!shutdown) {
                synchronized (this) {
                    wait(1000);
                    connMgr.closeExpiredConnections();
                    connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                }
            }
        } catch (InterruptedException ex) {
            shutdown();
        }
    }
    public void shutdown() {
        shutdown = true;
        synchronized (this) {
            notifyAll();
        }
    }
}

9. 接続の切断

接続は、正常に閉じる(閉じる前に出力バッファーをフラッシュする試みが行われる)か、shutdownメソッドを呼び出すことによって強制的に閉じることができます(出力バッファーはフラッシュされません)。

接続を適切に閉じるには、次のすべてを行う必要があります。

  • 応答を消費して閉じる(閉じることができる場合)

  • クライアントを閉じます

  • 接続マネージャーを閉じてシャットダウンする

例8.1。 Closing Connection and Releasing Resources

connManager = new PoolingHttpClientConnectionManager();
CloseableHttpClient client = HttpClients.custom()
  .setConnectionManager(connManager).build();
HttpGet get = new HttpGet("http://google.com");
CloseableHttpResponse response = client.execute(get);

EntityUtils.consume(response.getEntity());
response.close();
client.close();
connManager.close();

接続がすでに閉じられていない状態でマネージャがシャットダウンされた場合、すべての接続が閉じられ、すべてのリソースが解放されます。

これにより、既存の接続で進行中だった可能性のあるデータがフラッシュされないことに注意してください。

10. 結論

この記事では、HttpClientのHTTP接続管理APIを使用してthe entire process of managing connectionsを処理する方法について説明しました。それらを開いて割り当てることから、複数のエージェントによる同時使用を管理すること、そして最終的に閉じることまでです。

BasicHttpClientConnectionManagerが単一の接続を処理するための単純なソリューションであり、低レベルの接続を管理する方法を確認しました。 また、PoolingHttpClientConnectionManagerHttpClient APIを組み合わせると、HTTP接続を効率的かつプロトコルに準拠して使用できることもわかりました。