25
CloseableHttpResponse response = null;
try {
    // do some thing ....
    HttpPost request = new HttpPost("some url");
    response = getHttpClient().execute(request);
    // do some other thing ....
} catch(Exception e) {
    // deal with exception
} finally {
    if(response != null) {
        try {
            response.close(); // (1)
        } catch(Exception e) {}
        request.releaseConnection(); // (2)
    }
}

I've made a http request like above.

In order to release the underlying connection, is it correct to call (1) and (2)? and what's the difference between the two invocation?

Cœur
  • 37,241
  • 25
  • 195
  • 267
wyang
  • 251
  • 1
  • 3
  • 7

2 Answers2

32

Short answer:

request.releaseConnection() is releasing the underlying HTTP connection to allow it to be reused. response.close() is closing a stream (not a connection), this stream is the response content we are streaming from the network socket.

Long Answer:

The correct pattern to follow in any recent version > 4.2 and probably even before that, is not to use releaseConnection.

request.releaseConnection() releases the underlying httpConnection so the request can be reused, however the Java doc says:

A convenience method to simplify migration from HttpClient 3.1 API...

Instead of releasing the connection, we ensure the response content is fully consumed which in turn ensures the connection is released and ready for reuse. A short example is shown below:

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://targethost/homepage");
CloseableHttpResponse response1 = httpclient.execute(httpGet);
try {
    System.out.println(response1.getStatusLine());
    HttpEntity entity1 = response1.getEntity();
    // do something useful with the response body
    String bodyAsString = EntityUtils.toString(exportResponse.getEntity());
    System.out.println(bodyAsString);
    // and ensure it is fully consumed (this is how stream is released.
    EntityUtils.consume(entity1);
} finally {
    response1.close();
}
bric3
  • 40,072
  • 9
  • 91
  • 111
Matt
  • 1,167
  • 1
  • 15
  • 26
  • 6
    Hi Matt , is there any reference for this? Documentation is not at all clear on how the connection can be released, for instance if through response.close or httpclient.close. All examples do contain only the former, so it appears this is enough, but just to be sure. Also can you explain why EntityUtils.consume is necessary even after EntityUtils.toString which should exhaust complete inputstream? – Sumit Jain Oct 07 '15 at 19:08
  • 1
    Why EntityUtils.consume? This answer addresses your question: "It really boils down to being a "good citizen" (and really knowing the contracts of HTTPClient interfaces). What EntityUtils.consume will do is release all resources held by the httpEntity, which essentially implies releasing any underlying Stream and giving the Connection object back to its pool (in the case your connection manager is a multithreaded one) or freeing the connection manager so that it can process the next request." - http://stackoverflow.com/questions/15969037/why-did-the-author-use-entityutils-consumehttpentity – Matt Oct 13 '15 at 19:32
  • 1
    Also: https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/fundamentals.html and check out section 1.1.5, and 1.1.6 which explain that you can close without consuming: "When working with streaming entities, one can use the EntityUtils#consume(HttpEntity) method to ensure that the entity content has been fully consumed and the underlying stream has been closed. There can be situations... when only a small portion of the entire response content needs to be retrieved and the performance penalty for consuming the remaining content and making the connection reusable is too high ...." – Matt Oct 13 '15 at 19:40
  • 1
    Would it make sense to add the EntityUtils.consume() method to a finally block? – user1172490 Dec 08 '16 at 02:54
  • 1
    Yup, that should work and would protect you against the case that an exception was thrown and so the .consume() method wasn't called. – Matt Dec 08 '16 at 18:19
  • 4
    If the use case is to safely release the connection by consuming the entity in finally block, then you can use EntityUtils.consumeQuietly().(i am using httpcore 4.4.6) – Thiyanesh May 29 '17 at 20:12
  • 4
    You should put the EntityUtils.consumeQuietly in a finally block. We saw a situation where pooled connections, using HttpPost, were not released since their contentEntity was not read. A test case demonstrated this to be correlated directly to a memory leak problem. – ledlogic Jul 12 '17 at 18:30
  • "response.close() is closing a stream (not a connection), this stream is the response content we are streaming from the network socket." is incorrect. It also closes the socket. This is the chain of calls: HttpResponseProxy.close() -> ConnectionHolder.close() -> ConnectionHolder.releaseConnection(reusable=false) -> managedConn.close() -> BHttpConnectionBase.close() -> Socket.close() – joseph Mar 24 '22 at 15:54
0
  1. CloseableHttpResponse.close() closes the tcp socket
  2. HttpPost.releaseConnection() closes the tcp socket
  3. EntityUtils.consume(response.getEntity()) allows you to re-use the tcp socket

Details

CloseableHttpResponse.close() closes the tcp socket, preventing the connection from being re-used. You need to establish a new tcp connection in order to initiate another request.

This is the call chain that lead me to the above conclusion:

  1. HttpResponseProxy.close()
  2. -> ConnectionHolder.close()
  3. -> ConnectionHolder.releaseConnection(reusable=false)
  4. -> managedConn.close()
  5. -> BHttpConnectionBase.close()
  6. -> Socket.close()

HttpPost.releaseConnection() also closes the Socket. This is the call chain that lead me to the above conclusion:

  1. HttpPost.releaseConnection()
  2. HttpRequestBase.releaseConnect()
  3. AbstractExecutionAwareRequest.reset()
  4. ConnectionHolder.cancel() (
  5. ConnectionHolder.abortConnection()
  6. HttpConnection.shutdown()

Here is experimental code that also demonstrates the above three facts:

import java.lang.reflect.Constructor;
import java.net.Socket;
import java.net.SocketImpl;
import java.net.SocketImplFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;

public class Main {

    private static SocketImpl newSocketImpl() {
        try {
            Class<?> defaultSocketImpl = Class.forName("java.net.SocksSocketImpl");
            Constructor<?> constructor = defaultSocketImpl.getDeclaredConstructor();
            constructor.setAccessible(true);
            return (SocketImpl) constructor.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Exception {
        // this is a hack that lets me listen to Tcp socket creation
        final List<SocketImpl> allSockets = Collections.synchronizedList(new ArrayList<>());
        Socket.setSocketImplFactory(new SocketImplFactory() {
            public SocketImpl createSocketImpl() {
                SocketImpl socket = newSocketImpl();
                allSockets.add(socket);
                return socket;
            }
        });

        System.out.println("num of sockets after start: " + allSockets.size());
        CloseableHttpClient client = HttpClientBuilder.create().build();
        System.out.println("num of sockets after client created: " + allSockets.size());
        HttpGet request = new HttpGet("http://www.google.com");
        System.out.println("num of sockets after get created: " + allSockets.size());
        CloseableHttpResponse response = client.execute(request);
        System.out.println("num of sockets after get executed: " + allSockets.size());
        response.close();
        System.out.println("num of sockets after response closed: " + allSockets.size());
        response = client.execute(request);
        System.out.println("num of sockets after request executed again: " + allSockets.size());
        request.releaseConnection();
        System.out.println("num of sockets after release connection: " + allSockets.size());
        response = client.execute(request);
        System.out.println("num of sockets after request executed again for 3rd time: " + allSockets.size());
        EntityUtils.consume(response.getEntity());
        System.out.println("num of sockets after entityConsumed: " + allSockets.size());
        response = client.execute(request);
        System.out.println("num of sockets after request executed again for 4th time: " + allSockets.size());
    }

}

pom.xml

<project>
    <modelVersion>4.0.0</modelVersion>
    

    <groupId>org.joseph</groupId>
    <artifactId>close.vs.release.conn</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>

    <build>
        <plugins>
         </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>
    </dependencies>
</project>

Output:

num of sockets after start: 0
num of sockets after client created: 0
num of sockets after get created: 0
num of sockets after get executed: 1
num of sockets after response closed: 1
num of sockets after request executed again: 2
num of sockets after release connection: 2
num of sockets after request executed again for 3rd time: 3
num of sockets after entityConsumed: 3
num of sockets after request executed again for 4th time: 3

Notice that both .close() and .releaseConnection() both result in a new tcp connection. Only consuming the entity allows you to re-use the tcp connection.

If you want the connect to be re-usable after each request, then you need to do what @Matt recommended and consume the entity.

joseph
  • 2,429
  • 1
  • 22
  • 43