Gestion de la connexion HttpClient

Gestion de la connexion HttpClient

1. Vue d'ensemble

Dans cet article, nous allons passer en revue les bases de la gestion des connexions au sein de HttpClient 4.

Nous couvrirons l'utilisation deBasichttpClientConnectionManager etPoolingHttpClientConnectionManager pour assurer une utilisation sûre, conforme au protocole et efficace des connexions HTTP.

2. LesBasicHttpClientConnectionManager pour une connexion à un seul filetage de bas niveau

LeBasicHttpClientConnectionManager est disponible depuis HttpClient 4.3.3 comme l'implémentation la plus simple d'un gestionnaire de connexion HTTP. Il est utilisé pour créer et gérer une seule connexion qui ne peut être utilisée que par un seul thread à la fois.

Lectures complémentaires:

Configuration avancée du client

Configurations HttpClient pour les cas d'utilisation avancés.

Read more

Comment envoyer des cookies personnalisés avec Apache HttpClient 4.

Read more

HttpClient avec SSL

Exemple de configuration de HttpClient avec SSL.

Read more

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

La méthoderequestConnection obtient du gestionnaire un pool de connexions pour unroute spécifique auquel se connecter. Le paramètreroute spécifie une route de «sauts proxy» vers l'hôte cible, ou l'hôte cible lui-même.

Il est possible d'exécuter une requête en utilisant directement unHttpClientConnection, mais gardez à l'esprit que cette approche de bas niveau est verbeuse et difficile à gérer. Les connexions de bas niveau sont utiles pour accéder aux données de socket et de connexion telles que les délais d'expiration et les informations sur l'hôte cible, mais pour les exécutions standard, leHttpClient est une API beaucoup plus facile à utiliser.

3. Utilisation desPoolingHttpClientConnectionManager pour obtenir et gérer un pool de connexions multithread

LesPoolingHttpClientConnectionManager vont créer et gérer un pool de connexions pour chaque route ou hôte cible que nous utilisons. La taille par défaut du pool deconnections simultanés pouvant être ouverts par le gestionnaire est2 for each route or target host et20 for total connexions ouvertes. Tout d'abord, voyons comment configurer ce gestionnaire de connexions sur un simple HttpClient:

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

Ensuite, voyons comment le même gestionnaire de connexions peut être utilisé par deux HttpClients s'exécutant dans deux threads différents:

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

Notez que nous utilisonsa very simple custom thread implementation - le voici:

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

Notez l'appelEntityUtils.consume(response.getEntity) - nécessaire pour consommer tout le contenu de la réponse (entité) afin que le gestionnaire puisserelease the connection back to the pool.

4. Configurer le gestionnaire de connexion

Les valeurs par défaut du gestionnaire de connexions de mise en pool sont bien choisies mais, selon votre cas d'utilisation, risquent d'être trop petites. Alors - voyons comment nous pouvons configurer:

  • le nombre total de connexions

  • le nombre maximal de connexions par (n'importe quel) itinéraire

  • le nombre maximal de connexions par route unique et spécifique

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

Récapitulons l'API:

  • setMaxTotal(int max): définissez le nombre maximum de connexions ouvertes totales.

  • setDefaultMaxPerRoute(int max): définissez le nombre maximum de connexions simultanées par route, qui est de 2 par défaut.

  • setMaxPerRoute(int max): définissez le nombre total de connexions simultanées à une route spécifique, qui est de 2 par défaut.

Donc, sans changer la valeur par défaut,we’re going to reach the limits of the connection manager assez facilement - voyons à quoi cela ressemble:

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

Comme nous l'avons déjà mentionné,the per host connection limit is 2 par défaut. Donc, dans cet exemple, nous essayons d'avoir 3 threads pour faire3 requests to the same host, mais seulement 2 connexions seront allouées en parallèle.

Jetons un coup d'œil aux journaux - nous avons trois threads en cours d'exécution, mais seulement 2 connexions louées:

[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. Stratégie de connexion Keep-Alive

Citer le HttpClient 4.3.3. référence: «Si l'en-têteKeep-Alive n'est pas présent dans la réponse,HttpClient suppose que la connexion peut être maintenue indéfiniment active.» (See the HttpClient Reference).

Pour contourner cela et être capable de gérer les connexions mortes, nous avons besoin d'une implémentation de stratégie personnalisée et de l'intégrer dans lesHttpClient.

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

Cette stratégie essaiera d'abord d'appliquer la politiqueKeep-Alive de l'hôte indiquée dans l'en-tête. Si cette information ne figure pas dans l'en-tête de la réponse, les connexions resteront actives pendant 5 secondes.

Maintenant, créonsa client with this custom strategy:

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

6. Persistance / réutilisation de la connexion

La spécification HTTP / 1.1 indique que les connexions peuvent être réutilisées si elles n'ont pas été fermées - c'est ce que l'on appelle la persistance de la connexion.

Une fois la connexion établie par le gestionnaire, elle reste ouverte pour une utilisation ultérieure. Lors de l'utilisation d'unBasicHttpClientConnectionManager, qui ne peut gérer qu'une seule connexion, la connexion doit être libérée avant d'être à nouveau louée:

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

Voyons ce qui se passe.

Tout d'abord, notez que nous utilisons d'abord une connexion de bas niveau, juste pour avoir un contrôle total sur le moment où la connexion est libérée, puis une connexion de niveau supérieur normale avec un HttpClient. La logique complexe de bas niveau n'est pas très pertinente ici - la seule chose qui nous importe est l'appelreleaseConnection. Cela libère la seule connexion disponible et permet sa réutilisation.

Ensuite, le client exécute à nouveau la requête GET avec succès. Si nous omettons de libérer la connexion, nous obtiendrons une exception IllegalStateException à partir de 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)

Notez que la connexion existante n'est pas fermée, juste libérée, puis réutilisée par la deuxième demande.

Contrairement à l'exemple ci-dessus, lePoolingHttpClientConnectionManager permet la réutilisation de la connexion de manière transparente sans qu'il soit nécessaire de libérer une connexion implicitement:

Exemple 6.2. *PoolingHttpClientConnectionManager: Réutilisation des connexions avec des 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);
}

L'exemple ci-dessus contient 10 threads, exécutant 10 requêtes mais ne partageant que 5 connexions.

Bien entendu, cet exemple repose sur le délai d’expirationKeep-Alivedu serveur. Pour s'assurer que les connexions ne meurent pas avant d'être réutilisées, il est recommandé de configurer lesclient avec une stratégieKeep-Alive (voir l'exemple 5.1.).

7. Configuration des délais d'expiration - Délai d'expiration du socket à l'aide du gestionnaire de connexion

Le seul délai d'attente pouvant être défini au moment de la configuration du gestionnaire de connexions est le délai d'attente du socket:

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

Pour une discussion plus approfondie des délais d'expiration dans HttpClient -see this.

8. Expulsion de connexion

L'expulsion de connexion est utilisée pourdetect idle and expired connections and close them; il existe deux options pour ce faire.

  1. S'appuyer surHttpClient pour vérifier si la connexion est périmée avant d'exécuter une requête. C'est une option coûteuse qui n'est pas toujours fiable.

  2. Créez un thread de moniteur pour fermer les connexions inactives et / ou fermées.

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

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

La classeIdleConnectionMonitorThread est répertoriée ci-dessous:

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. Fermeture de la connexion

Une connexion peut être fermée correctement (une tentative de vidage du tampon de sortie avant la fermeture est effectuée), ou de force, en appelant la méthodeshutdown (le tampon de sortie n'est pas vidé).

Pour fermer correctement les connexions, nous devons effectuer toutes les tâches suivantes:

  • consommer et fermer la réponse (si fermable)

  • fermer le client

  • fermez et fermez le gestionnaire de connexion

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

Si le gestionnaire est arrêté sans que les connexions ne soient déjà fermées, toutes les connexions seront fermées et toutes les ressources libérées.

Il est important de garder à l'esprit que cela ne videra pas les données qui auraient pu être en cours pour les connexions existantes.

10. Conclusion

Dans cet article, nous avons expliqué comment utiliser l'API de gestion de connexion HTTP de HttpClient pour gérer lesthe entire process of managing connections - de leur ouverture et de leur allocation, en passant par la gestion de leur utilisation simultanée par plusieurs agents, pour finalement les fermer.

Nous avons vu comment leBasicHttpClientConnectionManager est une solution simple pour gérer des connexions uniques, et comment il peut gérer des connexions de bas niveau. Nous avons également vu comment lesPoolingHttpClientConnectionManager combinés à l'APIHttpClient permettent une utilisation efficace et conforme au protocole des connexions HTTP.