HttpEngine.java revision 76739264ce52fe7a6c5c3558dad87b649118deff
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;
192231db3e6bb54447a9b14cf004a6cb03c373651cjwilson
202231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport com.squareup.okhttp.Address;
212231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport com.squareup.okhttp.Connection;
22a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamathimport com.squareup.okhttp.OkHttpClient;
23a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamathimport com.squareup.okhttp.OkResponseCache;
242231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport com.squareup.okhttp.ResponseSource;
252231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport com.squareup.okhttp.TunnelRequest;
2654cf3446000fdcf88a9e62724f7deb0282e98da1jwilsonimport com.squareup.okhttp.internal.Dns;
272231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport com.squareup.okhttp.internal.Platform;
282231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport com.squareup.okhttp.internal.Util;
29c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.ByteArrayInputStream;
30c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.IOException;
31c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.InputStream;
32c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.OutputStream;
33c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.CacheRequest;
34c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.CacheResponse;
35c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.CookieHandler;
36a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamathimport java.net.HttpURLConnection;
37c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.Proxy;
38c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.URI;
39c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.URISyntaxException;
40c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.URL;
412231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport java.net.UnknownHostException;
42c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.Collections;
43c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.Date;
44c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.HashMap;
45c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.List;
46c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.Map;
47c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.zip.GZIPInputStream;
487899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamathimport javax.net.ssl.HostnameVerifier;
49c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport javax.net.ssl.SSLSocketFactory;
50c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
5154cf3446000fdcf88a9e62724f7deb0282e98da1jwilsonimport static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY;
5254cf3446000fdcf88a9e62724f7deb0282e98da1jwilsonimport static com.squareup.okhttp.internal.Util.getDefaultPort;
5354cf3446000fdcf88a9e62724f7deb0282e98da1jwilsonimport static com.squareup.okhttp.internal.Util.getEffectivePort;
5454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
55c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath/**
56c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * Handles a single HTTP request/response pair. Each HTTP engine follows this
57c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * lifecycle:
58c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * <ol>
5954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * <li>It is created.
6054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * <li>The HTTP request message is sent with sendRequest(). Once the request
6154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * is sent it is an error to modify the request headers. After
6254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * sendRequest() has been called the request body can be written to if
6354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * it exists.
6454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * <li>The HTTP response message is read with readResponse(). After the
6554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * response has been read the response headers and body can be read.
6654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * All responses have a response body input stream, though in some
6754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson * instances this stream is empty.
68c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * </ol>
69c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *
70c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * <p>The request and response may be served by the HTTP response cache, by the
71c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * network, or by both in the event of a conditional GET.
72c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath *
73c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * <p>This class may hold a socket connection that needs to be released or
74c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * recycled. By default, this socket connection is held when the last byte of
75c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * the response is consumed. To release the connection when it is no longer
76c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * required, use {@link #automaticallyReleaseConnectionToPool()}.
77c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath */
78c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathpublic class HttpEngine {
7954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private static final CacheResponse GATEWAY_TIMEOUT_RESPONSE = new CacheResponse() {
8054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    @Override public Map<String, List<String>> getHeaders() throws IOException {
8154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      Map<String, List<String>> result = new HashMap<String, List<String>>();
8254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      result.put(null, Collections.singletonList("HTTP/1.1 504 Gateway Timeout"));
8354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return result;
84c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath    }
8554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    @Override public InputStream getBody() throws IOException {
8654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return new ByteArrayInputStream(EMPTY_BYTE_ARRAY);
87c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath    }
8854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  };
8954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public static final int HTTP_CONTINUE = 100;
9054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
91a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath  protected final Policy policy;
92a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath  protected final OkHttpClient client;
9354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
9454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  protected final String method;
9554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
9654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private ResponseSource responseSource;
9754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
9854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  protected Connection connection;
9954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  protected RouteSelector routeSelector;
10054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private OutputStream requestBodyOut;
10154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
10254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private Transport transport;
10354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
10454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private InputStream responseTransferIn;
10554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private InputStream responseBodyIn;
10654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
10754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private CacheResponse cacheResponse;
10854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private CacheRequest cacheRequest;
10954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
11054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /** The time when the request headers were written, or -1 if they haven't been written yet. */
11154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  long sentRequestMillis = -1;
11254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
113415f345db25a1075e6103e55e7ea735ced0072b9Narayan Kamath  /** Whether the connection has been established */
114415f345db25a1075e6103e55e7ea735ced0072b9Narayan Kamath  boolean connected;
115415f345db25a1075e6103e55e7ea735ced0072b9Narayan Kamath
11654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
11754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * True if this client added an "Accept-Encoding: gzip" header field and is
11854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * therefore responsible for also decompressing the transfer stream.
11954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
12054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private boolean transparentGzip;
12154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
12254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  final URI uri;
12354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
12454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  final RequestHeaders requestHeaders;
12554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
12654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /** Null until a response is received from the network or the cache. */
12754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  ResponseHeaders responseHeaders;
12854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
12954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  // The cache response currently being validated on a conditional get. Null
13054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  // if the cached response doesn't exist or doesn't need validation. If the
13154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  // conditional get succeeds, these will be used for the response headers and
13254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  // body. If it fails, these be closed and set to null.
13354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private ResponseHeaders cachedResponseHeaders;
13454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private InputStream cachedResponseBody;
13554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
13654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
13754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * True if the socket connection should be released to the connection pool
13854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * when the response has been fully read.
13954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
14054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private boolean automaticallyReleaseConnectionToPool;
14154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
14254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /** True if the socket connection is no longer needed by this engine. */
14354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private boolean connectionReleased;
14454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
14554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
14654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * @param requestHeaders the client's supplied request headers. This class
147a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath   *     creates a private copy that it can mutate.
14854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * @param connection the connection used for an intermediate response
149a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath   *     immediately prior to this request/response pair, such as a same-host
150a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath   *     redirect. This engine assumes ownership of the connection and must
151a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath   *     release it when it is unneeded.
15254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
153a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath  public HttpEngine(OkHttpClient client, Policy policy, String method, RawHeaders requestHeaders,
15454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      Connection connection, RetryableOutputStream requestBodyOut) throws IOException {
155a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    this.client = client;
15654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    this.policy = policy;
15754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    this.method = method;
15854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    this.connection = connection;
15954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    this.requestBodyOut = requestBodyOut;
16054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
16154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    try {
16254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      uri = Platform.get().toUriLenient(policy.getURL());
16354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } catch (URISyntaxException e) {
164faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      throw new IOException(e.getMessage());
16554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
16654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
16754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    this.requestHeaders = new RequestHeaders(uri, new RawHeaders(requestHeaders));
16854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
16954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
17054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public URI getUri() {
17154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return uri;
17254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
17354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
17454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
17554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Figures out what the response source will be, and opens a socket to that
17654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * source if necessary. Prepares the request headers and gets ready to start
17754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * writing the request body if it exists.
17854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
17954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final void sendRequest() throws IOException {
18054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (responseSource != null) {
18154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return;
18254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
18354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
18454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    prepareRawRequestHeaders();
18554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    initResponseSource();
186a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    OkResponseCache responseCache = client.getOkResponseCache();
187a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    if (responseCache != null) {
188a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath      responseCache.trackResponse(responseSource);
18954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
19054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
19154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // The raw response source may require the network, but the request
19254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // headers may forbid network use. In that case, dispose of the network
19354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // response and use a GATEWAY_TIMEOUT response instead, as specified
19454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // by http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4.
19554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) {
19654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
19754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        Util.closeQuietly(cachedResponseBody);
19854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
19954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      this.responseSource = ResponseSource.CACHE;
20054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      this.cacheResponse = GATEWAY_TIMEOUT_RESPONSE;
20154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders(), true);
20254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody());
20354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
20454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
20554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (responseSource.requiresConnection()) {
20654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      sendSocketRequest();
20754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } else if (connection != null) {
208a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath      client.getConnectionPool().recycle(connection);
20954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      connection = null;
21054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
21154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
21254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
21354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
21454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Initialize the source for this response. It may be corrected later if the
21554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * request headers forbids network use.
21654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
21754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private void initResponseSource() throws IOException {
21854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    responseSource = ResponseSource.NETWORK;
219a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    if (!policy.getUseCaches()) return;
22054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
221a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    OkResponseCache responseCache = client.getOkResponseCache();
222a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    if (responseCache == null) return;
223a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath
224a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    CacheResponse candidate = responseCache.get(
225a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath        uri, method, requestHeaders.getHeaders().toMultimap(false));
226a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    if (candidate == null) return;
22754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
22854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    Map<String, List<String>> responseHeadersMap = candidate.getHeaders();
22954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    cachedResponseBody = candidate.getBody();
23054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (!acceptCacheResponseType(candidate)
23154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        || responseHeadersMap == null
23254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        || cachedResponseBody == null) {
23354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      Util.closeQuietly(cachedResponseBody);
23454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return;
23554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
23654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
23754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap, true);
23854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    cachedResponseHeaders = new ResponseHeaders(uri, rawResponseHeaders);
23954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    long now = System.currentTimeMillis();
24054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    this.responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders);
24154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (responseSource == ResponseSource.CACHE) {
24254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      this.cacheResponse = candidate;
24354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      setResponse(cachedResponseHeaders, cachedResponseBody);
24454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
24554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      this.cacheResponse = candidate;
24654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } else if (responseSource == ResponseSource.NETWORK) {
24754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      Util.closeQuietly(cachedResponseBody);
24854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } else {
24954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new AssertionError();
25054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
25154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
25254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
25354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private void sendSocketRequest() throws IOException {
25454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (connection == null) {
25554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      connect();
25654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
25754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
25854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (transport != null) {
25954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new IllegalStateException();
26054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
26154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
26254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    transport = (Transport) connection.newTransport(this);
26354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
26454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (hasRequestBody() && requestBodyOut == null) {
26554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // Create a request body if we don't have one already. We'll already
26654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // have one if we're retrying a failed POST.
26754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      requestBodyOut = transport.createRequestBody();
26854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
26954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
27054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
27154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /** Connect to the origin server either directly or via a proxy. */
27254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  protected final void connect() throws IOException {
27354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (connection != null) {
27454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return;
27554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
27654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (routeSelector == null) {
27754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      String uriHost = uri.getHost();
27854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (uriHost == null) {
27954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        throw new UnknownHostException(uri.toString());
28054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
2817407d6984ce69693097befc9b72609a8156463bbNarayan Kamath      SSLSocketFactory sslSocketFactory = null;
2827407d6984ce69693097befc9b72609a8156463bbNarayan Kamath      HostnameVerifier hostnameVerifier = null;
2837407d6984ce69693097befc9b72609a8156463bbNarayan Kamath      if (uri.getScheme().equalsIgnoreCase("https")) {
284a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath        sslSocketFactory = client.getSslSocketFactory();
285a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath        hostnameVerifier = client.getHostnameVerifier();
2867407d6984ce69693097befc9b72609a8156463bbNarayan Kamath      }
2877407d6984ce69693097befc9b72609a8156463bbNarayan Kamath      Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory,
288a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath          hostnameVerifier, client.getAuthenticator(), client.getProxy(), client.getTransports());
289a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath      routeSelector = new RouteSelector(address, uri, client.getProxySelector(),
290a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath          client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase());
29154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
292fb0eb65be9f50e75fa37c250e97914252c587cafjwilson    connection = routeSelector.next(method);
29354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (!connection.isConnected()) {
294a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath      connection.connect(client.getConnectTimeout(), client.getReadTimeout(), getTunnelConfig());
295a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath      client.getConnectionPool().maybeShare(connection);
296a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath      client.getRoutesDatabase().connected(connection.getRoute());
29776739264ce52fe7a6c5c3558dad87b649118deffNarayan Kamath    } else {
29876739264ce52fe7a6c5c3558dad87b649118deffNarayan Kamath      connection.updateReadTimeout(client.getReadTimeout());
29954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
30054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    connected(connection);
301a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    if (connection.getRoute().getProxy() != client.getProxy()) {
3027407d6984ce69693097befc9b72609a8156463bbNarayan Kamath      // Update the request line if the proxy changed; it may need a host name.
30354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      requestHeaders.getHeaders().setRequestLine(getRequestLine());
30454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
30554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
30654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
30754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
30854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Called after a socket connection has been created or retrieved from the
30954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * pool. Subclasses use this hook to get a reference to the TLS data.
31054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
31154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  protected void connected(Connection connection) {
312415f345db25a1075e6103e55e7ea735ced0072b9Narayan Kamath    connected = true;
31354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
31454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
31554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
31654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Called immediately before the transport transmits HTTP request headers.
31754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * This is used to observe the sent time should the request be cached.
31854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
31954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public void writingRequestHeaders() {
32054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (sentRequestMillis != -1) {
32154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new IllegalStateException();
32254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
32354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    sentRequestMillis = System.currentTimeMillis();
32454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
32554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
32654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
32754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * @param body the response body, or null if it doesn't exist or isn't
32854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * available.
32954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
33054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private void setResponse(ResponseHeaders headers, InputStream body) throws IOException {
33154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (this.responseBodyIn != null) {
33254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new IllegalStateException();
33354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
33454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    this.responseHeaders = headers;
33554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (body != null) {
33654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      initContentStream(body);
33754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
33854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
33954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
34054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  boolean hasRequestBody() {
34154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return method.equals("POST") || method.equals("PUT");
34254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
34354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
34454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /** Returns the request body or null if this request doesn't have a body. */
34554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final OutputStream getRequestBody() {
34654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (responseSource == null) {
34754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new IllegalStateException();
34854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
34954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return requestBodyOut;
35054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
35154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
35254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final boolean hasResponse() {
35354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return responseHeaders != null;
35454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
35554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
35654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final RequestHeaders getRequestHeaders() {
35754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return requestHeaders;
35854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
35954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
36054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final ResponseHeaders getResponseHeaders() {
36154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (responseHeaders == null) {
36254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new IllegalStateException();
36354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
36454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return responseHeaders;
36554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
36654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
36754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final int getResponseCode() {
36854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (responseHeaders == null) {
36954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new IllegalStateException();
37054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
37154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return responseHeaders.getHeaders().getResponseCode();
37254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
37354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
37454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final InputStream getResponseBody() {
37554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (responseHeaders == null) {
37654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new IllegalStateException();
37754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
37854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return responseBodyIn;
37954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
38054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
38154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final CacheResponse getCacheResponse() {
38254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return cacheResponse;
38354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
38454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
38554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final Connection getConnection() {
38654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return connection;
38754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
38854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
38954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
39054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Returns true if {@code cacheResponse} is of the right type. This
39154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * condition is necessary but not sufficient for the cached response to
39254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * be used.
39354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
39454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
39554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return true;
39654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
39754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
39854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private void maybeCache() throws IOException {
39954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // Are we caching at all?
400a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    if (!policy.getUseCaches()) return;
401a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    OkResponseCache responseCache = client.getOkResponseCache();
402a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    if (responseCache == null) return;
403a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath
404a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    HttpURLConnection connectionToCache = policy.getHttpConnectionToCache();
40554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
40654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // Should we cache this response for this request?
40754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (!responseHeaders.isCacheable(requestHeaders)) {
408a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath      responseCache.maybeRemove(connectionToCache.getRequestMethod(), uri);
40954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return;
41054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
41154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
41254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // Offer this request to the cache.
413a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    cacheRequest = responseCache.put(uri, connectionToCache);
41454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
41554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
41654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
41754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Cause the socket connection to be released to the connection pool when
41854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * it is no longer needed. If it is already unneeded, it will be pooled
41954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * immediately. Otherwise the connection is held so that redirects can be
42054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * handled by the same connection.
42154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
42254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final void automaticallyReleaseConnectionToPool() {
42354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    automaticallyReleaseConnectionToPool = true;
42454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (connection != null && connectionReleased) {
425a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath      client.getConnectionPool().recycle(connection);
42654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      connection = null;
42754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
42854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
42954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
43054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
43154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Releases this engine so that its resources may be either reused or
43254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * closed. Also call {@link #automaticallyReleaseConnectionToPool} unless
43354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * the connection will be used to follow a redirect.
43454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
43554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final void release(boolean streamCancelled) {
43654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // If the response body comes from the cache, close it.
43754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (responseBodyIn == cachedResponseBody) {
43854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      Util.closeQuietly(responseBodyIn);
439c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath    }
440c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
44154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (!connectionReleased && connection != null) {
44254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      connectionReleased = true;
443c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
44454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (transport == null || !transport.makeReusable(streamCancelled, requestBodyOut,
44554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          responseTransferIn)) {
44654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        Util.closeQuietly(connection);
44754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        connection = null;
44854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      } else if (automaticallyReleaseConnectionToPool) {
449a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath        client.getConnectionPool().recycle(connection);
45054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        connection = null;
45154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
45254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
45354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
454c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
45554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private void initContentStream(InputStream transferStream) throws IOException {
45654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    responseTransferIn = transferStream;
45754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (transparentGzip && responseHeaders.isContentEncodingGzip()) {
45854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // If the response was transparently gzipped, remove the gzip header field
45954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // so clients don't double decompress. http://b/3009828
46054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      //
46154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // Also remove the Content-Length in this case because it contains the
46254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // length 528 of the gzipped response. This isn't terribly useful and is
46354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // dangerous because 529 clients can query the content length, but not
46454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      // the content encoding.
46554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      responseHeaders.stripContentEncoding();
46654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      responseHeaders.stripContentLength();
46754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      responseBodyIn = new GZIPInputStream(transferStream);
46854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } else {
46954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      responseBodyIn = transferStream;
47054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
47154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
47254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
47354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
47454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Returns true if the response must have a (possibly 0-length) body.
47554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * See RFC 2616 section 4.3.
47654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
47754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final boolean hasResponseBody() {
47854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    int responseCode = responseHeaders.getHeaders().getResponseCode();
47954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
48054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // HEAD requests never yield a body regardless of the response headers.
48154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (method.equals("HEAD")) {
48254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return false;
48354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
48454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
48554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if ((responseCode < HTTP_CONTINUE || responseCode >= 200)
48654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        && responseCode != HttpURLConnectionImpl.HTTP_NO_CONTENT
48754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        && responseCode != HttpURLConnectionImpl.HTTP_NOT_MODIFIED) {
48854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return true;
48954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
49054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
49154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // If the Content-Length or Transfer-Encoding headers disagree with the
49254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // response code, the response is malformed. For best compatibility, we
49354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    // honor the headers.
49454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (responseHeaders.getContentLength() != -1 || responseHeaders.isChunked()) {
49554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return true;
49654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
49754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
49854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return false;
49954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
50054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
50154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
50254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Populates requestHeaders with defaults and cookies.
50354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   *
50454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * <p>This client doesn't specify a default {@code Accept} header because it
50554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * doesn't know what content types the application is interested in.
50654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
50754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private void prepareRawRequestHeaders() throws IOException {
50854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    requestHeaders.getHeaders().setRequestLine(getRequestLine());
50954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
51054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (requestHeaders.getUserAgent() == null) {
51154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      requestHeaders.setUserAgent(getDefaultUserAgent());
51254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
51354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
51454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (requestHeaders.getHost() == null) {
51554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      requestHeaders.setHost(getOriginAddress(policy.getURL()));
51654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
51754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
51854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if ((connection == null || connection.getHttpMinorVersion() != 0)
51954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        && requestHeaders.getConnection() == null) {
52054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      requestHeaders.setConnection("Keep-Alive");
52154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
52254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
52354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (requestHeaders.getAcceptEncoding() == null) {
52454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      transparentGzip = true;
52554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      requestHeaders.setAcceptEncoding("gzip");
52654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
52754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
52854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (hasRequestBody() && requestHeaders.getContentType() == null) {
52954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      requestHeaders.setContentType("application/x-www-form-urlencoded");
53054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
53154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
53254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    long ifModifiedSince = policy.getIfModifiedSince();
53354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (ifModifiedSince != 0) {
53454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      requestHeaders.setIfModifiedSince(new Date(ifModifiedSince));
53554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
53654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
537a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    CookieHandler cookieHandler = client.getCookieHandler();
53854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (cookieHandler != null) {
53954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      requestHeaders.addCookies(
54054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson          cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap(false)));
54154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
54254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
54354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
54454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
54554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Returns the request status line, like "GET / HTTP/1.1". This is exposed
54654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * to the application by {@link HttpURLConnectionImpl#getHeaderFields}, so
54754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * it needs to be set even if the transport is SPDY.
54854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
54954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  String getRequestLine() {
55054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    String protocol =
55154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        (connection == null || connection.getHttpMinorVersion() != 0) ? "HTTP/1.1" : "HTTP/1.0";
55254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return method + " " + requestString() + " " + protocol;
55354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
55454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
55554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  private String requestString() {
55654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    URL url = policy.getURL();
55754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (includeAuthorityInRequestLine()) {
55854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return url.toString();
55954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } else {
56054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return requestPath(url);
56154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
56254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
56354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
56454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
56554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Returns the path to request, like the '/' in 'GET / HTTP/1.1'. Never
56654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * empty, even if the request URL is. Includes the query component if it
56754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * exists.
56854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
56954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public static String requestPath(URL url) {
57054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    String fileOnly = url.getFile();
57154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (fileOnly == null) {
57254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return "/";
57354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } else if (!fileOnly.startsWith("/")) {
57454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return "/" + fileOnly;
57554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    } else {
57654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return fileOnly;
577c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath    }
57854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
579c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
58054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
58154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Returns true if the request line should contain the full URL with host
58254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * and port (like "GET http://android.com/foo HTTP/1.1") or only the path
58354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * (like "GET /foo HTTP/1.1").
58454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   *
58554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * <p>This is non-final because for HTTPS it's never necessary to supply the
58654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * full URL, even if a proxy is in use.
58754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
58854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  protected boolean includeAuthorityInRequestLine() {
5897407d6984ce69693097befc9b72609a8156463bbNarayan Kamath    return connection == null
5907407d6984ce69693097befc9b72609a8156463bbNarayan Kamath        ? policy.usingProxy() // A proxy was requested.
591faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        : connection.getRoute().getProxy().type() == Proxy.Type.HTTP; // A proxy was selected.
59254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
59354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
59454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public static String getDefaultUserAgent() {
59554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    String agent = System.getProperty("http.agent");
59654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return agent != null ? agent : ("Java" + System.getProperty("java.version"));
59754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
59854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
59954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public static String getOriginAddress(URL url) {
60054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    int port = url.getPort();
60154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    String result = url.getHost();
60254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (port > 0 && port != getDefaultPort(url.getProtocol())) {
60354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      result = result + ":" + port;
60454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
60554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return result;
60654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
60754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
60854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  /**
60954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * Flushes the remaining request header and body, parses the HTTP response
61054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   * headers and starts reading the HTTP response body if it exists.
61154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson   */
61254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public final void readResponse() throws IOException {
61354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (hasResponse()) {
614faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      responseHeaders.setResponseSource(responseSource);
61554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return;
61654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
61754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
61854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (responseSource == null) {
61954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      throw new IllegalStateException("readResponse() without sendRequest()");
62054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
62154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
62254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (!responseSource.requiresConnection()) {
62354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      return;
62454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
62554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
62654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (sentRequestMillis == -1) {
62754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (requestBodyOut instanceof RetryableOutputStream) {
62854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        int contentLength = ((RetryableOutputStream) requestBodyOut).contentLength();
62954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        requestHeaders.setContentLength(contentLength);
63054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
63154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      transport.writeRequestHeaders();
63254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
63354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
63454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (requestBodyOut != null) {
63554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      requestBodyOut.close();
63654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (requestBodyOut instanceof RetryableOutputStream) {
63754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        transport.writeRequestBody((RetryableOutputStream) requestBodyOut);
63854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
63954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    }
64054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
64154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    transport.flushRequest();
64254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
64354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    responseHeaders = transport.readResponseHeaders();
64454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    responseHeaders.setLocalTimestamps(sentRequestMillis, System.currentTimeMillis());
645faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    responseHeaders.setResponseSource(responseSource);
64654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson
64754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
64854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      if (cachedResponseHeaders.validate(responseHeaders)) {
64954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        release(false);
65054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders);
65154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        setResponse(combinedHeaders, cachedResponseBody);
652a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath        OkResponseCache responseCache = client.getOkResponseCache();
653a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath        responseCache.trackConditionalCacheHit();
654a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath        responseCache.update(cacheResponse, policy.getHttpConnectionToCache());
65554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        return;
65654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      } else {
65754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson        Util.closeQuietly(cachedResponseBody);
65854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      }
659c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath    }
660c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
66154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (hasResponseBody()) {
66254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      maybeCache(); // reentrant. this calls into user code which may call back into this!
663c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath    }
664c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
66554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    initContentStream(transport.getTransferStream(cacheRequest));
66654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
667c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath
66854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  protected TunnelRequest getTunnelConfig() {
66954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    return null;
67054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
6717899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath
67254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  public void receiveHeaders(RawHeaders headers) throws IOException {
673a82f42bbeedd0b07f3892f3b0efaa8122dc8f264Narayan Kamath    CookieHandler cookieHandler = client.getCookieHandler();
67454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson    if (cookieHandler != null) {
67554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson      cookieHandler.put(uri, headers.toMultimap(true));
6767899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath    }
67754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson  }
678c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath}
679