@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;
  }
 @Override
 public final void disconnect() {
   // Calling disconnect() before a connection exists should have no effect.
   if (httpEngine != null) {
     // We close the response body here instead of in
     // HttpEngine.release because that is called when input
     // has been completely read from the underlying socket.
     // However the response body can be a GZIPInputStream that
     // still has unread data.
     if (httpEngine.hasResponse()) {
       Util.closeQuietly(httpEngine.getResponseBody());
     }
     httpEngine.release(true);
   }
 }