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.
HttpClient 4 - Benutzerdefiniertes Cookie senden
So senden Sie benutzerdefinierte Cookies mit dem Apache HttpClient 4.
HTTP-Client mit SSL
Beispiel für die Konfiguration des HTTP-Clients mit SSL.
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.
-
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.
-
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.