Gerenciamento de conexões HttpClient

Gerenciamento de conexões HttpClient

1. Visão geral

Neste artigo, abordaremos os conceitos básicos do gerenciamento de conexões no HttpClient 4.

Cobriremos o uso deBasichttpClientConnectionManagerePoolingHttpClientConnectionManager para garantir um uso seguro, compatível com o protocolo e eficiente de conexões HTTP.

2. OBasicHttpClientConnectionManager para uma conexão de baixo nível, com rosca única

OBasicHttpClientConnectionManager está disponível desde HttpClient 4.3.3 como a implementação mais simples de um gerenciador de conexões HTTP. É usado para criar e gerenciar uma única conexão que só pode ser usada por um encadeamento por vez.

Leitura adicional:

Configuração avançada do HttpClient

Configurações HttpClient para casos de uso avançados.

Read more

Como enviar cookies personalizados com o Apache HttpClient 4.

Read more

HttpClient com SSL

Exemplo de como configurar o HttpClient com SSL.

Read more

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

O métodorequestConnection obtém do gerenciador um conjunto de conexões para umroute específico para se conectar. O parâmetroroute especifica uma rota de “saltos de proxy” para o host de destino ou o próprio host de destino.

É possível executar uma solicitação usando umHttpClientConnection diretamente, mas tenha em mente que essa abordagem de baixo nível é prolixa e difícil de gerenciar. Conexões de baixo nível são úteis para acessar dados de soquete e conexão, como tempos limite e informações de host de destino, mas para execuções padrão, oHttpClient é uma API muito mais fácil de trabalhar.

3. UsandoPoolingHttpClientConnectionManager para obter e gerenciar um conjunto de conexões multithread

OPoolingHttpClientConnectionManager criará e gerenciará um pool de conexões para cada rota ou host de destino que usarmos. O tamanho padrão do pool deconnections simultâneos que pode ser aberto pelo gerenciador é2 for each route or target host e20 for total conexões abertas. Primeiro - vamos dar uma olhada em como configurar este gerenciador de conexão em um HttpClient simples:

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

A seguir - vamos ver como o mesmo gerenciador de conexão pode ser usado por dois HttpClients em execução em dois threads diferentes:

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

Observe que estamos usandoa very simple custom thread implementation - aqui está:

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

Observe a chamadaEntityUtils.consume(response.getEntity) - necessária para consumir todo o conteúdo da resposta (entidade) para que o gerente possarelease the connection back to the pool.

4. Configure o Connection Manager

Os padrões do gerenciador de conexões de pool são bem escolhidos, mas - dependendo do seu caso de uso - pode ser muito pequeno. Então - vamos dar uma olhada em como podemos configurar:

  • o número total de conexões

  • o número máximo de conexões por (qualquer) rota

  • o número máximo de conexões por uma única rota específica

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

Vamos recapitular a API:

  • setMaxTotal(int max): Defina o número máximo de conexões abertas totais.

  • setDefaultMaxPerRoute(int max): Defina o número máximo de conexões simultâneas por rota, que é 2 por padrão.

  • setMaxPerRoute(int max): Defina o número total de conexões simultâneas para uma rota específica, que é 2 por padrão.

Portanto, sem alterar o padrão,we’re going to reach the limits of the connection manager com bastante facilidade - vamos ver como fica:

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

Como já discutimos,the per host connection limit is 2 por padrão. Portanto, neste exemplo, estamos tentando fazer com que 3 threads façam3 requests to the same host, mas apenas 2 conexões serão alocadas em paralelo.

Vamos dar uma olhada nos logs - temos três threads em execução, mas apenas 2 conexões alugadas:

[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. Estratégia Keep-Alive de conexão

Citando o HttpClient 4.3.3. referência: “Se o cabeçalhoKeep-Alive não estiver presente na resposta,HttpClient assume que a conexão pode ser mantida ativa indefinidamente.” (See the HttpClient Reference).

Para contornar isso e ser capaz de gerenciar conexões mortas, precisamos de uma implementação de estratégia personalizada e incorporá-la emHttpClient.

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

Esta estratégia tentará primeiro aplicar a políticaKeep-Alive do host indicada no cabeçalho. Se essa informação não estiver presente no cabeçalho da resposta, ela manterá as conexões ativas por 5 segundos.

Agora - vamos criara client with this custom strategy:

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

6. Persistência / Reutilização da conexão

A especificação HTTP / 1.1 indica que as conexões podem ser reutilizadas se não tiverem sido fechadas - isso é conhecido como persistência da conexão.

Depois que uma conexão é liberada pelo gerente, ela permanece aberta para reutilização. Ao usar umBasicHttpClientConnectionManager, que só pode gerenciar uma única conexão, a conexão deve ser liberada antes de ser alugada novamente:

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

Vamos dar uma olhada no que acontece.

Primeiro - observe que estamos usando uma conexão de baixo nível primeiro, apenas para termos controle total sobre quando a conexão é liberada e, em seguida, uma conexão normal de nível superior com um HttpClient. A complexa lógica de baixo nível não é muito relevante aqui - a única coisa com a qual nos importamos é a chamadareleaseConnection. Isso libera a única conexão disponível e permite que ela seja reutilizada.

Em seguida, o cliente executa a solicitação GET novamente com êxito. Se pularmos a liberação da conexão, obteremos uma IllegalStateException do 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)

Observe que a conexão existente não é fechada, apenas liberada e reutilizada pela segunda solicitação.

Em contraste com o exemplo acima, oPoolingHttpClientConnectionManager permite a reutilização da conexão de forma transparente, sem a necessidade de liberar uma conexão implicitamente:

Exemplo 6.2 *PoolingHttpClientConnectionManager: Reutilizando conexões com roscas *

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

O exemplo acima possui 10 threads, executando 10 solicitações, mas compartilhando apenas 5 conexões.

Claro, este exemplo depende do tempo limite deKeep-Alive do servidor. Para garantir que as conexões não morram antes de serem reutilizadas, é recomendado configurarclient com uma estratégiaKeep-Alive (Ver Exemplo 5.1.).

7. Configurando tempos limite - tempo limite do soquete usando o gerenciador de conexões

O único tempo limite que pode ser definido no momento em que o gerenciador de conexões é configurado é o tempo limite do soquete:

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

Para uma discussão mais aprofundada sobre tempos limite no HttpClient -see this.

8. Remoção de conexão

Remoção de conexão é usada paradetect idle and expired connections and close them; existem duas opções para fazer isso.

  1. Contando comHttpClient para verificar se a conexão está obsoleta antes de executar uma solicitação. Essa é uma opção cara, que nem sempre é confiável.

  2. Crie um encadeamento do monitor para fechar conexões ociosas e / ou fechadas.

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

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

A classeIdleConnectionMonitorThread está listada abaixo:

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. Fechamento de Conexão

Uma conexão pode ser fechada normalmente (uma tentativa de liberar o buffer de saída antes do fechamento é feita) ou forçosamente, chamando o métodoshutdown (o buffer de saída não é liberado).

Para fechar corretamente as conexões, precisamos fazer o seguinte:

  • consumir e fechar a resposta (se possível)

  • fechar o cliente

  • fechar e desligar o gerenciador de conexões

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

Se o gerente for desligado sem que as conexões já estejam fechadas - todas as conexões serão fechadas e todos os recursos liberados.

É importante ter em mente que isso não irá liberar nenhum dado que possa estar em andamento para as conexões existentes.

10. Conclusão

Neste artigo, discutimos como usar a API de gerenciamento de conexão HTTP de HttpClient para lidar comthe entire process of managing connections - desde abri-los e alocá-los, por meio do gerenciamento de seu uso simultâneo por vários agentes, até finalmente fechá-los.

Vimos como oBasicHttpClientConnectionManager é uma solução simples para lidar com conexões únicas e como pode gerenciar conexões de baixo nível. Também vimos como oPoolingHttpClientConnectionManager combinado com a APIHttpClient fornece um uso eficiente e compatível com o protocolo de conexões HTTP.