/** * 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); } }
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; } }