HttpClient-verbindingsbeheer

1. Overzicht

In dit artikel gaan we in op de basisprincipes van verbindingsbeheer binnen de HttpClient 4.

We behandelen het gebruik van BasichttpClientConnectionManager en PoolingHttpClientConnectionManager om een ​​veilig, protocolconform en efficiënt gebruik van HTTP-verbindingen af ​​te dwingen.

2. Het BasicHttpClientConnectionManager voor een lage, enkele schroefdraadverbinding

De BasicHttpClientConnectionManager is beschikbaar sinds HttpClient 4.3.3 als de eenvoudigste implementatie van een HTTP-verbindingsbeheerder. Het wordt gebruikt om een ​​enkele verbinding te maken en te beheren die slechts door één thread tegelijk kan worden gebruikt.

Voorbeeld 2.1. Een verbindingsverzoek krijgen voor een verbinding op laag niveau (HttpClientConnection)

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

De requestConnection methode krijgt van de manager een pool van verbindingen voor een specifiek route om verbinding mee te maken. De route parameter specificeert een route van “proxy-hops” naar de doelhost, of de doelhost zelf.

Het is mogelijk om een ​​verzoek uit te voeren met een HttpClientConnection direct, maar houd er rekening mee dat deze benadering op laag niveau uitgebreid en moeilijk te beheren is. Verbindingen op laag niveau zijn handig om toegang te krijgen tot socket- en verbindingsgegevens, zoals time-outs en doelhostinformatie, maar voor standaarduitvoeringen HttpClient is een veel eenvoudigere API om tegen te werken.

3. Gebruik de PoolingHttpClientConnectionManager om een ​​pool van multithread-verbindingen op te halen en te beheren

De PoolingHttpClientConnectionManager zal een pool van verbindingen maken en beheren voor elke route of doelhost die we gebruiken. De standaardgrootte van de pool van concurrent verbindingen dat kan worden geopend door de manager is 2 voor elke route of doelhost, en 20 voor totaal open verbindingen. Laten we eerst eens kijken hoe we deze verbindingsbeheerder kunnen instellen op een eenvoudige HttpClient:

Voorbeeld 3.1. De PoolingHttpClientConnectionManager instellen op een HttpClient

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

Vervolgens - laten we eens kijken hoe dezelfde verbindingsbeheerder kan worden gebruikt door twee HttpClients die in twee verschillende threads worden uitgevoerd:

Voorbeeld 3.2. Twee HttpClients gebruiken om elk verbinding te maken met één doelhost

HttpGet get1 = nieuwe HttpGet ("/"); HttpGet get2 = nieuwe HttpGet ("// google.com"); PoolingHttpClientConnectionManager connManager = nieuw PoolingHttpClientConnectionManager (); CloseableHttpClient client1 = HttpClients.custom (). SetConnectionManager (connManager) .build (); CloseableHttpClient client2 = HttpClients.custom (). SetConnectionManager (connManager) .build (); MultiHttpClientConnThread thread1 = nieuwe MultiHttpClientConnThread (client1, get1); MultiHttpClientConnThread thread2 = nieuwe MultiHttpClientConnThread (client2, get2); thread1.start (); thread2.start (); thread1.join (); thread2.join ();

Merk op dat we gebruiken een zeer eenvoudige implementatie van aangepaste threads - hier is het:

Voorbeeld 3.3. Aangepaste draad Het uitvoeren van een KRIJG Verzoek

openbare klasse MultiHttpClientConnThread breidt Thread {private CloseableHttpClient-client uit; privé HttpGet get; // standard constructors public void run () {probeer {HttpResponse response = client.execute (get); EntityUtils.consume (response.getEntity ()); } catch (ClientProtocolException ex) {} catch (IOException ex) {}}}

Let op deEntityUtils.consume (response.getEntity) oproep - nodig om de volledige inhoud van het antwoord (entiteit) te consumeren, zodat de manager dat kan maak de verbinding weer los naar het zwembad.

4. Configureer de Connection Manager

De standaardinstellingen van de pooling-verbindingsbeheerder zijn goed gekozen, maar kunnen - afhankelijk van uw gebruikssituatie - te klein zijn. Dus - laten we eens kijken hoe we kunnen configureren:

  • het totale aantal verbindingen
  • het maximale aantal verbindingen per (elke) route
  • het maximale aantal verbindingen per enkele, specifieke route

Voorbeeld 4.1. Het aantal verbindingen verhogen dat kan worden geopend en beheerd buiten de standaardlimieten

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

Laten we de API samenvatten:

  • setMaxTotal (int max): Stel het maximale aantal open verbindingen in.
  • setDefaultMaxPerRoute (int max): Stel het maximale aantal gelijktijdige verbindingen per route in, dit is standaard 2.
  • setMaxPerRoute (int max): Stel het totale aantal gelijktijdige verbindingen in op een specifieke route, dit is standaard 2.

Dus zonder de standaard te wijzigen, we gaan de grenzen van de verbindingsbeheerder bereiken vrij gemakkelijk - laten we eens kijken hoe dat eruit ziet:

Voorbeeld 4.2. Threads gebruiken om verbindingen tot stand te brengen

HttpGet get = nieuwe HttpGet ("// www.baeldung.com"); PoolingHttpClientConnectionManager connManager = nieuw PoolingHttpClientConnectionManager (); CloseableHttpClient-client = HttpClients.custom (). setConnectionManager (connManager) .build (); MultiHttpClientConnThread thread1 = nieuwe MultiHttpClientConnThread (client, get); MultiHttpClientConnThread thread2 = nieuwe MultiHttpClientConnThread (client, get); MultiHttpClientConnThread thread3 = nieuwe MultiHttpClientConnThread (client, get); thread1.start (); thread2.start (); thread3.start (); thread1.join (); thread2.join (); thread3.join ();

Zoals we al hebben besproken, de verbindingslimiet per host is 2 standaard. In dit voorbeeld proberen we dus 3 threads te laten maken 3 verzoeken aan dezelfde host, maar er worden slechts 2 verbindingen parallel toegewezen.

Laten we de logboeken eens bekijken - we hebben drie actieve threads, maar slechts 2 geleasde verbindingen:

[Thread-0] INFO obhcMultiHttpClientConnThread - Before - Leased Connections = 0 [Thread-1] INFO obhcMultiHttpClientConnThread - Before - Leased Connections = 0 [Thread-2] INFO obhcMultiHttpClientConnThread - Before - Leased Connections = 0 [Thread-2] INFO obhcMultiHttpClientConnThread - Na - Geleasde verbindingen = 2 [Thread-0] INFO obhcMultiHttpClientConnThread - Na - Geleasde verbindingen = 2

5. Strategie voor het behouden van verbindingen

Onder vermelding van de HttpClient 4.3.3. reference: “Als het In leven houden header is niet aanwezig in het antwoord, HttpClient gaat ervan uit dat de verbinding voor onbepaalde tijd in leven kan worden gehouden. " (Zie de HttpClient-referentie).

Om dit te omzeilen en dode verbindingen te kunnen beheren, hebben we een aangepaste strategie-implementatie nodig en deze in het HttpClient.

Voorbeeld 5.1. Een op maat gemaakte Keep Alive-strategie

ConnectionKeepAliveStrategy myStrategy = nieuwe ConnectionKeepAliveStrategy () {@Override public long getKeepAliveDuration (HttpResponse response, HttpContext context) {HeaderElementIterator it = new BasicHeaderElementIterator (response.headerIterator (HTTP.CONIVE )_KEEP_KEEP_ while (it.hasNext ()) {HeaderElement hij = it.nextElement (); String param = he.getName (); Stringwaarde = he.getValue (); if (waarde! = null && param.equalsIgnoreCase ("time-out")) {return Long.parseLong (waarde) * 1000; }} retourneer 5 * 1000; }};

Deze strategie zal eerst proberen om de host's toe te passen In leven houden beleid vermeld in de kop. Als die informatie niet aanwezig is in de antwoordkop, worden de verbindingen gedurende 5 seconden in stand gehouden.

Nu - laten we creëren een klant met deze aangepaste strategie:

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

6. Aanhoudende verbinding / hergebruik

De HTTP / 1.1-specificatie stelt dat verbindingen opnieuw kunnen worden gebruikt als ze niet zijn gesloten - dit staat bekend als verbindingspersistentie.

Zodra een verbinding is verbroken door de manager, blijft deze openstaan ​​voor hergebruik. Bij gebruik van een BasicHttpClientConnectionManager, die slechts een enkele verbinding kan beheren, moet de verbinding worden verbroken voordat deze weer wordt verhuurd:

Voorbeeld 6.1. BasicHttpClientConnectionManagerVerbinding hergebruiken

BasicHttpClientConnectionManager basicConnManager = nieuwe BasicHttpClientConnectionManager (); HttpClientContext context = HttpClientContext.create (); // HttpRoute-route op laag niveau = nieuwe HttpRoute (nieuwe HttpHost ("www.baeldung.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 = nieuwe HttpRequestExecutor (); context.setTargetHost ((nieuwe HttpHost ("www.baeldung.com", 80))); HttpGet get = nieuwe HttpGet ("// www.baeldung.com"); exeRequest.execute (get, conn, context); basicConnManager.releaseConnection (conn, null, 1, TimeUnit.SECONDS); // hoog niveau CloseableHttpClient client = HttpClients.custom () .setConnectionManager (basicConnManager) .build (); client.execute (get);

Laten we eens kijken wat er gebeurt.

Ten eerste: merk op dat we eerst een verbinding op laag niveau gebruiken, zodat we de volledige controle hebben over wanneer de verbinding wordt verbroken, en vervolgens een normale verbinding op een hoger niveau met een HttpClient. De complexe logica op laag niveau is hier niet erg relevant - het enige waar we om geven is de releaseConnection bellen. Hierdoor komt de enige beschikbare verbinding vrij en kan deze opnieuw worden gebruikt.

Vervolgens voert de client het GET-verzoek opnieuw uit met succes. Als we het vrijgeven van de verbinding overslaan, krijgen we een IllegalStateException van de HttpClient:

java.lang.IllegalStateException: Verbinding wordt nog steeds toegewezen op o.a.h.u.Asserts.check (Asserts.java:34) op o.a.h.i.c.BasicHttpClientConnectionManager.getConnection (BasicHttpClientConnectionManager.java:248)

Merk op dat de bestaande verbinding niet wordt gesloten, maar wordt vrijgegeven en vervolgens opnieuw wordt gebruikt door het tweede verzoek.

In tegenstelling tot het bovenstaande voorbeeld, The PoolingHttpClientConnectionManager staat transparant hergebruik van verbindingen toe zonder de noodzaak om impliciet een verbinding te verbreken:

Voorbeeld 6.2.PoolingHttpClientConnectionManager: Verbindingen met schroefdraad opnieuw gebruiken

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

Het bovenstaande voorbeeld heeft 10 threads, die 10 verzoeken uitvoeren maar slechts 5 verbindingen delen.

Dit voorbeeld is natuurlijk afhankelijk van de server In leven houden time-out. Om ervoor te zorgen dat de verbindingen niet kapot gaan voordat ze opnieuw worden gebruikt, wordt aanbevolen om de cliënt met een In leven houden strategie (zie voorbeeld 5.1.).

7. Time-outs configureren - Socket-time-out met Verbindingsbeheer

De enige time-out die kan worden ingesteld op het moment dat Connection Manager wordt geconfigureerd, is de socket-time-out:

Voorbeeld 7.1. Socket-time-out instellen op 5 seconden

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

Zie dit voor een meer diepgaande bespreking van time-outs in de HttpClient.

8. Uitzetting van verbindingen

Verbinding ontruiming is gewend detecteer inactieve en verlopen verbindingen en sluit ze; er zijn twee mogelijkheden om dit te doen.

  1. Zich baserend op de HttpClient om te controleren of de verbinding verouderd is voordat een verzoek wordt uitgevoerd. Dit is een dure optie die niet altijd betrouwbaar is.
  2. Maak een monitor-thread om inactieve en / of gesloten verbindingen te sluiten.

Voorbeeld 8.1. Instellen van de HttpClient om te controleren op verouderde verbindingen

PoolingHttpClientConnectionManager connManager = nieuw PoolingHttpClientConnectionManager (); CloseableHttpClient client = HttpClients.custom (). SetDefaultRequestConfig (RequestConfig.custom (). SetStaleConnectionCheckEnabled (true) .build ()) .setConnectionManager (connManager) .build ();

Voorbeeld 8.2. Met behulp van een verouderde verbindingsmonitor

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

De IdleConnectionMonitorThreadklasse is hieronder opgesomd:

openbare klasse IdleConnectionMonitorThread breidt Thread {private finale HttpClientConnectionManager connMgr; privé vluchtige booleaanse uitschakeling; openbare IdleConnectionMonitorThread (PoolingHttpClientConnectionManager connMgr) {super (); this.connMgr = connMgr; } @Override public void run () {probeer {while (! Shutdown) {synchronized (this) {wait (1000); connMgr.closeExpiredConnections (); connMgr.closeIdleConnections (30, TimeUnit.SECONDS); }}} catch (InterruptedException ex) {shutdown (); }} public void shutdown () {shutdown = true; gesynchroniseerd (dit) {meldAll (); }}}

9. Verbinding verbroken

Een verbinding kan netjes worden gesloten (er wordt geprobeerd de uitvoerbuffer door te spoelen voordat deze wordt gesloten), of krachtig, door de afsluiten methode (de uitvoerbuffer wordt niet leeggemaakt).

Om verbindingen goed te sluiten, moeten we al het volgende doen:

  • consumeer en sluit het antwoord (indien afsluitbaar)
  • sluit de cliënt
  • sluit en sluit de verbindingsbeheerder

Voorbeeld 8.1. Verbinding verbreken en bronnen vrijgeven

connManager = nieuwe PoolingHttpClientConnectionManager (); CloseableHttpClient client = HttpClients.custom () .setConnectionManager (connManager) .build (); HttpGet get = nieuwe HttpGet ("// google.com"); CloseableHttpResponse response = client.execute (get); EntityUtils.consume (response.getEntity ()); response.close (); client.close (); connManager.close (); 

Als de manager wordt afgesloten zonder dat er al verbindingen zijn gesloten, worden alle verbindingen verbroken en worden alle bronnen vrijgegeven.

Het is belangrijk om in gedachten te houden dat hierdoor geen gegevens worden gewist die mogelijk actief waren voor de bestaande verbindingen.

10. Conclusie

In dit artikel hebben we besproken hoe u de HTTP-verbindingsbeheer-API van HttpClient kunt gebruiken het hele proces van het beheren van verbindingen - van het openen en toewijzen ervan tot het beheren van hun gelijktijdig gebruik door meerdere agenten tot het uiteindelijk sluiten ervan.

We zagen hoe de BasicHttpClientConnectionManager is een eenvoudige oplossing voor het afhandelen van enkele verbindingen en hoe het verbindingen op laag niveau kan beheren. We hebben ook gezien hoe de PoolingHttpClientConnectionManager gecombineerd met de HttpClient API zorgt voor een efficiënt en protocolconform gebruik van HTTP-verbindingen.