Home | History | Annotate | Download | only in http
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 package com.squareup.okhttp.internal.http;
     19 
     20 import com.squareup.okhttp.Address;
     21 import com.squareup.okhttp.Connection;
     22 import com.squareup.okhttp.Headers;
     23 import com.squareup.okhttp.HostResolver;
     24 import com.squareup.okhttp.OkHttpClient;
     25 import com.squareup.okhttp.OkResponseCache;
     26 import com.squareup.okhttp.Request;
     27 import com.squareup.okhttp.Response;
     28 import com.squareup.okhttp.ResponseSource;
     29 import com.squareup.okhttp.Route;
     30 import com.squareup.okhttp.TunnelRequest;
     31 import java.io.IOException;
     32 import java.io.InputStream;
     33 import java.net.CacheRequest;
     34 import java.net.CookieHandler;
     35 import java.net.ProtocolException;
     36 import java.net.URL;
     37 import java.net.UnknownHostException;
     38 import java.security.cert.CertificateException;
     39 import java.util.List;
     40 import java.util.Map;
     41 import javax.net.ssl.HostnameVerifier;
     42 import javax.net.ssl.SSLHandshakeException;
     43 import javax.net.ssl.SSLSocketFactory;
     44 import okio.BufferedSink;
     45 import okio.GzipSource;
     46 import okio.Okio;
     47 import okio.Sink;
     48 import okio.Source;
     49 
     50 import static com.squareup.okhttp.internal.Util.closeQuietly;
     51 import static com.squareup.okhttp.internal.Util.getDefaultPort;
     52 import static com.squareup.okhttp.internal.Util.getEffectivePort;
     53 import static com.squareup.okhttp.internal.http.StatusLine.HTTP_CONTINUE;
     54 import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
     55 import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
     56 
     57 /**
     58  * Handles a single HTTP request/response pair. Each HTTP engine follows this
     59  * lifecycle:
     60  * <ol>
     61  * <li>It is created.
     62  * <li>The HTTP request message is sent with sendRequest(). Once the request
     63  * is sent it is an error to modify the request headers. After
     64  * sendRequest() has been called the request body can be written to if
     65  * it exists.
     66  * <li>The HTTP response message is read with readResponse(). After the
     67  * response has been read the response headers and body can be read.
     68  * All responses have a response body input stream, though in some
     69  * instances this stream is empty.
     70  * </ol>
     71  *
     72  * <p>The request and response may be served by the HTTP response cache, by the
     73  * network, or by both in the event of a conditional GET.
     74  */
     75 public class HttpEngine {
     76   final OkHttpClient client;
     77 
     78   private Connection connection;
     79   private RouteSelector routeSelector;
     80   private Route route;
     81   private final Response priorResponse;
     82 
     83   private Transport transport;
     84 
     85   /** The time when the request headers were written, or -1 if they haven't been written yet. */
     86   long sentRequestMillis = -1;
     87 
     88   /**
     89    * True if this client added an "Accept-Encoding: gzip" header field and is
     90    * therefore responsible for also decompressing the transfer stream.
     91    */
     92   private boolean transparentGzip;
     93 
     94   /**
     95    * True if the request body must be completely buffered before transmission;
     96    * false if it can be streamed. Buffering has two advantages: we don't need
     97    * the content-length in advance and we can retransmit if necessary. The
     98    * upside of streaming is that we can save memory.
     99    */
    100   public final boolean bufferRequestBody;
    101 
    102   /**
    103    * The original application-provided request. Never modified by OkHttp. When
    104    * follow-up requests are necessary, they are derived from this request.
    105    */
    106   private final Request userRequest;
    107 
    108   /**
    109    * The request to send on the network, or null for no network request. This is
    110    * derived from the user request, and customized to support OkHttp features
    111    * like compression and caching.
    112    */
    113   private Request networkRequest;
    114 
    115   /**
    116    * The cached response, or null if the cache doesn't exist or cannot be used
    117    * for this request. Conditional caching means this may be non-null even when
    118    * the network request is non-null. Never modified by OkHttp.
    119    */
    120   private Response cacheResponse;
    121 
    122   /**
    123    * The response read from the network. Null if the network response hasn't
    124    * been read yet, or if the network is not used. Never modified by OkHttp.
    125    */
    126   private Response networkResponse;
    127 
    128   /**
    129    * The user-visible response. This is derived from either the network
    130    * response, cache response, or both. It is customized to support OkHttp
    131    * features like compression and caching.
    132    */
    133   private Response userResponse;
    134 
    135   private Sink requestBodyOut;
    136   private BufferedSink bufferedRequestBody;
    137 
    138   private ResponseSource responseSource;
    139 
    140   /** Null until a response is received from the network or the cache. */
    141   private Source responseTransferSource;
    142   private Source responseBody;
    143   private InputStream responseBodyBytes;
    144 
    145   /** The cache request currently being populated from a network response. */
    146   private CacheRequest storeRequest;
    147 
    148   /**
    149    * @param request the HTTP request without a body. The body must be
    150    *     written via the engine's request body stream.
    151    * @param connection the connection used for an intermediate response
    152    *     immediately prior to this request/response pair, such as a same-host
    153    *     redirect. This engine assumes ownership of the connection and must
    154    *     release it when it is unneeded.
    155    * @param routeSelector the route selector used for a failed attempt
    156    *     immediately preceding this attempt, or null if this request doesn't
    157    *     recover from a failure.
    158    */
    159   public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody,
    160       Connection connection, RouteSelector routeSelector, RetryableSink requestBodyOut,
    161       Response priorResponse) {
    162     this.client = client;
    163     this.userRequest = request;
    164     this.bufferRequestBody = bufferRequestBody;
    165     this.connection = connection;
    166     this.routeSelector = routeSelector;
    167     this.requestBodyOut = requestBodyOut;
    168     this.priorResponse = priorResponse;
    169 
    170     if (connection != null) {
    171       connection.setOwner(this);
    172       this.route = connection.getRoute();
    173     } else {
    174       this.route = null;
    175     }
    176   }
    177 
    178   /**
    179    * Figures out what the response source will be, and opens a socket to that
    180    * source if necessary. Prepares the request headers and gets ready to start
    181    * writing the request body if it exists.
    182    */
    183   public final void sendRequest() throws IOException {
    184     if (responseSource != null) return; // Already sent.
    185     if (transport != null) throw new IllegalStateException();
    186 
    187     Request request = networkRequest(userRequest);
    188 
    189     OkResponseCache responseCache = client.getOkResponseCache();
    190     Response cacheCandidate = responseCache != null
    191         ? responseCache.get(request)
    192         : null;
    193     long now = System.currentTimeMillis();
    194     CacheStrategy cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
    195     responseSource = cacheStrategy.source;
    196     networkRequest = cacheStrategy.networkRequest;
    197     cacheResponse = cacheStrategy.cacheResponse;
    198 
    199     if (responseCache != null) {
    200       responseCache.trackResponse(responseSource);
    201     }
    202 
    203     if (cacheCandidate != null
    204         && (responseSource == ResponseSource.NONE || cacheResponse == null)) {
    205       closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    206     }
    207 
    208     if (networkRequest != null) {
    209       // Open a connection unless we inherited one from a redirect.
    210       if (connection == null) {
    211         connect(networkRequest);
    212       }
    213 
    214       // Blow up if we aren't the current owner of the connection.
    215       if (connection.getOwner() != this && !connection.isSpdy()) throw new AssertionError();
    216 
    217       transport = (Transport) connection.newTransport(this);
    218 
    219       // Create a request body if we don't have one already. We'll already have
    220       // one if we're retrying a failed POST.
    221       if (hasRequestBody() && requestBodyOut == null) {
    222         requestBodyOut = transport.createRequestBody(request);
    223       }
    224 
    225     } else {
    226       // We're using a cached response. Recycle a connection we may have inherited from a redirect.
    227       if (connection != null) {
    228         client.getConnectionPool().recycle(connection);
    229         connection = null;
    230       }
    231 
    232       // No need for the network! Promote the cached response immediately.
    233       this.userResponse = cacheResponse.newBuilder()
    234           .request(userRequest)
    235           .priorResponse(stripBody(priorResponse))
    236           .cacheResponse(stripBody(cacheResponse))
    237           .build();
    238       if (userResponse.body() != null) {
    239         initContentStream(userResponse.body().source());
    240       }
    241     }
    242   }
    243 
    244   private static Response stripBody(Response response) {
    245     return response != null && response.body() != null
    246         ? response.newBuilder().body(null).build()
    247         : response;
    248   }
    249 
    250   /** Connect to the origin server either directly or via a proxy. */
    251   private void connect(Request request) throws IOException {
    252     if (connection != null) throw new IllegalStateException();
    253 
    254     if (routeSelector == null) {
    255       String uriHost = request.url().getHost();
    256       if (uriHost == null || uriHost.length() == 0) {
    257         throw new UnknownHostException(request.url().toString());
    258       }
    259       SSLSocketFactory sslSocketFactory = null;
    260       HostnameVerifier hostnameVerifier = null;
    261       if (request.isHttps()) {
    262         sslSocketFactory = client.getSslSocketFactory();
    263         hostnameVerifier = client.getHostnameVerifier();
    264       }
    265       Address address = new Address(uriHost, getEffectivePort(request.url()),
    266           client.getSocketFactory(), sslSocketFactory, hostnameVerifier, client.getAuthenticator(),
    267           client.getProxy(), client.getProtocols());
    268       routeSelector = new RouteSelector(address, request.uri(), client.getProxySelector(),
    269           client.getConnectionPool(), client.getHostResolver(), client.getRoutesDatabase());
    270     }
    271 
    272     connection = routeSelector.next(request.method());
    273     connection.setOwner(this);
    274 
    275     if (!connection.isConnected()) {
    276       connection.connect(client.getConnectTimeout(), client.getReadTimeout(), getTunnelConfig());
    277       if (connection.isSpdy()) client.getConnectionPool().share(connection);
    278       client.getRoutesDatabase().connected(connection.getRoute());
    279     } else if (!connection.isSpdy()) {
    280       connection.updateReadTimeout(client.getReadTimeout());
    281     }
    282 
    283     route = connection.getRoute();
    284   }
    285 
    286   /**
    287    * Called immediately before the transport transmits HTTP request headers.
    288    * This is used to observe the sent time should the request be cached.
    289    */
    290   public void writingRequestHeaders() {
    291     if (sentRequestMillis != -1) throw new IllegalStateException();
    292     sentRequestMillis = System.currentTimeMillis();
    293   }
    294 
    295   boolean hasRequestBody() {
    296     return HttpMethod.hasRequestBody(userRequest.method());
    297   }
    298 
    299   /** Returns the request body or null if this request doesn't have a body. */
    300   public final Sink getRequestBody() {
    301     if (responseSource == null) throw new IllegalStateException();
    302     return requestBodyOut;
    303   }
    304 
    305   public final BufferedSink getBufferedRequestBody() {
    306     BufferedSink result = bufferedRequestBody;
    307     if (result != null) return result;
    308     Sink requestBody = getRequestBody();
    309     return requestBody != null
    310         ? (bufferedRequestBody = Okio.buffer(requestBody))
    311         : null;
    312   }
    313 
    314   public final boolean hasResponse() {
    315     return userResponse != null;
    316   }
    317 
    318   public final Request getRequest() {
    319     return userRequest;
    320   }
    321 
    322   /** Returns the engine's response. */
    323   // TODO: the returned body will always be null.
    324   public final Response getResponse() {
    325     if (userResponse == null) throw new IllegalStateException();
    326     return userResponse;
    327   }
    328 
    329   public final Source getResponseBody() {
    330     if (userResponse == null) throw new IllegalStateException();
    331     return responseBody;
    332   }
    333 
    334   public final InputStream getResponseBodyBytes() {
    335     InputStream result = responseBodyBytes;
    336     return result != null
    337         ? result
    338         : (responseBodyBytes = Okio.buffer(getResponseBody()).inputStream());
    339   }
    340 
    341   public final Connection getConnection() {
    342     return connection;
    343   }
    344 
    345   /**
    346    * Report and attempt to recover from {@code e}. Returns a new HTTP engine
    347    * that should be used for the retry if {@code e} is recoverable, or null if
    348    * the failure is permanent.
    349    */
    350   public HttpEngine recover(IOException e) {
    351     if (routeSelector != null && connection != null) {
    352       routeSelector.connectFailed(connection, e);
    353     }
    354 
    355     boolean canRetryRequestBody = requestBodyOut == null || requestBodyOut instanceof RetryableSink;
    356     if (routeSelector == null && connection == null // No connection.
    357         || routeSelector != null && !routeSelector.hasNext() // No more routes to attempt.
    358         || !isRecoverable(e)
    359         || !canRetryRequestBody) {
    360       return null;
    361     }
    362 
    363     Connection connection = close();
    364 
    365     // For failure recovery, use the same route selector with a new connection.
    366     return new HttpEngine(client, userRequest, bufferRequestBody, connection, routeSelector,
    367         (RetryableSink) requestBodyOut, priorResponse);
    368   }
    369 
    370   private boolean isRecoverable(IOException e) {
    371     // If the problem was a CertificateException from the X509TrustManager,
    372     // do not retry, we didn't have an abrupt server-initiated exception.
    373     boolean sslFailure =
    374         e instanceof SSLHandshakeException && e.getCause() instanceof CertificateException;
    375     boolean protocolFailure = e instanceof ProtocolException;
    376     return !sslFailure && !protocolFailure;
    377   }
    378 
    379   /**
    380    * Returns the route used to retrieve the response. Null if we haven't
    381    * connected yet, or if no connection was necessary.
    382    */
    383   public Route getRoute() {
    384     return route;
    385   }
    386 
    387   private void maybeCache() throws IOException {
    388     OkResponseCache responseCache = client.getOkResponseCache();
    389     if (responseCache == null) return;
    390 
    391     // Should we cache this response for this request?
    392     if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
    393       responseCache.maybeRemove(networkRequest);
    394       return;
    395     }
    396 
    397     // Offer this request to the cache.
    398     storeRequest = responseCache.put(stripBody(userResponse));
    399   }
    400 
    401   /**
    402    * Configure the socket connection to be either pooled or closed when it is
    403    * either exhausted or closed. If it is unneeded when this is called, it will
    404    * be released immediately.
    405    */
    406   public final void releaseConnection() throws IOException {
    407     if (transport != null && connection != null) {
    408       transport.releaseConnectionOnIdle();
    409     }
    410     connection = null;
    411   }
    412 
    413   /**
    414    * Immediately closes the socket connection if it's currently held by this
    415    * engine. Use this to interrupt an in-flight request from any thread. It's
    416    * the caller's responsibility to close the request body and response body
    417    * streams; otherwise resources may be leaked.
    418    */
    419   public final void disconnect() throws IOException {
    420     if (transport != null) {
    421       transport.disconnect(this);
    422     }
    423   }
    424 
    425   /**
    426    * Release any resources held by this engine. If a connection is still held by
    427    * this engine, it is returned.
    428    */
    429   public final Connection close() {
    430     if (bufferedRequestBody != null) {
    431       // This also closes the wrapped requestBodyOut.
    432       closeQuietly(bufferedRequestBody);
    433     } else if (requestBodyOut != null) {
    434       closeQuietly(requestBodyOut);
    435     }
    436 
    437     // If this engine never achieved a response body, its connection cannot be reused.
    438     if (responseBody == null) {
    439       closeQuietly(connection);
    440       connection = null;
    441       return null;
    442     }
    443 
    444     // Close the response body. This will recycle the connection if it is eligible.
    445     closeQuietly(responseBody);
    446 
    447     // Clear the buffer held by the response body input stream adapter.
    448     closeQuietly(responseBodyBytes);
    449 
    450     // Close the connection if it cannot be reused.
    451     if (transport != null && !transport.canReuseConnection()) {
    452       closeQuietly(connection);
    453       connection = null;
    454       return null;
    455     }
    456 
    457     // Prevent this engine from disconnecting a connection it no longer owns.
    458     if (connection != null && !connection.clearOwner()) {
    459       connection = null;
    460     }
    461 
    462     Connection result = connection;
    463     connection = null;
    464     return result;
    465   }
    466 
    467   /**
    468    * Initialize the response content stream from the response transfer source.
    469    * These two sources are the same unless we're doing transparent gzip, in
    470    * which case the content source is decompressed.
    471    *
    472    * <p>Whenever we do transparent gzip we also strip the corresponding headers.
    473    * We strip the Content-Encoding header to prevent the application from
    474    * attempting to double decompress. We strip the Content-Length header because
    475    * it is the length of the compressed content, but the application is only
    476    * interested in the length of the uncompressed content.
    477    *
    478    * <p>This method should only be used for non-empty response bodies. Response
    479    * codes like "304 Not Modified" can include "Content-Encoding: gzip" without
    480    * a response body and we will crash if we attempt to decompress the zero-byte
    481    * source.
    482    */
    483   private void initContentStream(Source transferSource) throws IOException {
    484     responseTransferSource = transferSource;
    485     if (transparentGzip && "gzip".equalsIgnoreCase(userResponse.header("Content-Encoding"))) {
    486       userResponse = userResponse.newBuilder()
    487           .removeHeader("Content-Encoding")
    488           .removeHeader("Content-Length")
    489           .build();
    490       responseBody = new GzipSource(transferSource);
    491     } else {
    492       responseBody = transferSource;
    493     }
    494   }
    495 
    496   /**
    497    * Returns true if the response must have a (possibly 0-length) body.
    498    * See RFC 2616 section 4.3.
    499    */
    500   public final boolean hasResponseBody() {
    501     // HEAD requests never yield a body regardless of the response headers.
    502     if (userRequest.method().equals("HEAD")) {
    503       return false;
    504     }
    505 
    506     int responseCode = userResponse.code();
    507     if ((responseCode < HTTP_CONTINUE || responseCode >= 200)
    508         && responseCode != HTTP_NO_CONTENT
    509         && responseCode != HTTP_NOT_MODIFIED) {
    510       return true;
    511     }
    512 
    513     // If the Content-Length or Transfer-Encoding headers disagree with the
    514     // response code, the response is malformed. For best compatibility, we
    515     // honor the headers.
    516     if (OkHeaders.contentLength(networkResponse) != -1
    517         || "chunked".equalsIgnoreCase(networkResponse.header("Transfer-Encoding"))) {
    518       return true;
    519     }
    520 
    521     return false;
    522   }
    523 
    524   /**
    525    * Populates request with defaults and cookies.
    526    *
    527    * <p>This client doesn't specify a default {@code Accept} header because it
    528    * doesn't know what content types the application is interested in.
    529    */
    530   private Request networkRequest(Request request) throws IOException {
    531     Request.Builder result = request.newBuilder();
    532 
    533     if (request.getUserAgent() == null) {
    534       result.setUserAgent(getDefaultUserAgent());
    535     }
    536 
    537     if (request.header("Host") == null) {
    538       result.header("Host", hostHeader(request.url()));
    539     }
    540 
    541     if ((connection == null || connection.getHttpMinorVersion() != 0)
    542         && request.header("Connection") == null) {
    543       result.header("Connection", "Keep-Alive");
    544     }
    545 
    546     if (request.header("Accept-Encoding") == null) {
    547       transparentGzip = true;
    548       result.header("Accept-Encoding", "gzip");
    549     }
    550 
    551     if (hasRequestBody() && request.header("Content-Type") == null) {
    552       result.header("Content-Type", "application/x-www-form-urlencoded");
    553     }
    554 
    555     CookieHandler cookieHandler = client.getCookieHandler();
    556     if (cookieHandler != null) {
    557       // Capture the request headers added so far so that they can be offered to the CookieHandler.
    558       // This is mostly to stay close to the RI; it is unlikely any of the headers above would
    559       // affect cookie choice besides "Host".
    560       Map<String, List<String>> headers = OkHeaders.toMultimap(result.build().headers(), null);
    561 
    562       Map<String, List<String>> cookies = cookieHandler.get(request.uri(), headers);
    563 
    564       // Add any new cookies to the request.
    565       OkHeaders.addCookies(result, cookies);
    566     }
    567 
    568     return result.build();
    569   }
    570 
    571   public static String getDefaultUserAgent() {
    572     String agent = System.getProperty("http.agent");
    573     return agent != null ? agent : ("Java" + System.getProperty("java.version"));
    574   }
    575 
    576   public static String hostHeader(URL url) {
    577     return getEffectivePort(url) != getDefaultPort(url.getProtocol())
    578         ? url.getHost() + ":" + url.getPort()
    579         : url.getHost();
    580   }
    581 
    582   /**
    583    * Flushes the remaining request header and body, parses the HTTP response
    584    * headers and starts reading the HTTP response body if it exists.
    585    */
    586   public final void readResponse() throws IOException {
    587     if (userResponse != null) {
    588       return; // Already ready.
    589     }
    590     if (networkRequest == null && cacheResponse == null) {
    591       throw new IllegalStateException("call sendRequest() first!");
    592     }
    593     if (networkRequest == null) {
    594       return; // No network response to read.
    595     }
    596 
    597     // Flush the request body if there's data outstanding.
    598     if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
    599       bufferedRequestBody.flush();
    600     }
    601 
    602     if (sentRequestMillis == -1) {
    603       if (OkHeaders.contentLength(networkRequest) == -1
    604           && requestBodyOut instanceof RetryableSink) {
    605         // We might not learn the Content-Length until the request body has been buffered.
    606         long contentLength = ((RetryableSink) requestBodyOut).contentLength();
    607         networkRequest = networkRequest.newBuilder()
    608             .header("Content-Length", Long.toString(contentLength))
    609             .build();
    610       }
    611       transport.writeRequestHeaders(networkRequest);
    612     }
    613 
    614     if (requestBodyOut != null) {
    615       if (bufferedRequestBody != null) {
    616         // This also closes the wrapped requestBodyOut.
    617         bufferedRequestBody.close();
    618       } else {
    619         requestBodyOut.close();
    620       }
    621       if (requestBodyOut instanceof RetryableSink) {
    622         transport.writeRequestBody((RetryableSink) requestBodyOut);
    623       }
    624     }
    625 
    626     transport.flushRequest();
    627 
    628     networkResponse = transport.readResponseHeaders()
    629         .request(networkRequest)
    630         .handshake(connection.getHandshake())
    631         .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
    632         .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
    633         .setResponseSource(responseSource)
    634         .build();
    635     connection.setHttpMinorVersion(networkResponse.httpMinorVersion());
    636     receiveHeaders(networkResponse.headers());
    637 
    638     if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
    639       if (cacheResponse.validate(networkResponse)) {
    640         userResponse = cacheResponse.newBuilder()
    641             .request(userRequest)
    642             .priorResponse(stripBody(priorResponse))
    643             .headers(combine(cacheResponse.headers(), networkResponse.headers()))
    644             .cacheResponse(stripBody(cacheResponse))
    645             .networkResponse(stripBody(networkResponse))
    646             .build();
    647         transport.emptyTransferStream();
    648         releaseConnection();
    649 
    650         // Update the cache after combining headers but before stripping the
    651         // Content-Encoding header (as performed by initContentStream()).
    652         OkResponseCache responseCache = client.getOkResponseCache();
    653         responseCache.trackConditionalCacheHit();
    654         responseCache.update(cacheResponse, stripBody(userResponse));
    655         if (cacheResponse.body() != null) {
    656           initContentStream(cacheResponse.body().source());
    657         }
    658 
    659         return;
    660       } else {
    661         closeQuietly(cacheResponse.body());
    662       }
    663     }
    664 
    665     userResponse = networkResponse.newBuilder()
    666         .request(userRequest)
    667         .priorResponse(stripBody(priorResponse))
    668         .cacheResponse(stripBody(cacheResponse))
    669         .networkResponse(stripBody(networkResponse))
    670         .build();
    671 
    672     if (!hasResponseBody()) {
    673       // Don't call initContentStream() when the response doesn't have any content.
    674       responseTransferSource = transport.getTransferStream(storeRequest);
    675       responseBody = responseTransferSource;
    676       return;
    677     }
    678 
    679     maybeCache();
    680     initContentStream(transport.getTransferStream(storeRequest));
    681   }
    682 
    683   /**
    684    * Combines cached headers with a network headers as defined by RFC 2616,
    685    * 13.5.3.
    686    */
    687   private static Headers combine(Headers cachedHeaders, Headers networkHeaders) throws IOException {
    688     Headers.Builder result = new Headers.Builder();
    689 
    690     for (int i = 0; i < cachedHeaders.size(); i++) {
    691       String fieldName = cachedHeaders.name(i);
    692       String value = cachedHeaders.value(i);
    693       if ("Warning".equals(fieldName) && value.startsWith("1")) {
    694         continue; // drop 100-level freshness warnings
    695       }
    696       if (!isEndToEnd(fieldName) || networkHeaders.get(fieldName) == null) {
    697         result.add(fieldName, value);
    698       }
    699     }
    700 
    701     for (int i = 0; i < networkHeaders.size(); i++) {
    702       String fieldName = networkHeaders.name(i);
    703       if (isEndToEnd(fieldName)) {
    704         result.add(fieldName, networkHeaders.value(i));
    705       }
    706     }
    707 
    708     return result.build();
    709   }
    710 
    711   /**
    712    * Returns true if {@code fieldName} is an end-to-end HTTP header, as
    713    * defined by RFC 2616, 13.5.1.
    714    */
    715   private static boolean isEndToEnd(String fieldName) {
    716     return !"Connection".equalsIgnoreCase(fieldName)
    717         && !"Keep-Alive".equalsIgnoreCase(fieldName)
    718         && !"Proxy-Authenticate".equalsIgnoreCase(fieldName)
    719         && !"Proxy-Authorization".equalsIgnoreCase(fieldName)
    720         && !"TE".equalsIgnoreCase(fieldName)
    721         && !"Trailers".equalsIgnoreCase(fieldName)
    722         && !"Transfer-Encoding".equalsIgnoreCase(fieldName)
    723         && !"Upgrade".equalsIgnoreCase(fieldName);
    724   }
    725 
    726   private TunnelRequest getTunnelConfig() {
    727     if (!userRequest.isHttps()) return null;
    728 
    729     String userAgent = userRequest.getUserAgent();
    730     if (userAgent == null) userAgent = getDefaultUserAgent();
    731 
    732     URL url = userRequest.url();
    733     return new TunnelRequest(url.getHost(), getEffectivePort(url), userAgent,
    734         userRequest.getProxyAuthorization());
    735   }
    736 
    737   public void receiveHeaders(Headers headers) throws IOException {
    738     CookieHandler cookieHandler = client.getCookieHandler();
    739     if (cookieHandler != null) {
    740       cookieHandler.put(userRequest.uri(), OkHeaders.toMultimap(headers, null));
    741     }
    742   }
    743 }
    744