Gestione connessione HttpClient

1. Panoramica

In questo articolo, esamineremo le basi della gestione della connessione all'interno di HttpClient 4.

Tratteremo l'uso di BasichttpClientConnectionManager e PoolingHttpClientConnectionManager per imporre un uso sicuro, conforme al protocollo ed efficiente delle connessioni HTTP.

2. BasicHttpClientConnectionManager per una connessione a thread singolo di basso livello

Il BasicHttpClientConnectionManager è disponibile dal HttpClient 4.3.3 come il più semplice implementazione di una gestione connessione HTTP. Viene utilizzato per creare e gestire una singola connessione che può essere utilizzata solo da un thread alla volta.

Esempio 2.1. Recupero di una richiesta di connessione per una connessione di basso livello ( HttpClientConnection )

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

Il metodo requestConnection ottiene dal gestore un pool di connessioni per un percorso specifico a cui connettersi. Il parametro route specifica una route di "proxy hop" all'host di destinazione o allo stesso host di destinazione.

È possibile eseguire una richiesta utilizzando direttamente una HttpClientConnection , ma tieni presente che questo approccio di basso livello è dettagliato e difficile da gestire. Le connessioni di basso livello sono utili per accedere ai dati di socket e connessione come timeout e informazioni sull'host di destinazione, ma per le esecuzioni standard, HttpClient è un'API molto più semplice con cui lavorare.

3. Utilizzo di PoolingHttpClientConnectionManager per ottenere e gestire un pool di connessioni multithread

Il PoolingHttpClientConnectionManager sarà creare e gestire un pool di connessioni per ogni host percorso o la destinazione che usiamo. La dimensione predefinita del pool di connessioni simultanee che possono essere aperte dal gestore è 2 per ogni route o host di destinazione e 20 per le connessioni aperte totali . Innanzitutto, diamo un'occhiata a come impostare questo gestore di connessione su un semplice HttpClient:

Esempio 3.1. Impostazione di PoolingHttpClientConnectionManager su un HttpClient

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

Successivamente, vediamo come lo stesso gestore connessione può essere utilizzato da due HttpClient in esecuzione in due thread diversi:

Esempio 3.2. Utilizzo di due HttpClient per connettersi a un host di destinazione ciascuno

HttpGet get1 = new HttpGet("/"); HttpGet get2 = new HttpGet("//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();

Si noti che stiamo utilizzando un'implementazione del thread personalizzato molto semplice : eccola:

Esempio 3.3. Thread personalizzato che esegue una richiesta GET

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

Notare la chiamata EntityUtils.consume (response.getEntity) , necessaria per consumare l'intero contenuto della risposta (entità) in modo che il gestore possa rilasciare la connessione al pool .

4. Configurare il Connection Manager

Le impostazioni predefinite del gestore connessione pool sono ben scelte ma, a seconda del caso d'uso, potrebbero essere troppo piccole. Quindi, diamo un'occhiata a come possiamo configurare:

  • il numero totale di connessioni
  • il numero massimo di connessioni per (qualsiasi) rotta
  • il numero massimo di connessioni per una singola rotta specifica

Esempio 4.1. Aumentare il numero di connessioni che possono essere aperte e gestite oltre i limiti predefiniti

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

Ricapitoliamo l'API:

  • setMaxTotal (int max) : Imposta il numero massimo di connessioni aperte totali.
  • setDefaultMaxPerRoute (int max) : imposta il numero massimo di connessioni simultanee per route, che è 2 per impostazione predefinita.
  • setMaxPerRoute (int max) : imposta il numero totale di connessioni simultanee a una route specifica, che è 2 per impostazione predefinita.

Quindi, senza modificare l'impostazione predefinita, raggiungeremo i limiti del gestore delle connessioni abbastanza facilmente - vediamo come appare:

Esempio 4.2. Utilizzo dei thread per eseguire le connessioni

HttpGet get = new HttpGet("//www.baeldung.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();

Come abbiamo già discusso, il limite di connessione per host è 2 per impostazione predefinita. Quindi, in questo esempio, stiamo cercando di fare in modo che 3 thread facciano 3 richieste allo stesso host , ma solo 2 connessioni verranno allocate in parallelo.

Diamo un'occhiata ai log: abbiamo tre thread in esecuzione ma solo 2 connessioni in leasing:

[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. Strategia di connessione Keep-Alive

Citando HttpClient 4.3.3. riferimento: "Se l' Keep-Aliveintestazione non è presente nella risposta, HttpClient presume che la connessione possa essere mantenuta attiva a tempo indeterminato." (Vedere il riferimento a HttpClient).

Per aggirare questo problema ed essere in grado di gestire le connessioni inattive, è necessaria un'implementazione della strategia personalizzata e incorporarla in HttpClient .

Esempio 5.1. Una strategia personalizzata Keep Alive

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

Questa strategia proverà prima ad applicare la politica Keep-Alive dell'host indicata nell'intestazione. Se tale informazione non è presente nell'intestazione della risposta, manterrà le connessioni attive per 5 secondi.

Ora, creiamo un cliente con questa strategia personalizzata :

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

6. Persistenza / riutilizzo della connessione

La specifica HTTP / 1.1 afferma che le connessioni possono essere riutilizzate se non sono state chiuse - questo è noto come persistenza della connessione.

Una volta che una connessione viene rilasciata dal gestore, rimane aperta per il riutilizzo. Quando si utilizza un BasicHttpClientConnectionManager, che può gestire solo una singola connessione, la connessione deve essere rilasciata prima di essere nuovamente affittata:

Esempio 6.1. Riutilizzo della connessione BasicHttpClientConnectionManager

BasicHttpClientConnectionManager basicConnManager = new BasicHttpClientConnectionManager(); HttpClientContext context = HttpClientContext.create(); // low level HttpRoute route = new HttpRoute(new 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 = new HttpRequestExecutor(); context.setTargetHost((new HttpHost("www.baeldung.com", 80))); HttpGet get = new HttpGet("//www.baeldung.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);

Diamo un'occhiata a cosa succede.

Primo: notare che stiamo usando prima una connessione di basso livello, solo per avere il pieno controllo su quando la connessione viene rilasciata, quindi una normale connessione di livello superiore con un HttpClient. La complessa logica di basso livello non è molto rilevante qui: l'unica cosa che ci interessa è la chiamata releaseConnection . Questo rilascia l'unica connessione disponibile e ne consente il riutilizzo.

Quindi, il client esegue nuovamente la richiesta GET con successo. Se saltiamo il rilascio della connessione, otterremo un'eccezione IllegalStateException da 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)

Si noti che la connessione esistente non viene chiusa, ma solo rilasciata e quindi riutilizzata dalla seconda richiesta.

In contrasto con l'esempio precedente, The PoolingHttpClientConnectionManager consente il riutilizzo della connessione in modo trasparente senza la necessità di rilasciare una connessione implicitamente:

Esempio 6.2. PoolingHttpClientConnectionManager : riutilizzo di connessioni con thread

HttpGet get = new HttpGet("//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); }

L'esempio sopra ha 10 thread, che eseguono 10 richieste ma condividono solo 5 connessioni.

Ovviamente, questo esempio si basa sul timeout Keep-Alive del server . Per assicurarsi che le connessioni non muoiano prima di essere riutilizzate, si consiglia di configurare il client con una strategia Keep-Alive (Vedi Esempio 5.1.).

7. Configurazione dei timeout - Timeout socket mediante Connection Manager

L'unico timeout che può essere impostato nel momento in cui viene configurato il gestore connessione è il timeout del socket:

Esempio 7.1. Impostazione del timeout socket a 5 secondi

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

Per una discussione più approfondita sui timeout in HttpClient, vedere questo.

8. Sfratto di connessione

L'eliminazione delle connessioni viene utilizzata per rilevare le connessioni inattive e scadute e chiuderle ; ci sono due opzioni per farlo.

  1. Affidarsi a HttpClien t per verificare se la connessione è obsoleta prima di eseguire una richiesta. Questa è un'opzione costosa che non è sempre affidabile.
  2. Creare un thread di monitoraggio per chiudere le connessioni inattive e / o chiuse.

Esempio 8.1. Impostazione di HttpClient per verificare la presenza di connessioni obsolete

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

Esempio 8.2. Utilizzo di un thread di monitoraggio della connessione non aggiornato

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

The IdleConnectionMonitorThreadclass is listed below:

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. Connection Closing

A connection can be closed gracefully (an attempt to flush the output buffer prior to closing is made), or forcefully, by calling the shutdown method (the output buffer is not flushed).

To properly close connections we need to do all of the following:

  • consume and close the response (if closeable)
  • close the client
  • close and shut down the connection manager

Example 8.1. Closing Connection and Releasing Resources

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

If the manager is shut down without connections being closed already – all connections will be closed and all resources released.

It's important to keep in mind that this will not flush any data that may have been ongoing for the existing connections.

10. Conclusion

In questo articolo abbiamo discusso come utilizzare l'API di gestione delle connessioni HTTP di HttpClient per gestire l'intero processo di gestione delle connessioni , dall'apertura e allocazione, passando per la gestione del loro utilizzo simultaneo da parte di più agenti, fino alla chiusura finale.

Abbiamo visto come BasicHttpClientConnectionManager è una soluzione semplice per gestire singole connessioni e come può gestire connessioni di basso livello. Abbiamo anche visto come PoolingHttpClientConnectionManager combinato con l' API HttpClient fornisce un utilizzo efficiente e conforme al protocollo delle connessioni HTTP.