/**
   * Report and attempt to recover from {@code e}. Returns true if the HTTP engine was replaced and
   * the request should be retried. Otherwise the failure is permanent.
   */
  private boolean handleFailure(IOException e) throws IOException {
    RouteSelector routeSelector = httpEngine.routeSelector;
    if (routeSelector != null && httpEngine.connection != null) {
      routeSelector.connectFailed(httpEngine.connection, e);
    }

    OutputStream requestBody = httpEngine.getRequestBody();
    boolean canRetryRequestBody =
        requestBody == null
            || requestBody instanceof RetryableOutputStream
            || (faultRecoveringRequestBody != null && faultRecoveringRequestBody.isRecoverable());
    if (routeSelector == null && httpEngine.connection == null // No connection.
        || routeSelector != null && !routeSelector.hasNext() // No more routes to attempt.
        || !isRecoverable(e)
        || !canRetryRequestBody) {
      httpEngineFailure = e;
      return false;
    }

    httpEngine.release(true);
    RetryableOutputStream retryableOutputStream =
        requestBody instanceof RetryableOutputStream ? (RetryableOutputStream) requestBody : null;
    httpEngine = newHttpEngine(method, rawRequestHeaders, null, retryableOutputStream);
    httpEngine.routeSelector = routeSelector; // Keep the same routeSelector.
    if (faultRecoveringRequestBody != null && faultRecoveringRequestBody.isRecoverable()) {
      httpEngine.sendRequest();
      faultRecoveringRequestBody.replaceStream(httpEngine.getRequestBody());
    }
    return true;
  }
  @Override
  public final OutputStream getOutputStream() throws IOException {
    connect();

    OutputStream out = httpEngine.getRequestBody();
    if (out == null) {
      throw new ProtocolException("method does not support a request body: " + method);
    } else if (httpEngine.hasResponse()) {
      throw new ProtocolException("cannot write request body after response has been read");
    }

    if (faultRecoveringRequestBody == null) {
      faultRecoveringRequestBody =
          new FaultRecoveringOutputStream(MAX_REPLAY_BUFFER_LENGTH, out) {
            @Override
            protected OutputStream replacementStream(IOException e) throws IOException {
              if (httpEngine.getRequestBody() instanceof AbstractOutputStream
                  && ((AbstractOutputStream) httpEngine.getRequestBody()).isClosed()) {
                return null; // Don't recover once the underlying stream has been closed.
              }
              if (handleFailure(e)) {
                return httpEngine.getRequestBody();
              }
              return null; // This is a permanent failure.
            }
          };
    }

    return faultRecoveringRequestBody;
  }
  /**
   * Aggressively tries to get the final HTTP response, potentially making many HTTP requests in the
   * process in order to cope with redirects and authentication.
   */
  private HttpEngine getResponse() throws IOException {
    initHttpEngine();

    if (httpEngine.hasResponse()) {
      return httpEngine;
    }

    while (true) {
      if (!execute(true)) {
        continue;
      }

      Retry retry = processResponseHeaders();
      if (retry == Retry.NONE) {
        httpEngine.automaticallyReleaseConnectionToPool();
        return httpEngine;
      }

      // The first request was insufficient. Prepare for another...
      String retryMethod = method;
      OutputStream requestBody = httpEngine.getRequestBody();

      // Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM
      // redirect should keep the same method, Chrome, Firefox and the
      // RI all issue GETs when following any redirect.
      int responseCode = getResponseCode();
      if (responseCode == HTTP_MULT_CHOICE
          || responseCode == HTTP_MOVED_PERM
          || responseCode == HTTP_MOVED_TEMP
          || responseCode == HTTP_SEE_OTHER) {
        retryMethod = "GET";
        requestBody = null;
      }

      if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) {
        throw new HttpRetryException(
            "Cannot retry streamed HTTP body", httpEngine.getResponseCode());
      }

      if (retry == Retry.DIFFERENT_CONNECTION) {
        httpEngine.automaticallyReleaseConnectionToPool();
      }

      httpEngine.release(false);

      httpEngine =
          newHttpEngine(
              retryMethod,
              rawRequestHeaders,
              httpEngine.getConnection(),
              (RetryableOutputStream) requestBody);
    }
  }
  @Override
  public final OutputStream getOutputStream() throws IOException {
    connect();

    OutputStream out = httpEngine.getRequestBody();
    if (out == null) {
      throw new ProtocolException("method does not support a request body: " + method);
    } else if (httpEngine.hasResponse()) {
      throw new ProtocolException("cannot write request body after response has been read");
    }

    return out;
  }