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.
HttpClient 4 - Enviar cookie personalizado
Como enviar cookies personalizados com o Apache HttpClient 4.
HttpClient com SSL
Exemplo de como configurar o HttpClient com SSL.
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.
-
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.
-
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.