gt:                         if (this.reuseStrategy.keepAlive(response, context)) {
context: /*
   * ====================================================================
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   */
  
  package ch.boye.httpclientandroidlib.impl.execchain;
  
  import java.io.IOException;
  import java.io.InterruptedIOException;
  import java.util.concurrent.ExecutionException;
  import java.util.concurrent.TimeUnit;
  
  import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
  /* LogFactory removed by HttpClient for Android script. */
  import ch.boye.httpclientandroidlib.ConnectionReuseStrategy;
  import ch.boye.httpclientandroidlib.HttpClientConnection;
  import ch.boye.httpclientandroidlib.HttpEntity;
  import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest;
  import ch.boye.httpclientandroidlib.HttpException;
  import ch.boye.httpclientandroidlib.HttpHost;
  import ch.boye.httpclientandroidlib.HttpRequest;
  import ch.boye.httpclientandroidlib.HttpResponse;
  import ch.boye.httpclientandroidlib.annotation.Immutable;
  import ch.boye.httpclientandroidlib.auth.AUTH;
  import ch.boye.httpclientandroidlib.auth.AuthProtocolState;
  import ch.boye.httpclientandroidlib.auth.AuthState;
  import ch.boye.httpclientandroidlib.client.AuthenticationStrategy;
  import ch.boye.httpclientandroidlib.client.NonRepeatableRequestException;
  import ch.boye.httpclientandroidlib.client.UserTokenHandler;
  import ch.boye.httpclientandroidlib.client.config.RequestConfig;
  import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse;
  import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware;
  import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper;
  import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext;
  import ch.boye.httpclientandroidlib.client.protocol.RequestClientConnControl;
  import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy;
  import ch.boye.httpclientandroidlib.conn.ConnectionRequest;
  import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager;
  import ch.boye.httpclientandroidlib.conn.routing.BasicRouteDirector;
  import ch.boye.httpclientandroidlib.conn.routing.HttpRoute;
  import ch.boye.httpclientandroidlib.conn.routing.HttpRouteDirector;
  import ch.boye.httpclientandroidlib.conn.routing.RouteTracker;
  import ch.boye.httpclientandroidlib.entity.BufferedHttpEntity;
  import ch.boye.httpclientandroidlib.impl.auth.HttpAuthenticator;
  import ch.boye.httpclientandroidlib.impl.conn.ConnectionShutdownException;
  import ch.boye.httpclientandroidlib.message.BasicHttpRequest;
  import ch.boye.httpclientandroidlib.protocol.HttpCoreContext;
  import ch.boye.httpclientandroidlib.protocol.HttpProcessor;
  import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor;
  import ch.boye.httpclientandroidlib.protocol.ImmutableHttpProcessor;
  import ch.boye.httpclientandroidlib.protocol.RequestTargetHost;
  import ch.boye.httpclientandroidlib.util.Args;
  import ch.boye.httpclientandroidlib.util.EntityUtils;
  
  /**
   * The last request executor in the HTTP request execution chain
   * that is responsible for execution of request / response
   * exchanges with the opposite endpoint.
   * This executor will automatically retry the request in case
   * of an authentication challenge by an intermediate proxy or
   * by the target server.
   *
   * @since 4.3
   */
  @Immutable
  public class MainClientExec implements ClientExecChain {
  
      public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass());
  
      private final HttpRequestExecutor requestExecutor;
      private final HttpClientConnectionManager connManager;
      private final ConnectionReuseStrategy reuseStrategy;
      private final ConnectionKeepAliveStrategy keepAliveStrategy;
      private final HttpProcessor proxyHttpProcessor;
      private final AuthenticationStrategy targetAuthStrategy;
      private final AuthenticationStrategy proxyAuthStrategy;
      private final HttpAuthenticator authenticator;
      private final UserTokenHandler userTokenHandler;
      private final HttpRouteDirector routeDirector;
  
  
      public MainClientExec(
              final HttpRequestExecutor requestExecutor,
              final HttpClientConnectionManager connManager,
              final ConnectionReuseStrategy reuseStrategy,
              final ConnectionKeepAliveStrategy keepAliveStrategy,
              final AuthenticationStrategy targetAuthStrategy,
              final AuthenticationStrategy proxyAuthStrategy,
              final UserTokenHandler userTokenHandler) {
          Args.notNull(requestExecutor, "HTTP request executor");
          Args.notNull(connManager, "Client connection manager");
          Args.notNull(reuseStrategy, "Connection reuse strategy");
          Args.notNull(keepAliveStrategy, "Connection keep alive strategy");
          Args.notNull(targetAuthStrategy, "Target authentication strategy");
          Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
          Args.notNull(userTokenHandler, "User token handler");
          this.authenticator      = new HttpAuthenticator();
          this.proxyHttpProcessor = new ImmutableHttpProcessor(
                  new RequestTargetHost(), new RequestClientConnControl());
          this.routeDirector      = new BasicRouteDirector();
          this.requestExecutor    = requestExecutor;
          this.connManager        = connManager;
          this.reuseStrategy      = reuseStrategy;
          this.keepAliveStrategy  = keepAliveStrategy;
          this.targetAuthStrategy = targetAuthStrategy;
          this.proxyAuthStrategy  = proxyAuthStrategy;
          this.userTokenHandler   = userTokenHandler;
      }
  
      public CloseableHttpResponse execute(
              final HttpRoute route,
              final HttpRequestWrapper request,
              final HttpClientContext context,
              final HttpExecutionAware execAware) throws IOException, HttpException {
          Args.notNull(route, "HTTP route");
          Args.notNull(request, "HTTP request");
          Args.notNull(context, "HTTP context");
  
          AuthState targetAuthState = context.getTargetAuthState();
          if (targetAuthState == null) {
              targetAuthState = new AuthState();
              context.setAttribute(HttpClientContext.TARGET_AUTH_STATE, targetAuthState);
          }
          AuthState proxyAuthState = context.getProxyAuthState();
          if (proxyAuthState == null) {
              proxyAuthState = new AuthState();
              context.setAttribute(HttpClientContext.PROXY_AUTH_STATE, proxyAuthState);
          }
  
          if (request instanceof HttpEntityEnclosingRequest) {
              Proxies.enhanceEntity((HttpEntityEnclosingRequest) request);
          }
  
          Object userToken = context.getUserToken();
  
          final ConnectionRequest connRequest = connManager.requestConnection(route, userToken);
          if (execAware != null) {
              if (execAware.isAborted()) {
                  connRequest.cancel();
                  throw new RequestAbortedException("Request aborted");
              } else {
                  execAware.setCancellable(connRequest);
              }
          }
  
          final RequestConfig config = context.getRequestConfig();
  
          final HttpClientConnection managedConn;
          try {
              final int timeout = config.getConnectionRequestTimeout();
              managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS);
          } catch(final InterruptedException interrupted) {
              Thread.currentThread().interrupt();
              throw new RequestAbortedException("Request aborted", interrupted);
          } catch(final ExecutionException ex) {
              Throwable cause = ex.getCause();
              if (cause == null) {
                  cause = ex;
              }
              throw new RequestAbortedException("Request execution failed", cause);
          }
  
          context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn);
  
          if (config.isStaleConnectionCheckEnabled()) {
              // validate connection
              if (managedConn.isOpen()) {
                  this.log.debug("Stale connection check");
                  if (managedConn.isStale()) {
                      this.log.debug("Stale connection detected");
                      managedConn.close();
                  }
              }
          }
  
          final ConnectionHolder connHolder = new ConnectionHolder(this.log, this.connManager, managedConn);
          try {
              if (execAware != null) {
                  execAware.setCancellable(connHolder);
              }
  
              HttpResponse response;
              for (int execCount = 1;; execCount++) {
  
                  if (execCount > 1 && !Proxies.isRepeatable(request)) {
                      throw new NonRepeatableRequestException("Cannot retry request " +
                              "with a non-repeatable request entity.");
                  }
  
                  if (execAware != null && execAware.isAborted()) {
                      throw new RequestAbortedException("Request aborted");
                  }
  
                  if (!managedConn.isOpen()) {
                      this.log.debug("Opening connection " + route);
                      try {
                          establishRoute(proxyAuthState, managedConn, route, request, context);
                      } catch (final TunnelRefusedException ex) {
                          if (this.log.isDebugEnabled()) {
                              this.log.debug(ex.getMessage());
                          }
                          response = ex.getResponse();
                          break;
                      }
                  }
                  final int timeout = config.getSocketTimeout();
                  if (timeout >= 0) {
                      managedConn.setSocketTimeout(timeout);
                  }
  
                  if (execAware != null && execAware.isAborted()) {
                      throw new RequestAbortedException("Request aborted");
                  }
  
                  if (this.log.isDebugEnabled()) {
                      this.log.debug("Executing request " + request.getRequestLine());
                  }
  
                  if (!request.containsHeader(AUTH.WWW_AUTH_RESP)) {
                      if (this.log.isDebugEnabled()) {
                          this.log.debug("Target auth state: " + targetAuthState.getState());
                      }
                      this.authenticator.generateAuthResponse(request, targetAuthState, context);
                  }
                  if (!request.containsHeader(AUTH.PROXY_AUTH_RESP) && !route.isTunnelled()) {
                      if (this.log.isDebugEnabled()) {
                          this.log.debug("Proxy auth state: " + proxyAuthState.getState());
                      }
                      this.authenticator.generateAuthResponse(request, proxyAuthState, context);
                  }
  
                  response = requestExecutor.execute(request, managedConn, context);
  
                  // The connection is in or can be brought to a re-usable state.
                  if (reuseStrategy.keepAlive(response, context)) {
                      // Set the idle duration of this connection
                      final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
                      if (this.log.isDebugEnabled()) {
                          final String s;
                          if (duration > 0) {
                              s = "for " + duration + " " + TimeUnit.MILLISECONDS;
                          } else {
                              s = "indefinitely";
                          }
                          this.log.debug("Connection can be kept alive " + s);
                      }
                      connHolder.setValidFor(duration, TimeUnit.MILLISECONDS);
                      connHolder.markReusable();
                  } else {
                      connHolder.markNonReusable();
                  }
  
                  if (needAuthentication(
                          targetAuthState, proxyAuthState, route, response, context)) {
                      // Make sure the response body is fully consumed, if present
                      final HttpEntity entity = response.getEntity();
                      if (connHolder.isReusable()) {
                          EntityUtils.consume(entity);
                      } else {
                          managedConn.close();
                          if (proxyAuthState.getState() == AuthProtocolState.SUCCESS
                                  && proxyAuthState.getAuthScheme() != null
                                  && proxyAuthState.getAuthScheme().isConnectionBased()) {
                              this.log.debug("Resetting proxy auth state");
                              proxyAuthState.reset();
                          }
                          if (targetAuthState.getState() == AuthProtocolState.SUCCESS
                                  && targetAuthState.getAuthScheme() != null
                                  && targetAuthState.getAuthScheme().isConnectionBased()) {
                              this.log.debug("Resetting target auth state");
                              targetAuthState.reset();
                          }
                      }
                      // discard previous auth headers
                      final HttpRequest original = request.getOriginal();
                      if (!original.containsHeader(AUTH.WWW_AUTH_RESP)) {
                          request.removeHeaders(AUTH.WWW_AUTH_RESP);
                      }
                      if (!original.containsHeader(AUTH.PROXY_AUTH_RESP)) {
                          request.removeHeaders(AUTH.PROXY_AUTH_RESP);
                      }
                  } else {
                      break;
                  }
              }
  
              if (userToken == null) {
                  userToken = userTokenHandler.getUserToken(context);
                  context.setAttribute(HttpClientContext.USER_TOKEN, userToken);
              }
              if (userToken != null) {
                  connHolder.setState(userToken);
              }
  
              // check for entity, release connection if possible
              final HttpEntity entity = response.getEntity();
              if (entity == null || !entity.isStreaming()) {
                  // connection not needed and (assumed to be) in re-usable state
                  connHolder.releaseConnection();
                  return Proxies.enhanceResponse(response, null);
              } else {
                  return Proxies.enhanceResponse(response, connHolder);
              }
          } catch (final ConnectionShutdownException ex) {
              final InterruptedIOException ioex = new InterruptedIOException(
                      "Connection has been shut down");
              ioex.initCause(ex);
              throw ioex;
          } catch (final HttpException ex) {
              connHolder.abortConnection();
              throw ex;
          } catch (final IOException ex) {
              connHolder.abortConnection();
              throw ex;
          } catch (final RuntimeException ex) {
              connHolder.abortConnection();
              throw ex;
          }
      }
  
      /**
       * Establishes the target route.
       */
      void establishRoute(
              final AuthState proxyAuthState,
              final HttpClientConnection managedConn,
              final HttpRoute route,
              final HttpRequest request,
              final HttpClientContext context) throws HttpException, IOException {
          final RequestConfig config = context.getRequestConfig();
          final int timeout = config.getConnectTimeout();
          final RouteTracker tracker = new RouteTracker(route);
          int step;
          do {
              final HttpRoute fact = tracker.toRoute();
              step = this.routeDirector.nextStep(route, fact);
  
              switch (step) {
  
              case HttpRouteDirector.CONNECT_TARGET:
                  this.connManager.connect(
                          managedConn,
                          route,
                          timeout > 0 ? timeout : 0,
                          context);
                  tracker.connectTarget(route.isSecure());
                  break;
              case HttpRouteDirector.CONNECT_PROXY:
                  this.connManager.connect(
                          managedConn,
                          route,
                          timeout > 0 ? timeout : 0,
                          context);
                  final HttpHost proxy  = route.getProxyHost();
                  tracker.connectProxy(proxy, false);
                  break;
              case HttpRouteDirector.TUNNEL_TARGET: {
                  final boolean secure = createTunnelToTarget(
                          proxyAuthState, managedConn, route, request, context);
                  this.log.debug("Tunnel to target created.");
                  tracker.tunnelTarget(secure);
              }   break;
  
              case HttpRouteDirector.TUNNEL_PROXY: {
                  // The most simple example for this case is a proxy chain
                  // of two proxies, where P1 must be tunnelled to P2.
                  // route: Source -> P1 -> P2 -> Target (3 hops)
                  // fact:  Source -> P1 -> Target       (2 hops)
                  final int hop = fact.getHopCount()-1; // the hop to establish
                  final boolean secure = createTunnelToProxy(route, hop, context);
                  this.log.debug("Tunnel to proxy created.");
                  tracker.tunnelProxy(route.getHopTarget(hop), secure);
              }   break;
  
              case HttpRouteDirector.LAYER_PROTOCOL:
                  this.connManager.upgrade(managedConn, route, context);
                  tracker.layerProtocol(route.isSecure());
                  break;
  
              case HttpRouteDirector.UNREACHABLE:
                  throw new HttpException("Unable to establish route: " +
                          "planned = " + route + "; current = " + fact);
              case HttpRouteDirector.COMPLETE:
                  this.connManager.routeComplete(managedConn, route, context);
                  break;
              default:
                  throw new IllegalStateException("Unknown step indicator "
                          + step + " from RouteDirector.");
              }
  
          } while (step > HttpRouteDirector.COMPLETE);
      }
  
      /**
       * Creates a tunnel to the target server.
       * The connection must be established to the (last) proxy.
       * A CONNECT request for tunnelling through the proxy will
       * be created and sent, the response received and checked.
       * This method does <i>not</i> update the connection with
       * information about the tunnel, that is left to the caller.
       */
      private boolean createTunnelToTarget(
              final AuthState proxyAuthState,
              final HttpClientConnection managedConn,
              final HttpRoute route,
              final HttpRequest request,
              final HttpClientContext context) throws HttpException, IOException {
  
          final RequestConfig config = context.getRequestConfig();
          final int timeout = config.getConnectTimeout();
  
          final HttpHost target = route.getTargetHost();
          final HttpHost proxy = route.getProxyHost();
          HttpResponse response;
  
          final String authority = target.toHostString();
          final HttpRequest connect = new BasicHttpRequest("CONNECT", authority, request.getProtocolVersion());
  
          this.requestExecutor.preProcess(connect, this.proxyHttpProcessor, context);
  
          for (;;) {
              if (!managedConn.isOpen()) {
                  this.connManager.connect(
                          managedConn,
                          route,
                          timeout > 0 ? timeout : 0,
                          context);
              }
  
              connect.removeHeaders(AUTH.PROXY_AUTH_RESP);
              this.authenticator.generateAuthResponse(connect, proxyAuthState, context);
  
              response = this.requestExecutor.execute(connect, managedConn, context);
  
              final int status = response.getStatusLine().getStatusCode();
              if (status < 200) {
                  throw new HttpException("Unexpected response to CONNECT request: " +
                          response.getStatusLine());
              }
  
              if (config.isAuthenticationEnabled()) {
                  if (this.authenticator.isAuthenticationRequested(proxy, response,
                          this.proxyAuthStrategy, proxyAuthState, context)) {
                      if (this.authenticator.handleAuthChallenge(proxy, response,
                              this.proxyAuthStrategy, proxyAuthState, context)) {
                          // Retry request
  