Article From:https://www.cnblogs.com/mumuxinfei/p/9121829.html

 

Preface

  Apache’s httpclient (4.5.x) has been used for interactive processing of HTTP, while the httpclient instance uses the HTTP connection pool, and if the connection pool is involved, there are some hidden pits in the use of the pool.In fact, by analyzing the httpclient source code, it is found that it solves this problem gracefully, while hiding all the details of the connection pool. Here today here are taking notes here.

 

Official code snippet

  This is a snippet of Apache httpclient official website:

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://targethost/homepage");
CloseableHttpResponse response1 = httpclient.execute(httpGet);
// The connection object is held by the response object to ensure that the content is consumed through the response object./ / ensure that ClosableHttpResponse#close calls are added to the finally code block./ / to be noteworthy, ifThe connection is not completely consumed, and the connection will not be safely multiplexed, will be closed, and will be discarded by the connection pool.Try {System.out.println (response1.getStatusLine ());HttpEntity entity1 = response1.getEntity ();/ / / / do something useful with the response body// and ensure it is fully consumedEntityUtils.consume (entity1);} finally {Response1.close ();}

  Simple analysis of the code, very concise, you can not see any connection pool operation clues, how it is designed, and how to do it?

Attention point of regular connection pool

  The use of the connection pool needs to ensure the following points, especially for the self researched connection pool.
  1. ConnectionGet/release pairing.
  2. Ensure that the request / response processing in a HTTP interaction is complete and clean (cleanup).
  For example, in a request interaction, the content is still in the socket cache for some reason, causing the content to be in the caching of the socket. Then, the response content of the same connection is the first response, the consequences are terrible. The time of the c++ development time,When you write the redis connection pool, you encounter similar problems, which is very impressive.

 

Connection encapsulation

  httpclientThe ConnectionHolder class is introduced, and a bridge between the real connection (HttpCilentConnection) and the connection pool (HttpClientConnectionManager) is built, and the connection is maintained at the same time.Use (reusable) and lease (leased) state.

class ConnectionHolder implements ConnectionReleaseTrigger, 
        Cancellable, Closeable {
    private final Log log;
    private final HttpClientConnectionManager manager;
    private final HttpClientConnection managedConn;
    private final AtomicBoolean released;  // Connection pool lease statusPrivate volatile Boolean reusable; / / connection is reusable}

  The most important method of this class is releaseConnection, and subsequent execution processes will more or less involve this method.

private void releaseConnection(boolean reusable) {
    // *) Determine the status of the lease. If the connection pool has been returned, the subsequent code will no longer be executed.If (this.released.compareAndSet (false, true)) {HttpClientConNection var2 = this.managedConn;Synchronized (this.managedConn) {/ *) based on ReusabilityCondition processing and return to the connection poolIf (reusable) {This.manager.releaseConnection (this.manageDConn,This.state, this.validDuration, this.tunit);} else {Try {/ / *) close connectionThis.managedConn.close ();This.log.debug ("Connection discarded");} catch (IOException var9){If (this.log.isDebugEnabled ()) {This.log.debug (var9.geTMessage () (), var9);}} finally {This.manageR.releaseConnection (this.managedConn,(Object) null, 0L, TimeUnit.MILLISECONDS);}}}}}

  CloseableHttpResponse also holds ConnectionHolder objects, and its close method essentially calls the releaseConnection side of ConnectionHolder indirectly.Law.

class HttpResponseProxy implements CloseableHttpResponse {

    public void close() throws IOException {
        if(this.connHolder != null) {
            this.connHolder.close();
        }
    }
}

class ConnectionHolder 
        implements ConnectionReleaseTrigger, Cancellable, Closeable {

    public void close() throws IOException {
        this.releaseConnection(false);
    }

}

  Thus it can be seen that the recommended practice of the official sample, guaranteed the call of the ClosableHttpResponse#close in the finally, can ensure the get/release pairing of the connection pool. If close, the connection stateThe connection is still not explicitly reused (leased is false).

 

Reusability judgment

  httpThe rule of long connection reuse is divided into two categories.
  1. httpProtocol support + request / response header specified
  2. Integrity of interactive processing (clean response content consumption)
  For the former, httpclient introduces ConnectionReuseStrategy to deal with it.

  • HTTP/1.0Connection:Keep-Alive is added to Header to indicate support for long connections.
  • HTTP/1.1Default support for long connections, unless explicitly specified Connection:Close in Header is considered a short join mode.

  The related code fragments in the MainClientExec class:

var27 = this.requestExecutor.execute(request, managedConn, context);
if(this.reuseStrategy.keepAlive(var27, context)) {
    long entity = this.keepAliveStrategy.getKeepAliveDuration(var27, context);
    if(this.log.isDebugEnabled()) {
        String s;
        if(entity > 0L) {
            s = "for " + entity + " " + TimeUnit.MILLISECONDS;
        } else {
            s = "indefinitely";
        }

        this.log.debug("Connection can be kept alive " + s);
    }

    var25.setValidFor(entity, TimeUnit.MILLISECONDS);
    var25.markReusable();
} else {
    var25.markNonReusable();
}

  In specific ReusableStrategy, the execution code is as follows:

public class DefaultClientConnectionReuseStrategy 
            extends DefaultConnectionReuseStrategy {
    public static final DefaultClientConnectionReuseStrategy INSTANCE 
            = new DefaultClientConnectionReuseStrategy();

    public DefaultClientConnectionReuseStrategy() {
    }

    public boolean keepAlive(HttpResponse response, HttpContext context) {
        HttpRequest request = (HttpRequest)context
              .getAttribute("http.request");
        if(request != null) {
            // *) Looking for Connection:CloseHeader[] connHeaders = request.getHeaders ("Connection");If (connHeaders.length! = 0) {BasicTokenIterator Ti = new BasicTokenIterator (New BasicHeaderIterator (connHeaders, (String) null));While (ti.hasNext ()) {String token = ti.nextToken ();If ("Close".EqualsIgnoreCase (token)) {Return false;}}}}Return super.keepAlive (response, context);}}
 In the keepAlive function of the parent class, it is as follows:
public class DefaultConnectionReuseStrategy 
        implements ConnectionReuseStrategy {

    public boolean keepAlive(HttpResponse response, HttpContext context) {
        // Omit a piece of codeIf (headerIterator1.hasNext ()) {Try {BasicTokenIteratorPX1 = new BasicTokenIterator (headerIterator1);Boolean keepalive1 = false;While (px1.hasNext ()) {String token = px1.nextToken ();/ *) there is a Close Tag, which is not reusableIf ("Close".EqualsIgnoreCase (token)) {Return false;}/ / *) the existence of Keep-Alive Tag is reusableIf ("Keep-Alive".EqualsIgnoreCase (token)) {Keepalive1 = true;}}If (keepalive1) {ReturnTrue;}} catch (ParseException var11) {Return false;}}/ / / / HTTP/1.0 version of all reusable connectionsReturn! Ver1.lessEquals (HttpVersion.HTTP_1_0);}}

  Sum up:

  • requestThe first part contains Connection:Close, not reused
  • responseIn the middle Content-Length, the length is not set correctly and does not reuse
  • responseThe first includes Connection:Close, not reused
  • reponseThe first includes Connection:Keep-Alive, reuse
  • If there is no hit, if HTTP version is higher than 1, reuse.

  And for the latter (the integrity of an interaction), how can this be decided? In fact, it’s very simple that the InputStream (HttpEntity#getContent) returned by response explicitly calls the close method (without causing s)Ocket’s close), that is, that consumption is complete.
  Let’s briefly analyze the EntityUtils.consume method.

public final class EntityUtils {

    public static void consume(HttpEntity entity) throws IOException {
        if(entity != null) {
            if(entity.isStreaming()) {
                InputStream instream = entity.getContent();
                if(instream != null) {
                    instream.close();
                }
            }
        }
    }

} 

  Let’s add breakpoints in the releaseConnection method of the ConnectionHolder class.

  

  Then we execute a HTTP request, and we will find that the thread calling stack when the program runs to the breakpoint is as follows:

"main@1" prio=5 tid=0x1 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
      at org.apache.http.impl.execchain.ConnectionHolder.releaseConnection(ConnectionHolder.java:97)
      at org.apache.http.impl.execchain.ConnectionHolder.releaseConnection(ConnectionHolder.java:120)
      at org.apache.http.impl.execchain.ResponseEntityProxy.releaseConnection(ResponseEntityProxy.java:76)
      at org.apache.http.impl.execchain.ResponseEntityProxy.streamClosed(ResponseEntityProxy.java:145)
      at org.apache.http.conn.EofSensorInputStream.checkClose(EofSensorInputStream.java:228)
      at org.apache.http.conn.EofSensorInputStream.close(EofSensorInputStream.java:172)
      at org.apache.http.client.entity.LazyDecompressingInputStream.close(LazyDecompressingInputStream.java:97)
      at org.apache.http.util.EntityUtils.consume(EntityUtils.java:90)

  You will find the call of the inputstream#close, which will trigger the return of the connection, and at this time the reusable state is true (precondition KeepaliveStrategy determines that the connection is reusable).
  Add an additional close implementation of the ContentLengthInputStream class defined in Apache HttpClient to make it clear that close will consume the data with the data in order to eliminate the final doubt.

public class ContentLengthInputStream extends InputStream {

    // *) The close will consume all the remaining bytes before setting itself in a closed state.Public void close () throws IOException {If (! This.closed){Try {If (this.pos < this.contentLength) {Byte[] buffer = new byte[2048];While (true) {If (this.read (b)Uffer) > = 0) {Continue;}}}} finally {This.closed = true;}}}}

  

summary

  Let’s go back to the original official sample code.

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

    // *) Triggering releaseConnect () calls, the reusable value depends on the keepAliveStrategy decision, and the leased is true.EntityUtils.consume (entity1);} finally {/ / * if leased is connected to false, then releaseConnect (false) is called explicitly, and leased is true./ / *) if evenThen leased is true, then do nothingResponse1.close ();}

  c++The RAII mode is used to automatically implement and release resources by using the structure / destructor of the object. In the Java side, a clear finally is needed to add the guaranteed release code.
  In general, the code is perfect. For the official recommended code, you can use it safely and boldly.

 

Reference article

  HttpPersistent connection and HttpClient connection pool
  Research on the retrial strategy of HttpClient

 

Similar Posts:

Leave a Reply

Your email address will not be published. Required fields are marked *