HttpClient-Verbindungsverwaltung

HTTP-Client-Verbindungsverwaltung

1. Überblick

In diesem Artikel werden die Grundlagen der Verbindungsverwaltung im HttpClient 4 erläutert.

Wir werden die Verwendung vonBasichttpClientConnectionManager undPoolingHttpClientConnectionManager behandeln, um eine sichere, protokollkonforme und effiziente Verwendung von HTTP-Verbindungen zu gewährleisten.

2. DieBasicHttpClientConnectionManager für eine Low-Level-Single-Threaded-Verbindung

BasicHttpClientConnectionManager ist seit HttpClient 4.3.3 als einfachste Implementierung eines HTTP-Verbindungsmanagers verfügbar. Es wird zum Erstellen und Verwalten einer einzelnen Verbindung verwendet, die jeweils nur von einem Thread verwendet werden kann.

Weitere Lektüre:

Erweiterte HttpClient-Konfiguration

HTTP-Client-Konfigurationen für erweiterte Anwendungsfälle.

Read more

So senden Sie benutzerdefinierte Cookies mit dem Apache HttpClient 4.

Read more

HTTP-Client mit SSL

Beispiel für die Konfiguration des HTTP-Clients mit SSL.

Read more

Beispiel 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);

DierequestConnection-Methode erhält vom Manager einen Pool von Verbindungen für eine bestimmteroute, zu der eine Verbindung hergestellt werden soll. Der Parameterroute gibt eine Route von "Proxy-Hops" zum Zielhost oder zum Zielhost selbst an.

Es ist möglich, eine Anforderung mitHttpClientConnection direkt auszuführen. Beachten Sie jedoch, dass dieser Ansatz auf niedriger Ebene ausführlich und schwierig zu verwalten ist. Low-Level-Verbindungen sind nützlich, um auf Socket- und Verbindungsdaten wie Zeitüberschreitungen und Zielhostinformationen zuzugreifen. Bei Standardausführungen istHttpClient jedoch eine viel einfachere API, mit der gearbeitet werden kann.

3. Verwenden vonPoolingHttpClientConnectionManager zum Abrufen und Verwalten eines Pools von Multithread-Verbindungen

DiePoolingHttpClientConnectionManager erstellen und verwalten einen Verbindungspool für jede Route oder jeden Zielhost, den wir verwenden. Die Standardgröße des Pools gleichzeitigerconnections, die vom Manager geöffnet werden können, beträgt2 for each route or target host und20 for total offene Verbindungen. Schauen wir uns zunächst an, wie Sie diesen Verbindungsmanager auf einem einfachen HttpClient einrichten:

Beispiel 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);

Weiter - Lassen Sie uns sehen, wie derselbe Verbindungsmanager von zwei HttpClients verwendet werden kann, die in zwei verschiedenen Threads ausgeführt werden:

Beispiel 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();

Beachten Sie, dass wira very simple custom thread implementation verwenden - hier ist es:

Beispiel 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) {
        }
    }
}

Beachten Sie den Aufruf vonEntityUtils.consume(response.getEntity) - erforderlich, um den gesamten Inhalt der Antwort (Entität) zu verbrauchen, damit der Managerrelease the connection back to the pool ausführen kann.

4. Konfigurieren Sie den Verbindungsmanager

Die Standardeinstellungen des Pooling Connection Managers sind gut gewählt, können jedoch - abhängig von Ihrem Anwendungsfall - zu klein sein. Schauen wir uns also an, wie wir Folgendes konfigurieren können:

  • die Gesamtzahl der Verbindungen

  • die maximale Anzahl von Verbindungen pro (beliebiger) Route

  • die maximale Anzahl von Verbindungen pro einzelner, spezifischer Route

Beispiel 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);

Lassen Sie uns die API rekapitulieren:

  • setMaxTotal(int max): Legen Sie die maximale Anzahl offener Verbindungen fest.

  • setDefaultMaxPerRoute(int max): Legen Sie die maximale Anzahl gleichzeitiger Verbindungen pro Route fest, die standardmäßig 2 beträgt.

  • setMaxPerRoute(int max): Legen Sie die Gesamtzahl der gleichzeitigen Verbindungen zu einer bestimmten Route fest, standardmäßig 2.

Ohne die Standardeinstellung zu ändern, istwe’re going to reach the limits of the connection manageralso ganz einfach - mal sehen, wie das aussieht:

Beispiel 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();

Wie bereits erwähnt, istthe per host connection limit is 2tandardmäßig. In diesem Beispiel versuchen wir, 3 Threads3 requests to the same host machen zu lassen, aber nur 2 Verbindungen werden parallel zugewiesen.

Werfen wir einen Blick auf die Protokolle. Es werden drei Threads ausgeführt, aber nur zwei geleaste Verbindungen:

[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. Verbindungs-Keep-Alive-Strategie

Zitieren des HTTP-Clients 4.3.3. Referenz: "Wenn der Header vonKeep-Alive nicht in der Antwort vorhanden ist, gehtHttpClient davon aus, dass die Verbindung auf unbestimmte Zeit am Leben gehalten werden kann." (See the HttpClient Reference).

Um dies zu umgehen und tote Verbindungen verwalten zu können, benötigen wir eine angepasste Strategieimplementierung und bauen sie inHttpClientein.

Beispiel 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;
    }
};

Diese Strategie versucht zunächst, die im Header angegebeneKeep-Alive-Richtlinie des Hosts anzuwenden. Wenn diese Informationen nicht im Antwortheader enthalten sind, werden die Verbindungen 5 Sekunden lang aufrecht erhalten.

Jetzt erstellen wira client with this custom strategy:

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

6. Verbindungspersistenz / Wiederverwendung

In der HTTP / 1.1-Spezifikation wird angegeben, dass Verbindungen wiederverwendet werden können, wenn sie nicht geschlossen wurden. Dies wird als Verbindungspersistenz bezeichnet.

Sobald eine Verbindung vom Manager freigegeben wurde, bleibt sie zur Wiederverwendung geöffnet. Wenn Sie einBasicHttpClientConnectionManager, verwenden, das nur eine einzelne Verbindung verwalten kann, muss die Verbindung freigegeben werden, bevor sie wieder zurückgemietet wird:

Beispiel 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);

Werfen wir einen Blick darauf, was passiert.

Beachten Sie zunächst, dass wir zuerst eine Verbindung auf niedriger Ebene verwenden, damit wir die volle Kontrolle darüber haben, wann die Verbindung freigegeben wird, und dann eine normale Verbindung auf höherer Ebene mit einem HttpClient. Die komplexe Low-Level-Logik ist hier nicht sehr relevant - das einzige, was uns wichtig ist, ist der Aufruf vonreleaseConnection. Dadurch wird die einzig verfügbare Verbindung freigegeben und kann erneut verwendet werden.

Anschließend führt der Client die GET-Anforderung erneut mit Erfolg aus. Wenn wir das Trennen der Verbindung überspringen, erhalten wir eine IllegalStateException vom HttpClient:

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)

Beachten Sie, dass die vorhandene Verbindung nicht geschlossen, sondern nur freigegeben und dann von der zweiten Anforderung wiederverwendet wird.

Im Gegensatz zum obigen Beispiel ermöglichtPoolingHttpClientConnectionManager die transparente Wiederverwendung von Verbindungen, ohne dass eine Verbindung implizit freigegeben werden muss:

Beispiel 6.2. *PoolingHttpClientConnectionManager: Wiederverwenden von Verbindungen mit Threads *

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);
}

Das obige Beispiel enthält 10 Threads, die 10 Anforderungen ausführen, aber nur 5 Verbindungen gemeinsam nutzen.

Dieses Beispiel basiert natürlich auf dem Timeout des ServersKeep-Alive. Um sicherzustellen, dass die Verbindungen vor der Wiederverwendung nicht unterbrochen werden, wird empfohlen,client mit einerKeep-Alive-Strategie zu konfigurieren (siehe Beispiel 5.1.).

7. Zeitüberschreitungen konfigurieren - Socket-Zeitüberschreitung mithilfe des Verbindungsmanagers

Das einzige Zeitlimit, das zum Zeitpunkt der Konfiguration des Verbindungsmanagers festgelegt werden kann, ist das Socket-Zeitlimit:

Beispiel 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());

Für eine eingehendere Diskussion der Zeitüberschreitungen im HttpClient -see this.

8. Verbindungsräumung

Die Verbindungsentfernung wird fürdetect idle and expired connections and close them verwendet. Hierfür gibt es zwei Möglichkeiten.

  1. Verlassen Sie sich auf dieHttpClient, um zu überprüfen, ob die Verbindung veraltet ist, bevor Sie eine Anforderung ausführen. Dies ist eine teure Option, die nicht immer zuverlässig ist.

  2. Erstellen Sie einen Monitor-Thread, um inaktive und / oder geschlossene Verbindungen zu schließen.

Beispiel 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();

Beispiel 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);

Die KlasseIdleConnectionMonitorThreadist unten aufgeführt:

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. Verbindung schließen

Eine Verbindung kann ordnungsgemäß geschlossen werden (es wird versucht, den Ausgabepuffer vor dem Schließen zu leeren) oder zwangsweise durch Aufrufen der Methodeshutdown (der Ausgabepuffer wird nicht geleert).

Um die Verbindungen ordnungsgemäß zu schließen, müssen Sie folgende Schritte ausführen:

  • verbrauchen und schließen Sie die Antwort (wenn schließbar)

  • Schließen Sie den Client

  • Schließen Sie den Verbindungsmanager, und fahren Sie ihn herunter

Beispiel 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();

Wenn der Manager heruntergefahren wird, ohne dass bereits Verbindungen geschlossen wurden, werden alle Verbindungen geschlossen und alle Ressourcen freigegeben.

Es ist wichtig zu beachten, dass dadurch keine Daten gelöscht werden, die möglicherweise für die vorhandenen Verbindungen ausgeführt wurden.

10. Fazit

In diesem Artikel wurde erläutert, wie Sie die HTTP-Verbindungsverwaltungs-API von HttpClient verwenden, umthe entire process of managing connections zu verarbeiten - vom Öffnen und Zuweisen über die Verwaltung ihrer gleichzeitigen Verwendung durch mehrere Agenten bis zum endgültigen Schließen.

Wir haben gesehen, wieBasicHttpClientConnectionManager eine einfache Lösung für die Verarbeitung einzelner Verbindungen ist und wie es Verbindungen auf niedriger Ebene verwalten kann. Wir haben auch gesehen, wie diePoolingHttpClientConnectionManager in Kombination mit derHttpClient-API eine effiziente und protokollkonforme Verwendung von HTTP-Verbindungen ermöglichen.