/**
   * 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;
  }
 /**
  * Prepares the HTTP headers and sends them to the server.
  *
  * <p>For streaming requests with a body, headers must be prepared <strong>before</strong> the
  * output stream has been written to. Otherwise the body would need to be buffered!
  *
  * <p>For non-streaming requests with a body, headers must be prepared <strong>after</strong> the
  * output stream has been written to and closed. This ensures that the {@code Content-Length}
  * header field receives the proper value.
  */
 public void writeRequestHeaders(Request request) throws IOException {
   httpEngine.writingRequestHeaders();
   String requestLine =
       RequestLine.get(
           request,
           httpEngine.getConnection().getRoute().getProxy().type(),
           httpEngine.getConnection().getProtocol());
   httpConnection.writeRequest(request.headers(), requestLine);
 }
 /**
  * Returns an input stream from the server in the case of error such as the requested file (txt,
  * htm, html) is not found on the remote server.
  */
 @Override
 public final InputStream getErrorStream() {
   try {
     HttpEngine response = getResponse();
     if (response.hasResponseBody() && response.getResponseCode() >= HTTP_BAD_REQUEST) {
       return response.getResponseBody();
     }
     return null;
   } catch (IOException e) {
     return null;
   }
 }
  @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;
  }
 /**
  * Sends a request and optionally reads a response. Returns true if the request was successfully
  * executed, and false if the request can be retried. Throws an exception if the request failed
  * permanently.
  */
 private boolean execute(boolean readResponse) throws IOException {
   try {
     httpEngine.sendRequest();
     if (readResponse) {
       httpEngine.readResponse();
     }
     return true;
   } catch (IOException e) {
     if (handleFailure(e)) {
       return false;
     } else {
       throw e;
     }
   }
 }
 @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);
   }
 }
Exemple #8
0
 public synchronized void cancel(Object tag) {
   Iterator<AsyncCall> i = this.readyCalls.iterator();
   while (i.hasNext()) {
     if (Util.equal(tag, ((AsyncCall) i.next()).tag())) {
       i.remove();
     }
   }
   for (AsyncCall call : this.runningCalls) {
     if (Util.equal(tag, call.tag())) {
       call.get().canceled = true;
       HttpEngine engine = call.get().engine;
       if (engine != null) {
         engine.disconnect();
       }
     }
   }
 }
  @Override
  public boolean canReuseConnection() {
    // If the request specified that the connection shouldn't be reused, don't reuse it.
    if ("close".equalsIgnoreCase(httpEngine.getRequest().header("Connection"))) {
      return false;
    }

    // If the response specified that the connection shouldn't be reused, don't reuse it.
    if ("close".equalsIgnoreCase(httpEngine.getResponse().header("Connection"))) {
      return false;
    }

    if (httpConnection.isClosed()) {
      return false;
    }

    return true;
  }
  @Override
  public final InputStream getInputStream() throws IOException {
    if (!doInput) {
      throw new ProtocolException("This protocol does not support input");
    }

    HttpEngine response = getResponse();

    // if the requested file does not exist, throw an exception formerly the
    // Error page from the server was returned if the requested file was
    // text/html this has changed to return FileNotFoundException for all
    // file types
    if (getResponseCode() >= HTTP_BAD_REQUEST) {
      throw new FileNotFoundException(url.toString());
    }

    InputStream result = response.getResponseBody();
    if (result == null) {
      throw new ProtocolException("No response body exists; responseCode=" + getResponseCode());
    }
    return result;
  }
  /**
   * 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);
    }
  }
  private Source getTransferStream(Response response) throws IOException {
    if (!HttpEngine.hasBody(response)) {
      return httpConnection.newFixedLengthSource(0);
    }

    if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
      return httpConnection.newChunkedSource(httpEngine);
    }

    long contentLength = OkHeaders.contentLength(response);
    if (contentLength != -1) {
      return httpConnection.newFixedLengthSource(contentLength);
    }

    // Wrap the input stream from the connection (rather than just returning
    // "socketIn" directly here), so that we can control its use after the
    // reference escapes.
    return httpConnection.newUnknownLengthSource();
  }
  /**
   * Returns the retry action to take for the current response headers. The headers, proxy and
   * target URL or this connection may be adjusted to prepare for a follow up request.
   */
  private Retry processResponseHeaders() throws IOException {
    Proxy selectedProxy =
        httpEngine.connection != null
            ? httpEngine.connection.getRoute().getProxy()
            : requestedProxy;
    final int responseCode = getResponseCode();
    switch (responseCode) {
      case HTTP_PROXY_AUTH:
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
        }
        // fall-through
      case HTTP_UNAUTHORIZED:
        boolean credentialsFound =
            HttpAuthenticator.processAuthHeader(
                getResponseCode(),
                httpEngine.getResponseHeaders().getHeaders(),
                rawRequestHeaders,
                selectedProxy,
                url);
        return credentialsFound ? Retry.SAME_CONNECTION : Retry.NONE;

      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
      case HTTP_TEMP_REDIRECT:
        if (!getInstanceFollowRedirects()) {
          return Retry.NONE;
        }
        if (++redirectionCount > MAX_REDIRECTS) {
          throw new ProtocolException("Too many redirects: " + redirectionCount);
        }
        if (responseCode == HTTP_TEMP_REDIRECT && !method.equals("GET") && !method.equals("HEAD")) {
          // "If the 307 status code is received in response to a request other than GET or HEAD,
          // the user agent MUST NOT automatically redirect the request"
          return Retry.NONE;
        }
        String location = getHeaderField("Location");
        if (location == null) {
          return Retry.NONE;
        }
        URL previousUrl = url;
        url = new URL(previousUrl, location);
        if (!url.getProtocol().equals("https") && !url.getProtocol().equals("http")) {
          return Retry.NONE; // Don't follow redirects to unsupported protocols.
        }
        boolean sameProtocol = previousUrl.getProtocol().equals(url.getProtocol());
        if (!sameProtocol && !followProtocolRedirects) {
          return Retry.NONE; // This client doesn't follow redirects across protocols.
        }
        boolean sameHost = previousUrl.getHost().equals(url.getHost());
        boolean samePort = getEffectivePort(previousUrl) == getEffectivePort(url);
        if (sameHost && samePort && sameProtocol) {
          return Retry.SAME_CONNECTION;
        } else {
          return Retry.DIFFERENT_CONNECTION;
        }

      default:
        return Retry.NONE;
    }
  }