HttpEngine.java revision faf49723fb689c626f69876e718c58018eff8ee7
1c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath/*
2c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *  Licensed to the Apache Software Foundation (ASF) under one or more
3c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *  contributor license agreements.  See the NOTICE file distributed with
4c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *  this work for additional information regarding copyright ownership.
5c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *  The ASF licenses this file to You under the Apache License, Version 2.0
6c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *  (the "License"); you may not use this file except in compliance with
7c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *  the License.  You may obtain a copy of the License at
8c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *
9c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *     http://www.apache.org/licenses/LICENSE-2.0
10c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *
11c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *  Unless required by applicable law or agreed to in writing, software
12c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *  distributed under the License is distributed on an "AS IS" BASIS,
13c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *  See the License for the specific language governing permissions and
15c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *  limitations under the License.
16c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath */
17c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
182231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonpackage com.squareup.okhttp.internal.http;
19c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
202231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport com.squareup.okhttp.Connection;
212231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport com.squareup.okhttp.ConnectionPool;
22faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamathimport com.squareup.okhttp.OkAuthenticator;
237407d6984ce69693097befc9b72609a8156463bbNarayan Kamathimport com.squareup.okhttp.OkHttpClient;
24faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamathimport com.squareup.okhttp.Route;
25faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamathimport com.squareup.okhttp.internal.AbstractOutputStream;
26faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamathimport com.squareup.okhttp.internal.FaultRecoveringOutputStream;
272231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport com.squareup.okhttp.internal.Util;
28c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.FileNotFoundException;
29c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.IOException;
30c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.InputStream;
31c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.OutputStream;
322231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport java.net.CookieHandler;
33c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.HttpRetryException;
342231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport java.net.HttpURLConnection;
35c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.InetSocketAddress;
36c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.ProtocolException;
37c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.Proxy;
382231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport java.net.ProxySelector;
39c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.SocketPermission;
40c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.URL;
41c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.security.Permission;
422231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport java.security.cert.CertificateException;
43faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamathimport java.util.ArrayList;
44c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.List;
45c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.Map;
46faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamathimport java.util.Set;
477407d6984ce69693097befc9b72609a8156463bbNarayan Kamathimport javax.net.ssl.HostnameVerifier;
482231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport javax.net.ssl.SSLHandshakeException;
497407d6984ce69693097befc9b72609a8156463bbNarayan Kamathimport javax.net.ssl.SSLSocketFactory;
50c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
5154cf3446000fdcf88a9e62724f7deb0282e98da1jwilsonimport static com.squareup.okhttp.internal.Util.getEffectivePort;
5254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
53c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath/**
54c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * This implementation uses HttpEngine to send requests and receive responses.
55c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * This class may use multiple HttpEngines to follow redirects, authentication
56c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * retries, etc. to retrieve the final response body.
57c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *
58c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * <h3>What does 'connected' mean?</h3>
59c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * This class inherits a {@code connected} field from the superclass. That field
60c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * is <strong>not</strong> used to indicate not whether this URLConnection is
61c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * currently connected. Instead, it indicates whether a connection has ever been
62c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * attempted. Once a connection has been attempted, certain properties (request
63c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * header fields, request method, etc.) are immutable. Test the {@code
64c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * connection} field on this class for null/non-null to determine of an instance
65c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * is currently connected to a server.
66c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath */
672231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonpublic class HttpURLConnectionImpl extends HttpURLConnection {
68faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
69faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  /** Numeric status code, 307: Temporary Redirect. */
70faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  static final int HTTP_TEMP_REDIRECT = 307;
71faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
7254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
7354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * How many redirects should we follow? Chrome follows 21; Firefox, curl,
7454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
7554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
7654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private static final int MAX_REDIRECTS = 20;
7754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
78faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  /**
79faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   * The minimum number of request body bytes to transmit before we're willing
80faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   * to let a routine {@link IOException} bubble up to the user. This is used to
81faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   * size a buffer for data that will be replayed upon error.
82faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   */
83faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private static final int MAX_REPLAY_BUFFER_LENGTH = 8192;
84faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
857407d6984ce69693097befc9b72609a8156463bbNarayan Kamath  private final boolean followProtocolRedirects;
867407d6984ce69693097befc9b72609a8156463bbNarayan Kamath
877407d6984ce69693097befc9b72609a8156463bbNarayan Kamath  /** The proxy requested by the client, or null for a proxy to be selected automatically. */
887407d6984ce69693097befc9b72609a8156463bbNarayan Kamath  final Proxy requestedProxy;
8954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
9054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  final ProxySelector proxySelector;
9154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  final CookieHandler cookieHandler;
92faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  final OkResponseCache responseCache;
9354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  final ConnectionPool connectionPool;
947407d6984ce69693097befc9b72609a8156463bbNarayan Kamath  /* SSL configuration; necessary for HTTP requests that get redirected to HTTPS. */
957407d6984ce69693097befc9b72609a8156463bbNarayan Kamath  SSLSocketFactory sslSocketFactory;
967407d6984ce69693097befc9b72609a8156463bbNarayan Kamath  HostnameVerifier hostnameVerifier;
97faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private List<String> transports;
98faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  OkAuthenticator authenticator;
99faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  final Set<Route> failedRoutes;
10054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
10154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private final RawHeaders rawRequestHeaders = new RawHeaders();
10254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
10354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private int redirectionCount;
104faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private FaultRecoveringOutputStream faultRecoveringRequestBody;
10554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
10654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  protected IOException httpEngineFailure;
10754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  protected HttpEngine httpEngine;
10854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
109faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  public HttpURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache responseCache,
110faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      Set<Route> failedRoutes) {
11154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    super(url);
1127407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    this.followProtocolRedirects = client.getFollowProtocolRedirects();
113faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    this.failedRoutes = failedRoutes;
1147407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    this.requestedProxy = client.getProxy();
1157407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    this.proxySelector = client.getProxySelector();
1167407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    this.cookieHandler = client.getCookieHandler();
1177407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    this.connectionPool = client.getConnectionPool();
1187407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    this.sslSocketFactory = client.getSslSocketFactory();
1197407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    this.hostnameVerifier = client.getHostnameVerifier();
120faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    this.transports = client.getTransports();
121faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    this.authenticator = client.getAuthenticator();
122faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    this.responseCache = responseCache;
123faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
124faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
125faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  Set<Route> getFailedRoutes() {
126faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    return failedRoutes;
127faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
128faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
129faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  List<String> getTransports() {
130faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    return transports;
13154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
13254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
13354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final void connect() throws IOException {
13454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    initHttpEngine();
13554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    boolean success;
13654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    do {
13754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      success = execute(false);
13854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } while (!success);
13954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
14054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
14154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final void disconnect() {
14254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // Calling disconnect() before a connection exists should have no effect.
14354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (httpEngine != null) {
14454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // We close the response body here instead of in
14554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // HttpEngine.release because that is called when input
14654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // has been completely read from the underlying socket.
14754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // However the response body can be a GZIPInputStream that
14854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // still has unread data.
14954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (httpEngine.hasResponse()) {
15054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        Util.closeQuietly(httpEngine.getResponseBody());
15154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
15254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      httpEngine.release(true);
15354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
15454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
15554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
15654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
15754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Returns an input stream from the server in the case of error such as the
15854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * requested file (txt, htm, html) is not found on the remote server.
15954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
16054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final InputStream getErrorStream() {
16154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    try {
16254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      HttpEngine response = getResponse();
16354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (response.hasResponseBody() && response.getResponseCode() >= HTTP_BAD_REQUEST) {
16454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        return response.getResponseBody();
16554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
16654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return null;
16754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } catch (IOException e) {
16854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return null;
16954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
17054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
17154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
17254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
17354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Returns the value of the field at {@code position}. Returns null if there
17454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * are fewer than {@code position} headers.
17554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
17654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final String getHeaderField(int position) {
17754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    try {
17854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return getResponse().getResponseHeaders().getHeaders().getValue(position);
17954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } catch (IOException e) {
18054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return null;
18154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
18254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
18354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
18454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
18554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Returns the value of the field corresponding to the {@code fieldName}, or
18654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * null if there is no such field. If the field has multiple values, the
18754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * last value is returned.
18854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
18954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final String getHeaderField(String fieldName) {
19054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    try {
19154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      RawHeaders rawHeaders = getResponse().getResponseHeaders().getHeaders();
19254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return fieldName == null ? rawHeaders.getStatusLine() : rawHeaders.get(fieldName);
19354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } catch (IOException e) {
19454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return null;
19554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
19654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
19754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
19854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final String getHeaderFieldKey(int position) {
19954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    try {
20054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return getResponse().getResponseHeaders().getHeaders().getFieldName(position);
20154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } catch (IOException e) {
20254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return null;
20354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
20454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
20554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
20654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final Map<String, List<String>> getHeaderFields() {
20754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    try {
20854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return getResponse().getResponseHeaders().getHeaders().toMultimap(true);
20954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } catch (IOException e) {
21054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return null;
21154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
21254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
21354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
21454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final Map<String, List<String>> getRequestProperties() {
21554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (connected) {
21654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new IllegalStateException(
21754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          "Cannot access request header fields after connection is set");
21854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
21954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return rawRequestHeaders.toMultimap(false);
22054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
22154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
22254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final InputStream getInputStream() throws IOException {
22354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (!doInput) {
22454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new ProtocolException("This protocol does not support input");
22554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
22654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
22754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    HttpEngine response = getResponse();
22854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
22954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // if the requested file does not exist, throw an exception formerly the
23054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // Error page from the server was returned if the requested file was
23154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // text/html this has changed to return FileNotFoundException for all
23254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // file types
23354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (getResponseCode() >= HTTP_BAD_REQUEST) {
23454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new FileNotFoundException(url.toString());
23554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
23654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
23754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    InputStream result = response.getResponseBody();
23854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (result == null) {
23954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new ProtocolException("No response body exists; responseCode=" + getResponseCode());
24054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
24154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return result;
24254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
24354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
24454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final OutputStream getOutputStream() throws IOException {
24554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    connect();
24654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
247faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    OutputStream out = httpEngine.getRequestBody();
248faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (out == null) {
24954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new ProtocolException("method does not support a request body: " + method);
25054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } else if (httpEngine.hasResponse()) {
25154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new ProtocolException("cannot write request body after response has been read");
25254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
25354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
254faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (faultRecoveringRequestBody == null) {
255faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      faultRecoveringRequestBody = new FaultRecoveringOutputStream(MAX_REPLAY_BUFFER_LENGTH, out) {
256faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        @Override protected OutputStream replacementStream(IOException e) throws IOException {
257faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          if (httpEngine.getRequestBody() instanceof AbstractOutputStream
258faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath              && ((AbstractOutputStream) httpEngine.getRequestBody()).isClosed()) {
259faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath            return null; // Don't recover once the underlying stream has been closed.
260faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          }
261faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          if (handleFailure(e)) {
262faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath            return httpEngine.getRequestBody();
263faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          }
264faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          return null; // This is a permanent failure.
265faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        }
266faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      };
267faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
268faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
269faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    return faultRecoveringRequestBody;
27054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
27154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
27254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final Permission getPermission() throws IOException {
2737407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    String hostName = getURL().getHost();
2747407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    int hostPort = Util.getEffectivePort(getURL());
2757407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    if (usingProxy()) {
2767407d6984ce69693097befc9b72609a8156463bbNarayan Kamath      InetSocketAddress proxyAddress = (InetSocketAddress) requestedProxy.address();
2777407d6984ce69693097befc9b72609a8156463bbNarayan Kamath      hostName = proxyAddress.getHostName();
2787407d6984ce69693097befc9b72609a8156463bbNarayan Kamath      hostPort = proxyAddress.getPort();
2797407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    }
2807407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    return new SocketPermission(hostName + ":" + hostPort, "connect, resolve");
28154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
28254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
28354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final String getRequestProperty(String field) {
28454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (field == null) {
28554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return null;
28654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
28754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return rawRequestHeaders.get(field);
28854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
28954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
29054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private void initHttpEngine() throws IOException {
29154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (httpEngineFailure != null) {
29254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw httpEngineFailure;
29354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } else if (httpEngine != null) {
29454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return;
29554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
29654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
29754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    connected = true;
29854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    try {
29954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (doOutput) {
30054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        if (method.equals("GET")) {
30154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          // they are requesting a stream to write to. This implies a POST method
30254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          method = "POST";
30354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        } else if (!method.equals("POST") && !method.equals("PUT")) {
30454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          // If the request method is neither POST nor PUT, then you're not writing
30554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          throw new ProtocolException(method + " does not support writing");
306c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath        }
30754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
30854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      httpEngine = newHttpEngine(method, rawRequestHeaders, null, null);
30954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } catch (IOException e) {
31054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      httpEngineFailure = e;
31154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw e;
31254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
31354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
31454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
3157407d6984ce69693097befc9b72609a8156463bbNarayan Kamath  protected HttpURLConnection getHttpConnectionToCache() {
3167407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    return this;
3177407d6984ce69693097befc9b72609a8156463bbNarayan Kamath  }
3187407d6984ce69693097befc9b72609a8156463bbNarayan Kamath
3197407d6984ce69693097befc9b72609a8156463bbNarayan Kamath  private HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
32054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      Connection connection, RetryableOutputStream requestBody) throws IOException {
3217407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    if (url.getProtocol().equals("http")) {
3227407d6984ce69693097befc9b72609a8156463bbNarayan Kamath      return new HttpEngine(this, method, requestHeaders, connection, requestBody);
3237407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    } else if (url.getProtocol().equals("https")) {
3247407d6984ce69693097befc9b72609a8156463bbNarayan Kamath      return new HttpsURLConnectionImpl.HttpsEngine(
3257407d6984ce69693097befc9b72609a8156463bbNarayan Kamath          this, method, requestHeaders, connection, requestBody);
3267407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    } else {
3277407d6984ce69693097befc9b72609a8156463bbNarayan Kamath      throw new AssertionError();
3287407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    }
32954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
33054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
33154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
33254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Aggressively tries to get the final HTTP response, potentially making
33354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * many HTTP requests in the process in order to cope with redirects and
33454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * authentication.
33554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
33654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private HttpEngine getResponse() throws IOException {
33754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    initHttpEngine();
33854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
33954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (httpEngine.hasResponse()) {
34054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return httpEngine;
34154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
34254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
34354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    while (true) {
34454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (!execute(true)) {
34554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        continue;
34654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
34754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
34854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      Retry retry = processResponseHeaders();
34954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (retry == Retry.NONE) {
35054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        httpEngine.automaticallyReleaseConnectionToPool();
35154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        return httpEngine;
35254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
35354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
35454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // The first request was insufficient. Prepare for another...
35554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      String retryMethod = method;
35654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      OutputStream requestBody = httpEngine.getRequestBody();
35754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
35854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM
35954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // redirect should keep the same method, Chrome, Firefox and the
36054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // RI all issue GETs when following any redirect.
36154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      int responseCode = getResponseCode();
36254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (responseCode == HTTP_MULT_CHOICE
36354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          || responseCode == HTTP_MOVED_PERM
36454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          || responseCode == HTTP_MOVED_TEMP
36554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          || responseCode == HTTP_SEE_OTHER) {
36654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        retryMethod = "GET";
36754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        requestBody = null;
36854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
36954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
37054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) {
37154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        throw new HttpRetryException("Cannot retry streamed HTTP body",
37254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson            httpEngine.getResponseCode());
37354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
37454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
37554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (retry == Retry.DIFFERENT_CONNECTION) {
37654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        httpEngine.automaticallyReleaseConnectionToPool();
37754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
37854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
37954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      httpEngine.release(false);
38054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
38154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      httpEngine = newHttpEngine(retryMethod, rawRequestHeaders, httpEngine.getConnection(),
38254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          (RetryableOutputStream) requestBody);
38354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
38454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
38554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
38654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
38754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Sends a request and optionally reads a response. Returns true if the
38854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * request was successfully executed, and false if the request can be
38954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * retried. Throws an exception if the request failed permanently.
39054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
39154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private boolean execute(boolean readResponse) throws IOException {
39254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    try {
39354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      httpEngine.sendRequest();
39454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (readResponse) {
39554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        httpEngine.readResponse();
39654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
39754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return true;
39854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } catch (IOException e) {
399faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (handleFailure(e)) {
40054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        return false;
401faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      } else {
402faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        throw e;
40354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
404faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
405faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
406faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
407faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  /**
408faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   * Report and attempt to recover from {@code e}. Returns true if the HTTP
409faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   * engine was replaced and the request should be retried. Otherwise the
410faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   * failure is permanent.
411faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   */
412faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private boolean handleFailure(IOException e) throws IOException {
413faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    RouteSelector routeSelector = httpEngine.routeSelector;
414faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (routeSelector != null && httpEngine.connection != null) {
415faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      routeSelector.connectFailed(httpEngine.connection, e);
416faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
417faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
418faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    OutputStream requestBody = httpEngine.getRequestBody();
419faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    boolean canRetryRequestBody = requestBody == null
420faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        || requestBody instanceof RetryableOutputStream
421faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        || (faultRecoveringRequestBody != null && faultRecoveringRequestBody.isRecoverable());
422faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (routeSelector == null && httpEngine.connection == null // No connection.
423faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        || routeSelector != null && !routeSelector.hasNext() // No more routes to attempt.
424faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        || !isRecoverable(e)
425faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        || !canRetryRequestBody) {
42654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      httpEngineFailure = e;
427faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      return false;
42854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
429faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
430faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    httpEngine.release(true);
431faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    RetryableOutputStream retryableOutputStream = requestBody instanceof RetryableOutputStream
432faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        ? (RetryableOutputStream) requestBody
433faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        : null;
434faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    httpEngine = newHttpEngine(method, rawRequestHeaders, null, retryableOutputStream);
435faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    httpEngine.routeSelector = routeSelector; // Keep the same routeSelector.
436faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (faultRecoveringRequestBody != null && faultRecoveringRequestBody.isRecoverable()) {
437faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      httpEngine.sendRequest();
438faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      faultRecoveringRequestBody.replaceStream(httpEngine.getRequestBody());
439faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
440faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    return true;
44154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
44254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
44354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private boolean isRecoverable(IOException e) {
44454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // If the problem was a CertificateException from the X509TrustManager,
44554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // do not retry, we didn't have an abrupt server initiated exception.
44654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    boolean sslFailure =
44754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        e instanceof SSLHandshakeException && e.getCause() instanceof CertificateException;
44854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    boolean protocolFailure = e instanceof ProtocolException;
44954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return !sslFailure && !protocolFailure;
45054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
45154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
452faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  public HttpEngine getHttpEngine() {
45354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return httpEngine;
45454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
45554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
45654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  enum Retry {
45754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    NONE,
45854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    SAME_CONNECTION,
45954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    DIFFERENT_CONNECTION
46054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
46154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
46254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
46354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Returns the retry action to take for the current response headers. The
46454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * headers, proxy and target URL or this connection may be adjusted to
46554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * prepare for a follow up request.
46654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
46754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private Retry processResponseHeaders() throws IOException {
4687407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    Proxy selectedProxy = httpEngine.connection != null
469faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        ? httpEngine.connection.getRoute().getProxy()
4707407d6984ce69693097befc9b72609a8156463bbNarayan Kamath        : requestedProxy;
471faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    final int responseCode = getResponseCode();
472faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    switch (responseCode) {
47354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      case HTTP_PROXY_AUTH:
4747407d6984ce69693097befc9b72609a8156463bbNarayan Kamath        if (selectedProxy.type() != Proxy.Type.HTTP) {
47554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
476c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath        }
47754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        // fall-through
47854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      case HTTP_UNAUTHORIZED:
479faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        boolean credentialsFound = HttpAuthenticator.processAuthHeader(authenticator,
480faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath            getResponseCode(), httpEngine.getResponseHeaders().getHeaders(), rawRequestHeaders,
481faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath            selectedProxy, url);
48254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        return credentialsFound ? Retry.SAME_CONNECTION : Retry.NONE;
48354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
48454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      case HTTP_MULT_CHOICE:
48554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      case HTTP_MOVED_PERM:
48654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      case HTTP_MOVED_TEMP:
48754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      case HTTP_SEE_OTHER:
488faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case HTTP_TEMP_REDIRECT:
48954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        if (!getInstanceFollowRedirects()) {
49054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          return Retry.NONE;
491c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath        }
49254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        if (++redirectionCount > MAX_REDIRECTS) {
49354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          throw new ProtocolException("Too many redirects: " + redirectionCount);
494c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath        }
495faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        if (responseCode == HTTP_TEMP_REDIRECT && !method.equals("GET") && !method.equals("HEAD")) {
496faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          // "If the 307 status code is received in response to a request other than GET or HEAD,
497faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          // the user agent MUST NOT automatically redirect the request"
498faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          return Retry.NONE;
499faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        }
50054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        String location = getHeaderField("Location");
50154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        if (location == null) {
50254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          return Retry.NONE;
503c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath        }
50454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        URL previousUrl = url;
50554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        url = new URL(previousUrl, location);
5067407d6984ce69693097befc9b72609a8156463bbNarayan Kamath        if (!url.getProtocol().equals("https") && !url.getProtocol().equals("http")) {
5077407d6984ce69693097befc9b72609a8156463bbNarayan Kamath          return Retry.NONE; // Don't follow redirects to unsupported protocols.
5087407d6984ce69693097befc9b72609a8156463bbNarayan Kamath        }
5097407d6984ce69693097befc9b72609a8156463bbNarayan Kamath        boolean sameProtocol = previousUrl.getProtocol().equals(url.getProtocol());
5107407d6984ce69693097befc9b72609a8156463bbNarayan Kamath        if (!sameProtocol && !followProtocolRedirects) {
5117407d6984ce69693097befc9b72609a8156463bbNarayan Kamath          return Retry.NONE; // This client doesn't follow redirects across protocols.
512c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath        }
5137407d6984ce69693097befc9b72609a8156463bbNarayan Kamath        boolean sameHost = previousUrl.getHost().equals(url.getHost());
5147407d6984ce69693097befc9b72609a8156463bbNarayan Kamath        boolean samePort = getEffectivePort(previousUrl) == getEffectivePort(url);
5157407d6984ce69693097befc9b72609a8156463bbNarayan Kamath        if (sameHost && samePort && sameProtocol) {
51654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          return Retry.SAME_CONNECTION;
51754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        } else {
51854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          return Retry.DIFFERENT_CONNECTION;
5192231db3e6bb54447a9b14cf004a6cb03c373651cjwilson        }
5202231db3e6bb54447a9b14cf004a6cb03c373651cjwilson
52154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      default:
52254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        return Retry.NONE;
5232231db3e6bb54447a9b14cf004a6cb03c373651cjwilson    }
52454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
5252231db3e6bb54447a9b14cf004a6cb03c373651cjwilson
52654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /** @see java.net.HttpURLConnection#setFixedLengthStreamingMode(int) */
52754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  final int getFixedContentLength() {
52854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return fixedContentLength;
52954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
530c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
53154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /** @see java.net.HttpURLConnection#setChunkedStreamingMode(int) */
53254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  final int getChunkLength() {
53354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return chunkLength;
53454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
535c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
53654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final boolean usingProxy() {
5377407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    return (requestedProxy != null && requestedProxy.type() != Proxy.Type.DIRECT);
53854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
539c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
54054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public String getResponseMessage() throws IOException {
54154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return getResponse().getResponseHeaders().getHeaders().getResponseMessage();
54254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
543c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
54454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final int getResponseCode() throws IOException {
54554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return getResponse().getResponseCode();
54654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
547c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
54854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final void setRequestProperty(String field, String newValue) {
54954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (connected) {
55054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new IllegalStateException("Cannot set request property after connection is made");
551c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath    }
55254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (field == null) {
55354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new NullPointerException("field == null");
554c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath    }
555faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if ("X-Android-Transports".equals(field)) {
556faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      setTransports(newValue, false /* append */);
557faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    } else {
558faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      rawRequestHeaders.set(field, newValue);
559faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
56054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
561c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
56254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  @Override public final void addRequestProperty(String field, String value) {
56354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (connected) {
56454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new IllegalStateException("Cannot add request property after connection is made");
565c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath    }
56654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (field == null) {
56754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new NullPointerException("field == null");
568c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath    }
569faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
570faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if ("X-Android-Transports".equals(field)) {
571faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      setTransports(value, true /* append */);
572faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    } else {
573faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      rawRequestHeaders.add(field, value);
574faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
575faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
576faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
577faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  /*
578faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   * Splits and validates a comma-separated string of transports.
579faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   * When append == false, we require that the transport list contains "http/1.1".
580faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   */
581faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private void setTransports(String transportsString, boolean append) {
582faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (transportsString == null) {
583faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      throw new NullPointerException("transportsString == null");
584faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
585faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
586faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    String[] transports = transportsString.split(",", -1);
587faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    ArrayList<String> transportsList = new ArrayList<String>();
588faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (!append) {
589faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      // If we're not appending to the list, we need to make sure
590faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      // the list contains "http/1.1". We do this in a separate loop
591faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      // to avoid modifying any state before we validate the input.
592faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      boolean containsHttp = false;
593faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      for (int i = 0; i < transports.length; ++i) {
594faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        if ("http/1.1".equals(transports[i])) {
595faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          containsHttp = true;
596faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          break;
597faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        }
598faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
599faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
600faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (!containsHttp) {
601faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        throw new IllegalArgumentException("Transport list doesn't contain http/1.1");
602faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
603faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    } else {
604faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      transportsList.addAll(this.transports);
605faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
606faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
607faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    for (int i = 0; i < transports.length; ++i) {
608faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (transports[i].length() == 0) {
609faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        throw new IllegalArgumentException("Transport list contains an empty transport");
610faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
611faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
612faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (!transportsList.contains(transports[i])) {
613faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        transportsList.add(transports[i]);
614faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
615faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
616faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
617faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    this.transports = Util.immutableList(transportsList);
61854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
619c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath}
620