shadows/httpclient/src/main/java/org/robolectric/shadows/httpclient/DefaultRequestDirector.java
@Override public HttpResponse execute(HttpHost target, HttpRequest request,
                HttpContext context)
    throws HttpException, IOException {

    HttpRequest orig = request;
    RequestWrapper origWrapper = wrapRequest(orig);
    origWrapper.setParams(params);
    HttpRoute origRoute = determineRoute(target, origWrapper, context);

    virtualHost = (HttpHost) orig.getParams().getParameter(
        ClientPNames.VIRTUAL_HOST);

    RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute);

    long timeout = ConnManagerParams.getTimeout(params);

    int execCount = 0;

    boolean reuse = false;
    boolean done = false;
    try {
      HttpResponse response = null;
      while (!done) {
        // In this loop, the RoutedRequest may be replaced by a
        // followup request and route. The request and route passed
        // in the method arguments will be replaced. The original
        // request is still available in 'orig'.

        RequestWrapper wrapper = roureq.getRequest();
        HttpRoute route = roureq.getRoute();
        response = null;

        // See if we have a user token bound to the execution context
        Object userToken = context.getAttribute(ClientContext.USER_TOKEN);

        // Allocate connection if needed
        if (managedConn == null) {
          ClientConnectionRequest connRequest = connManager.requestConnection(
              route, userToken);
          if (orig instanceof AbortableHttpRequest) {
            ((AbortableHttpRequest) orig).setConnectionRequest(connRequest);
          }

          try {
            managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
          } catch(InterruptedException interrupted) {
            InterruptedIOException iox = new InterruptedIOException();
            iox.initCause(interrupted);
            throw iox;
          }

          if (HttpConnectionParams.isStaleCheckingEnabled(params)) {
            // validate connection
            if (managedConn.isOpen()) {
              this.log.debug("Stale connection check");
              if (managedConn.isStale()) {
                this.log.debug("Stale connection detected");
                managedConn.close();
              }
            }
          }
        }

        if (orig instanceof AbortableHttpRequest) {
          ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn);
        }

        // Reopen connection if needed
        if (!managedConn.isOpen()) {
          managedConn.open(route, context, params);
        } else {
          managedConn.setSocketTimeout(HttpConnectionParams.getSoTimeout(params));
        }

        try {
          establishRoute(route, context);
        } catch (TunnelRefusedException ex) {
          if (this.log.isDebugEnabled()) {
            this.log.debug(ex.getMessage());
          }
          response = ex.getResponse();
          break;
        }

        // Reset headers on the request wrapper
        wrapper.resetHeaders();

        // Re-write request URI if needed
        rewriteRequestURI(wrapper, route);

        // Use virtual host if set
        target = virtualHost;

        if (target == null) {
          target = route.getTargetHost();
        }

        HttpHost proxy = route.getProxyHost();

        // Populate the execution context
        context.setAttribute(ExecutionContext.HTTP_TARGET_HOST,
            target);
        context.setAttribute(ExecutionContext.HTTP_PROXY_HOST,
            proxy);
        context.setAttribute(ExecutionContext.HTTP_CONNECTION,
            managedConn);
        context.setAttribute(ClientContext.TARGET_AUTH_STATE,
            targetAuthState);
        context.setAttribute(ClientContext.PROXY_AUTH_STATE,
            proxyAuthState);

        // Run request protocol interceptors
        requestExec.preProcess(wrapper, httpProcessor, context);

        boolean retrying = true;
        Exception retryReason = null;
        while (retrying) {
          // Increment total exec count (with redirects)
          execCount++;
          // Increment exec count for this particular request
          wrapper.incrementExecCount();
          if (!wrapper.isRepeatable()) {
            this.log.debug("Cannot retry non-repeatable request");
            if (retryReason != null) {
              throw new NonRepeatableRequestException("Cannot retry request " +
                "with a non-repeatable request entity.  The cause lists the " +
                "reason the original request failed: " + retryReason);
            } else {
              throw new NonRepeatableRequestException("Cannot retry request " +
                  "with a non-repeatable request entity.");
            }
          }

          try {
            if (this.log.isDebugEnabled()) {
              this.log.debug("Attempt " + execCount + " to execute request");
            }
            response = requestExec.execute(wrapper, managedConn, context);
            retrying = false;

          } catch (IOException ex) {
            this.log.debug("Closing the connection.");
            managedConn.close();
            if (retryHandler.retryRequest(ex, wrapper.getExecCount(), context)) {
              if (this.log.isInfoEnabled()) {
                this.log.info("I/O exception ("+ ex.getClass().getName() +
                    ") caught when processing request: "
                    + ex.getMessage());
              }
              if (this.log.isDebugEnabled()) {
                this.log.debug(ex.getMessage(), ex);
              }
              this.log.info("Retrying request");
              retryReason = ex;
            } else {
              throw ex;
            }

            // If we have a direct route to the target host
            // just re-open connection and re-try the request
            if (!route.isTunnelled()) {
              this.log.debug("Reopening the direct connection.");
              managedConn.open(route, context, params);
            } else {
              // otherwise give up
              this.log.debug("Proxied connection. Need to start over.");
              retrying = false;
            }

          }

        }

        if (response == null) {
          // Need to start over
          continue;
        }

        // Run response protocol interceptors
        response.setParams(params);
        requestExec.postProcess(response, httpProcessor, context);


        // The connection is in or can be brought to a re-usable state.
        reuse = reuseStrategy.keepAlive(response, context);
        if (reuse) {
          // Set the idle duration of this connection
          long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
          managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS);

          if (this.log.isDebugEnabled()) {
            if (duration >= 0) {
              this.log.debug("Connection can be kept alive for " + duration + " ms");
            } else {
              this.log.debug("Connection can be kept alive indefinitely");
            }
          }
        }

        RoutedRequest followup = handleResponse(roureq, response, context);
        if (followup == null) {
          done = true;
        } else {
          if (reuse) {
            // Make sure the response body is fully consumed, if present
            HttpEntity entity = response.getEntity();
            if (entity != null) {
              entity.consumeContent();
            }
            // entity consumed above is not an auto-release entity,
            // need to mark the connection re-usable explicitly
            managedConn.markReusable();
          } else {
            managedConn.close();
          }
          // check if we can use the same connection for the followup
          if (!followup.getRoute().equals(roureq.getRoute())) {
            releaseConnection();
          }
          roureq = followup;
        }

        if (managedConn != null && userToken == null) {
          userToken = userTokenHandler.getUserToken(context);
          context.setAttribute(ClientContext.USER_TOKEN, userToken);
          if (userToken != null) {
            managedConn.setState(userToken);
          }
        }

      } // while not done


      // check for entity, release connection if possible
      if ((response == null) || (response.getEntity() == null) ||
        !response.getEntity().isStreaming()) {
        // connection not needed and (assumed to be) in re-usable state
        if (reuse)
          managedConn.markReusable();
        releaseConnection();
      } else {
        // install an auto-release entity
        HttpEntity entity = response.getEntity();
        entity = new BasicManagedEntity(entity, managedConn, reuse);
        response.setEntity(entity);
      }

      return response;

    } catch (HttpException | RuntimeException | IOException ex) {
      abortConnection();
      throw ex;
    }
  }
@Override public HttpResponse execute(HttpHost target, HttpRequest request,
                HttpContext context)
    throws HttpException, IOException {

    HttpRequest orig = request;
    RequestWrapper origWrapper = wrapRequest(orig);
    origWrapper.setParams(params);
    HttpRoute origRoute = determineRoute(target, origWrapper, context);

    virtualHost = (HttpHost) orig.getParams().getParameter(
        ClientPNames.VIRTUAL_HOST);

    RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute);

    long timeout = ConnManagerParams.getTimeout(params);

    int execCount = 0;

    boolean reuse = false;
    boolean done = false;
    try {
      HttpResponse response = null;
      while (!done) {
        // In this loop, the RoutedRequest may be replaced by a
        // followup request and route. The request and route passed
        // in the method arguments will be replaced. The original
        // request is still available in 'orig'.

        RequestWrapper wrapper = roureq.getRequest();
        HttpRoute route = roureq.getRoute();
        response = null;

        // See if we have a user token bound to the execution context
        Object userToken = context.getAttribute(ClientContext.USER_TOKEN);

        // Allocate connection if needed
        if (managedConn == null) {
          ClientConnectionRequest connRequest = connManager.requestConnection(
              route, userToken);
          if (orig instanceof AbortableHttpRequest) {
            ((AbortableHttpRequest) orig).setConnectionRequest(connRequest);
          }

          try {
            managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
          } catch(InterruptedException interrupted) {
            InterruptedIOException iox = new InterruptedIOException();
            iox.initCause(interrupted);
            throw iox;
          }

          if (HttpConnectionParams.isStaleCheckingEnabled(params)) {
            // validate connection
            if (managedConn.isOpen()) {
              this.log.debug("Stale connection check");
              if (managedConn.isStale()) {
                this.log.debug("Stale connection detected");
                managedConn.close();
              }
            }
          }
        }

        if (orig instanceof AbortableHttpRequest) {
          ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn);
        }

        // Reopen connection if needed
        if (!managedConn.isOpen()) {
          managedConn.open(route, context, params);
        } else {
          managedConn.setSocketTimeout(HttpConnectionParams.getSoTimeout(params));
        }

        try {
          establishRoute(route, context);
        } catch (TunnelRefusedException ex) {
          if (this.log.isDebugEnabled()) {
            this.log.debug(ex.getMessage());
          }
          response = ex.getResponse();
          break;
        }

        // Reset headers on the request wrapper
        wrapper.resetHeaders();

        // Re-write request URI if needed
        rewriteRequestURI(wrapper, route);

        // Use virtual host if set
        target = virtualHost;

        if (target == null) {
          target = route.getTargetHost();
        }

        HttpHost proxy = route.getProxyHost();

        // Populate the execution context
        context.setAttribute(ExecutionContext.HTTP_TARGET_HOST,
            target);
        context.setAttribute(ExecutionContext.HTTP_PROXY_HOST,
            proxy);
        context.setAttribute(ExecutionContext.HTTP_CONNECTION,
            managedConn);
        context.setAttribute(ClientContext.TARGET_AUTH_STATE,
            targetAuthState);
        context.setAttribute(ClientContext.PROXY_AUTH_STATE,
            proxyAuthState);

        // Run request protocol interceptors
        requestExec.preProcess(wrapper, httpProcessor, context);

        boolean retrying = true;
        Exception retryReason = null;
        while (retrying) {
          // Increment total exec count (with redirects)
          execCount++;
          // Increment exec count for this particular request
          wrapper.incrementExecCount();
          if (!wrapper.isRepeatable()) {
            this.log.debug("Cannot retry non-repeatable request");
            if (retryReason != null) {
              throw new NonRepeatableRequestException("Cannot retry request " +
                "with a non-repeatable request entity.  The cause lists the " +
                "reason the original request failed: " + retryReason);
            } else {
              throw new NonRepeatableRequestException("Cannot retry request " +
                  "with a non-repeatable request entity.");
            }
          }

          try {
            if (this.log.isDebugEnabled()) {
              this.log.debug("Attempt " + execCount + " to execute request");
            }
            response = requestExec.execute(wrapper, managedConn, context);
            retrying = false;

          } catch (IOException ex) {
            this.log.debug("Closing the connection.");
            managedConn.close();
            if (retryHandler.retryRequest(ex, wrapper.getExecCount(), context)) {
              if (this.log.isInfoEnabled()) {
                this.log.info("I/O exception ("+ ex.getClass().getName() +
                    ") caught when processing request: "
                    + ex.getMessage());
              }
              if (this.log.isDebugEnabled()) {
                this.log.debug(ex.getMessage(), ex);
              }
              this.log.info("Retrying request");
              retryReason = ex;
            } else {
              throw ex;
            }

            // If we have a direct route to the target host
            // just re-open connection and re-try the request
            if (!route.isTunnelled()) {
              this.log.debug("Reopening the direct connection.");
              managedConn.open(route, context, params);
            } else {
              // otherwise give up
              this.log.debug("Proxied connection. Need to start over.");
              retrying = false;
            }

          }

        }

        if (response == null) {
          // Need to start over
          continue;
        }

        // Run response protocol interceptors
        response.setParams(params);
        requestExec.postProcess(response, httpProcessor, context);


        // The connection is in or can be brought to a re-usable state.
        reuse = reuseStrategy.keepAlive(response, context);
        if (reuse) {
          // Set the idle duration of this connection
          long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
          managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS);

          if (this.log.isDebugEnabled()) {
            if (duration >= 0) {
              this.log.debug("Connection can be kept alive for " + duration + " ms");
            } else {
              this.log.debug("Connection can be kept alive indefinitely");
            }
          }
        }

        RoutedRequest followup = handleResponse(roureq, response, context);
        if (followup == null) {
          done = true;
        } else {
          if (reuse) {
            // Make sure the response body is fully consumed, if present
            HttpEntity entity = response.getEntity();
            if (entity != null) {
              entity.consumeContent();
            }
            // entity consumed above is not an auto-release entity,
            // need to mark the connection re-usable explicitly
            managedConn.markReusable();
          } else {
            managedConn.close();
          }
          // check if we can use the same connection for the followup
          if (!followup.getRoute().equals(roureq.getRoute())) {
            releaseConnection();
          }
          roureq = followup;
        }

        if (managedConn != null && userToken == null) {
          userToken = userTokenHandler.getUserToken(context);
          context.setAttribute(ClientContext.USER_TOKEN, userToken);
          if (userToken != null) {
            managedConn.setState(userToken);
          }
        }

      } // while not done


      // check for entity, release connection if possible
      if ((response == null) || (response.getEntity() == null) ||
        !response.getEntity().isStreaming()) {
        // connection not needed and (assumed to be) in re-usable state
        if (reuse)
          managedConn.markReusable();
        releaseConnection();
      } else {
        // install an auto-release entity
        HttpEntity entity = response.getEntity();
        entity = new BasicManagedEntity(entity, managedConn, reuse);
        response.setEntity(entity);
      }

      return response;

    } catch (HttpException | RuntimeException | IOException ex) {
      abortConnection();
      throw ex;
    }
  }
non-javadoc, see interface ClientRequestDirector