Apache HttpClient Connection Management – Apache HttpClient连接管理

最后修改: 2014年 7月 6日

1. Overview

1.概述

In this tutorial, we’ll go over the basics of connection management within HttpClient 4.

在本教程中,我们将介绍HttpClient 4中连接管理的基本知识。

We’ll cover the use of BasicHttpClientConnectionManager and PoolingHttpClientConnectionManager to enforce a safe, protocol-compliant and efficient use of HTTP connections.

我们将涵盖BasicHttpClientConnectionManagerPoolingHttpClientConnectionManager的使用,以执行安全、符合协议和高效的HTTP连接的使用。

2. The BasicHttpClientConnectionManager for a Low-Level, Single-Threaded Connection

2.用于低级别、单线程连接的BasicHttpClientConnectionManager

The BasicHttpClientConnectionManager is available since HttpClient 4.3.3 as the simplest implementation of an HTTP connection manager.

BasicHttpClientConnectionManager从HttpClient 4.3.3开始就可以作为HTTP连接管理器的最简单实现。

We use it to create and manage a single connection that only one thread can use at a time.

Example 2.1. Getting a Connection Request for a Low-Level Connection (HttpClientConnection)

我们用它来创建和管理一个单一的连接,每次只有一个线程可以使用。

例2.1.获取一个低级连接的连接请求(HttpClientConnection

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

The requestConnection method gets from the manager a pool of connections for a specific route to connect to. The route parameter specifies a route of “proxy hops” to the target host, or the target host itself.

requestConnection方法从管理器获得一个连接池,用于连接特定的routeroute参数指定了一条通往目标主机的 “代理跳 “路线,或者目标主机本身。

It is possible to run a request using an HttpClientConnection directly. However, keep in mind this low-level approach is verbose and difficult to manage. Low-level connections are useful to access socket and connection data such as timeouts and target host information. But for standard executions, the HttpClient is a much easier API to work against.

可以直接使用HttpClientConnection来运行一个请求。然而,请记住,这种低级别的方法是冗长的,而且难以管理。低级连接对于访问套接字和连接数据很有用,比如超时和目标主机信息。但是对于标准的执行,HttpClient是一个更容易操作的API。

3. Using the PoolingHttpClientConnectionManager to Get and Manage a Pool of Multithreaded Connections

3.使用PoolingHttpClientConnectionManager来获取和管理多线程连接池

The PoolingHttpClientConnectionManager will create and manage a pool of connections for each route or target host we use. The default size of the pool of concurrent connections that can be open by the manager is two for each route or target host and 20 for total open connections.

PoolingHttpClientConnectionManager将为我们使用的每个路由或目标主机创建并管理一个连接池。管理器可以打开的并发连接池的默认大小是每个路由或目标主机两个,开放连接数为20。

First, let’s take a look at how to set up this connection manager on a simple HttpClient:

首先,让我们看一下如何在一个简单的HttpClient上设置这个连接管理器。

Example 3.1. Setting the PoolingHttpClientConnectionManager on an HttpClient

例3.1.在一个HttpClient上设置PoolingHttpClientConnectionManager

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

Next, let’s see how the same connection manager can be used by two HttpClients running in two different threads:

接下来,让我们看看同一个连接管理器如何被运行在两个不同线程中的两个HttpClients使用。

Example 3.2. Using Two HttpClients to Connect to One Target Host Each

例3.2.使用两个HttpClients分别连接到一个目标主机

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

Notice that we’re using a very simple custom thread implementation:

注意,我们正在使用一个非常简单的自定义线程实现

Example 3.3. Custom Thread Executing a GET Request

例3.3.执行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) {
        }
    }
}

Notice the EntityUtils.consume(response.getEntity) call. This is necessary to consume the entire content of the response (entity) so that the manager can release the connection back to the pool.

请注意 EntityUtils.consume(response.getEntity)调用。这对于消耗响应(实体)的全部内容是必要的,这样管理器就可以将连接释放回池中。

4. Configure the Connection Manager

4.配置连接管理器

The defaults of the pooling connection manager are well chosen. But, depending on our use case, they may be too small.

池化连接管理器的默认值是很好的选择。但是,根据我们的使用情况,它们可能太小了。

So, let’s see how we can configure

因此,让我们看看如何配置

  • the total number of connections
  • the maximum number of connections per (any) route
  • the maximum number of connections per a single, specific route

Example 4.1. Increasing the Number of Connections That Can Be Open and Managed Beyond the default Limits

例4.1.增加可打开和管理的连接数,超过默认限制

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

Let’s recap the API:

让我们回顾一下API的情况。

  • setMaxTotal(int max) – Set the maximum number of total open connections
  • setDefaultMaxPerRoute(int max) – Set the maximum number of concurrent connections per route, which is two by default
  • setMaxPerRoute(int max) – Set the total number of concurrent connections to a specific route, which is two by default

So, without changing the default, we’re going to reach the limits of the connection manager quite easily.

因此,如果不改变默认值,我们将很容易达到连接管理器的极限

Let’s see what that looks like:

让我们看看这看起来像什么。

Example 4.2. Using Threads to Execute Connections

例4.2.使用线程来执行连接

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

Remember that the per host connection limit is two by default. So, in this example, we want three threads to make three requests to the same host, but only two connections will be allocated in parallel.

请记住,每个主机的连接限制是两个,默认情况下。因此,在这个例子中,我们想让三个线程向同一个主机发出三个请求,但只有两个连接将被并行分配。

Let’s take a look at the logs.

让我们看一下日志。

We have three threads running but only two leased connections:

我们有三个线程在运行,但只有两个租用的连接。

[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. Connection Keep-Alive Strategy

5.连接的保持时间策略

According to the HttpClient 4.3.3. reference: “If the Keep-Alive header is not present in the response, HttpClient assumes the connection can be kept alive indefinitely.” (See the HttpClient Reference).

根据HttpClient 4.3.3.参考资料。”如果Keep-Alive头在响应中不存在,HttpClient假定连接可以无限期地保持活力。”(见HttpClient参考资料)。

To get around this and be able to manage dead connections, we need a customized strategy implementation and to build it into the HttpClient.

为了绕过这一点并能够管理死连接,我们需要一个定制的策略实现,并将其构建到HttpClient中。

Example 5.1. A Custom Keep-Alive Strategy

例5.1.一个自定义的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;
    }
};

This strategy will first try to apply the host’s Keep-Alive policy stated in the header. If that information is not present in the response header, it will keep alive connections for five seconds.

这个策略将首先尝试应用头中所述的主机的Keep-Alive策略。如果该信息没有出现在响应头中,它将保持5秒钟的连接。

Now let’s create a client with this custom strategy:

现在让我们用这个自定义策略创建一个客户

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

6. Connection Persistence/Reuse

6.连接的持久性/重复使用

The HTTP/1.1 Spec states that we can reuse connections if they have not been closed. This is known as connection persistence.

HTTP/1.1规范指出,如果连接没有被关闭,我们可以重复使用。这就是所谓的连接持久性。

Once the manager releases a connection, it stays open for reuse.

一旦管理器释放了一个连接,它就会保持开放,以便重复使用。

When using a BasicHttpClientConnectionManager, which can only mange a single connection, the connection must be released before it is leased back again:

当使用一个只能管理一个连接的BasicHttpClientConnectionManager时,在再次租回连接之前必须释放该连接。

Example 6.1. BasicHttpClientConnectionManager Connection Reuse

例6.1.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("http://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);

Let’s take a look at what happens.

让我们来看看会发生什么。

Notice that we use a low-level connection first, just so that we have full control over when we release the connection, and then a normal higher-level connection with an HttpClient.

请注意,我们首先使用一个低级连接,只是为了让我们能够完全控制释放连接的时间,然后再使用一个正常的高级连接与HttpClient。

The complex low-level logic is not very relevant here. The only thing we care about is the releaseConnection call. That releases the only available connection and allows it to be reused.

复杂的底层逻辑在这里并不十分相关。我们唯一关心的是releaseConnection调用。这释放了唯一可用的连接,并允许它被重新使用。

Then the client runs the GET request again with success.

然后,客户端再次运行GET请求,并获得成功。

If we skip releasing the connection, we will get an IllegalStateException from the HttpClient:

如果我们跳过释放连接,我们将从HttpClient得到一个IllegalStateException。

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)

Note that the existing connection isn’t closed, just released and then reused by the second request.

注意,现有的连接并没有关闭,只是被释放,然后被第二个请求重新使用。

In contrast to the above example, the PoolingHttpClientConnectionManager allows connection reuse transparently without the need to release a connection implicitly:

与上面的例子相比,PoolingHttpClientConnectionManager允许连接透明地重用,而不需要隐式释放连接。

Example 6.2. PoolingHttpClientConnectionManager: Reusing Connections With Threads

例6.2. PoolingHttpClientConnectionManager:用线程重用连接

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

The example above has 10 threads running 10 requests but only sharing 5 connections.

上面的例子有10个线程运行10个请求,但只共享5个连接。

Of course, this example relies on the server’s Keep-Alive timeout. To make sure the connections don’t die before reuse, we should configure the client with a Keep-Alive strategy (See Example 5.1.).

当然,这个例子依赖于服务器的Keep-Alive超时。为了确保连接不会在重用前死亡,我们应该给client配置一个Keep-Alive策略(见例5.1)。

7. Configuring Timeouts – Socket Timeout Using the Connection Manager

7.配置超时 – 使用连接管理器的套接字超时

The only timeout that we can set when we configure the connection manager is the socket timeout:

当我们配置连接管理器时,唯一可以设置的超时是套接字超时。

Example 7.1. Setting Socket Timeout to 5 Seconds

例7.1.将套接字超时设置为5秒

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

For a more in-depth discussion of timeouts in the HttpClient, see here.

关于HttpClient中超时的更深入讨论,参见

8. Connection Eviction

8.连接驱逐

We use connection eviction to detect idle and expired connections and close them. We have two options to do this:

我们使用连接驱逐来检测空闲和过期的连接并关闭它们。我们有两个选项来做这个。

  1. Rely on the HttpClient to check if the connection is stale before running a request. This is an expensive option that is not always reliable.
  2. Create a monitor thread to close idle and/or closed connections

Example 8.1. Setting the HttpClient to Check for Stale Connections

例8.1.设置HttpClient以检查过期的连接

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

Example 8.2. Using a Stale Connection Monitor Thread

例8.2.使用陈旧的连接监控线程

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

Let’s look at the IdleConnectionMonitorThread class:

让我们来看看IdleConnectionMonitorThread类。

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

9.连接关闭

We can close a connection gracefully (we make an attempt to flush the output buffer prior to closing), or we can do it forcefully, by calling the shutdown method (the output buffer is not flushed).

我们可以优雅地关闭一个连接(我们在关闭前尝试刷新输出缓冲区),或者我们可以通过调用shutdown方法来强制关闭(输出缓冲区不被刷新)。

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 9.1. Closing Connection and Releasing Resources

例9.1.关闭连接和释放资源

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

If we shut down the manager without closing connections 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

10.结论

In this article, we discussed how to use the HTTP Connection Management API of HttpClient to handle the entire process of managing connections. This included opening and allocating them, managing their concurrent use by multiple agents and finally closing them.

在这篇文章中,我们讨论了如何使用HttpClient的HTTP连接管理API来处理管理连接的整个过程。这包括打开和分配它们,管理它们被多个代理并发使用,最后关闭它们。

We saw how the BasicHttpClientConnectionManager is a simple solution to handle single connections and how it can manage low-level connections.

我们看到了BasicHttpClientConnectionManager是如何处理单一连接的简单解决方案,以及它如何管理低级别的连接。

We also saw how the PoolingHttpClientConnectionManager combined with the HttpClient API provide for an efficient and protocol-compliant use of HTTP connections.

我们还看到了PoolingHttpClientConnectionManagerHttpClient API的结合,提供了高效且符合协议的HTTP连接使用。

The code used in this article can be found over on GitHub.

本文中使用的代码可以在GitHub上找到over