Home | History | Annotate | Download | only in huc
      1 /*
      2  * Copyright (C) 2014 Square, Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.squareup.okhttp.internal.huc;
     18 
     19 import com.squareup.okhttp.AbstractResponseCache;
     20 import com.squareup.okhttp.AndroidInternal;
     21 import com.squareup.okhttp.AndroidShimResponseCache;
     22 import com.squareup.okhttp.Headers;
     23 import com.squareup.okhttp.OkHttpClient;
     24 import com.squareup.okhttp.OkUrlFactory;
     25 import com.squareup.okhttp.internal.Internal;
     26 import com.squareup.okhttp.internal.SslContextBuilder;
     27 import com.squareup.okhttp.mockwebserver.MockResponse;
     28 import com.squareup.okhttp.mockwebserver.MockWebServer;
     29 import com.squareup.okhttp.mockwebserver.RecordedRequest;
     30 import com.squareup.okhttp.testing.RecordingHostnameVerifier;
     31 import java.io.BufferedReader;
     32 import java.io.ByteArrayInputStream;
     33 import java.io.FileNotFoundException;
     34 import java.io.IOException;
     35 import java.io.InputStream;
     36 import java.io.InputStreamReader;
     37 import java.io.OutputStream;
     38 import java.net.CacheRequest;
     39 import java.net.CacheResponse;
     40 import java.net.CookieManager;
     41 import java.net.HttpCookie;
     42 import java.net.HttpURLConnection;
     43 import java.net.ProtocolException;
     44 import java.net.ResponseCache;
     45 import java.net.SecureCacheResponse;
     46 import java.net.URI;
     47 import java.net.URISyntaxException;
     48 import java.net.URL;
     49 import java.net.URLConnection;
     50 import java.nio.charset.StandardCharsets;
     51 import java.security.Principal;
     52 import java.security.cert.Certificate;
     53 import java.text.DateFormat;
     54 import java.text.SimpleDateFormat;
     55 import java.util.ArrayList;
     56 import java.util.Arrays;
     57 import java.util.Date;
     58 import java.util.HashMap;
     59 import java.util.List;
     60 import java.util.Locale;
     61 import java.util.Map;
     62 import java.util.TimeZone;
     63 import java.util.concurrent.TimeUnit;
     64 import java.util.concurrent.atomic.AtomicBoolean;
     65 import java.util.concurrent.atomic.AtomicInteger;
     66 import java.util.concurrent.atomic.AtomicReference;
     67 import javax.net.ssl.HostnameVerifier;
     68 import javax.net.ssl.HttpsURLConnection;
     69 import javax.net.ssl.SSLContext;
     70 import okio.Buffer;
     71 import okio.BufferedSink;
     72 import okio.GzipSink;
     73 import okio.Okio;
     74 import org.junit.After;
     75 import org.junit.Before;
     76 import org.junit.Rule;
     77 import org.junit.Test;
     78 import org.junit.rules.TemporaryFolder;
     79 
     80 import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
     81 import static org.junit.Assert.assertEquals;
     82 import static org.junit.Assert.assertFalse;
     83 import static org.junit.Assert.assertNotNull;
     84 import static org.junit.Assert.assertNull;
     85 import static org.junit.Assert.assertTrue;
     86 import static org.junit.Assert.fail;
     87 
     88 /**
     89  * Tests the interaction between OkHttp and {@link ResponseCache}.
     90  * Based on com.squareup.okhttp.CacheTest with changes for ResponseCache and HttpURLConnection.
     91  */
     92 public final class ResponseCacheTest {
     93   @Rule public TemporaryFolder cacheRule = new TemporaryFolder();
     94   @Rule public MockWebServer server = new MockWebServer();
     95   @Rule public MockWebServer server2 = new MockWebServer();
     96 
     97   private HostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
     98   private SSLContext sslContext = SslContextBuilder.localhost();
     99   private OkHttpClient client;
    100   private ResponseCache cache;
    101   private CookieManager cookieManager;
    102 
    103   @Before public void setUp() throws Exception {
    104     server.setProtocolNegotiationEnabled(false);
    105 
    106     client = new OkHttpClient();
    107 
    108     cache = AndroidShimResponseCache.create(cacheRule.getRoot(), 10 * 1024 * 1024);
    109     AndroidInternal.setResponseCache(new OkUrlFactory(client), cache);
    110 
    111     cookieManager = new CookieManager();
    112     CookieManager.setDefault(cookieManager);
    113   }
    114 
    115   @After public void tearDown() throws Exception {
    116     CookieManager.setDefault(null);
    117     ResponseCache.setDefault(null);
    118   }
    119 
    120   private HttpURLConnection openConnection(URL url) {
    121     return new OkUrlFactory(client).open(url);
    122   }
    123 
    124   /**
    125    * Test that response caching is consistent with the RI and the spec.
    126    * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4
    127    */
    128   @Test public void responseCachingByResponseCode() throws Exception {
    129     // Test each documented HTTP/1.1 code, plus the first unused value in each range.
    130     // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
    131 
    132     // We can't test 100 because it's not really a response.
    133     // assertCached(false, 100);
    134     assertCached(false, 101);
    135     assertCached(false, 102);
    136     assertCached(true,  200);
    137     assertCached(false, 201);
    138     assertCached(false, 202);
    139     assertCached(true,  203);
    140     assertCached(true,  204);
    141     assertCached(false, 205);
    142     assertCached(false, 206); //Electing to not cache partial responses
    143     assertCached(false, 207);
    144     assertCached(true,  300);
    145     assertCached(true,  301);
    146     assertCached(true,  302);
    147     assertCached(false, 303);
    148     assertCached(false, 304);
    149     assertCached(false, 305);
    150     assertCached(false, 306);
    151     assertCached(true,  307);
    152     assertCached(true,  308);
    153     assertCached(false, 400);
    154     assertCached(false, 401);
    155     assertCached(false, 402);
    156     assertCached(false, 403);
    157     assertCached(true,  404);
    158     assertCached(true,  405);
    159     assertCached(false, 406);
    160     assertCached(false, 408);
    161     assertCached(false, 409);
    162     // the HTTP spec permits caching 410s, but the RI doesn't.
    163     assertCached(true,  410);
    164     assertCached(false, 411);
    165     assertCached(false, 412);
    166     assertCached(false, 413);
    167     assertCached(true,  414);
    168     assertCached(false, 415);
    169     assertCached(false, 416);
    170     assertCached(false, 417);
    171     assertCached(false, 418);
    172 
    173     assertCached(false, 500);
    174     assertCached(true,  501);
    175     assertCached(false, 502);
    176     assertCached(false, 503);
    177     assertCached(false, 504);
    178     assertCached(false, 505);
    179     assertCached(false, 506);
    180   }
    181 
    182   private void assertCached(boolean shouldPut, int responseCode) throws Exception {
    183     server = new MockWebServer();
    184     MockResponse mockResponse = new MockResponse()
    185         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
    186         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    187         .setResponseCode(responseCode)
    188         .setBody("ABCDE")
    189         .addHeader("WWW-Authenticate: challenge");
    190     if (responseCode == HttpURLConnection.HTTP_PROXY_AUTH) {
    191       mockResponse.addHeader("Proxy-Authenticate: Basic realm=\"protected area\"");
    192     } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
    193       mockResponse.addHeader("WWW-Authenticate: Basic realm=\"protected area\"");
    194     }
    195     server.enqueue(mockResponse);
    196     server.start();
    197 
    198     URL url = server.getUrl("/");
    199     HttpURLConnection connection = openConnection(url);
    200     assertEquals(responseCode, connection.getResponseCode());
    201 
    202     // Exhaust the content stream.
    203     readAscii(connection);
    204 
    205     CacheResponse cached = cache.get(url.toURI(), "GET", null);
    206     if (shouldPut) {
    207       assertNotNull(Integer.toString(responseCode), cached);
    208     } else {
    209       assertNull(Integer.toString(responseCode), cached);
    210     }
    211     server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers
    212   }
    213 
    214   @Test public void responseCachingAndInputStreamSkipWithFixedLength() throws IOException {
    215     testResponseCaching(TransferKind.FIXED_LENGTH);
    216   }
    217 
    218   @Test public void responseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException {
    219     testResponseCaching(TransferKind.CHUNKED);
    220   }
    221 
    222   @Test public void responseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException {
    223     testResponseCaching(TransferKind.END_OF_STREAM);
    224   }
    225 
    226   /**
    227    * HttpURLConnection.getInputStream().skip(long) causes ResponseCache corruption
    228    * http://code.google.com/p/android/issues/detail?id=8175
    229    */
    230   private void testResponseCaching(TransferKind transferKind) throws IOException {
    231     MockResponse mockResponse = new MockResponse()
    232         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
    233         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    234         .setStatus("HTTP/1.1 200 Fantastic");
    235     transferKind.setBody(mockResponse, "I love puppies but hate spiders", 1);
    236     server.enqueue(mockResponse);
    237 
    238     // Make sure that calling skip() doesn't omit bytes from the cache.
    239     HttpURLConnection urlConnection = openConnection(server.getUrl("/"));
    240     InputStream in = urlConnection.getInputStream();
    241     assertEquals("I love ", readAscii(urlConnection, "I love ".length()));
    242     reliableSkip(in, "puppies but hate ".length());
    243     assertEquals("spiders", readAscii(urlConnection, "spiders".length()));
    244     assertEquals(-1, in.read());
    245     in.close();
    246 
    247     urlConnection = openConnection(server.getUrl("/")); // cached!
    248     in = urlConnection.getInputStream();
    249     assertEquals("I love puppies but hate spiders",
    250         readAscii(urlConnection, "I love puppies but hate spiders".length()));
    251     assertEquals(200, urlConnection.getResponseCode());
    252     assertEquals("Fantastic", urlConnection.getResponseMessage());
    253 
    254     assertEquals(-1, in.read());
    255     in.close();
    256   }
    257 
    258   @Test public void secureResponseCaching() throws IOException {
    259     server.useHttps(sslContext.getSocketFactory(), false);
    260     server.enqueue(new MockResponse()
    261         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
    262         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    263         .setBody("ABC"));
    264 
    265     HttpsURLConnection c1 = (HttpsURLConnection) openConnection(server.getUrl("/"));
    266     c1.setSSLSocketFactory(sslContext.getSocketFactory());
    267     c1.setHostnameVerifier(hostnameVerifier);
    268     assertEquals("ABC", readAscii(c1));
    269 
    270     // OpenJDK 6 fails on this line, complaining that the connection isn't open yet
    271     String suite = c1.getCipherSuite();
    272     List<Certificate> localCerts = toListOrNull(c1.getLocalCertificates());
    273     List<Certificate> serverCerts = toListOrNull(c1.getServerCertificates());
    274     Principal peerPrincipal = c1.getPeerPrincipal();
    275     Principal localPrincipal = c1.getLocalPrincipal();
    276 
    277     HttpsURLConnection c2 = (HttpsURLConnection) openConnection(server.getUrl("/")); // cached!
    278     c2.setSSLSocketFactory(sslContext.getSocketFactory());
    279     c2.setHostnameVerifier(hostnameVerifier);
    280     assertEquals("ABC", readAscii(c2));
    281 
    282     assertEquals(suite, c2.getCipherSuite());
    283     assertEquals(localCerts, toListOrNull(c2.getLocalCertificates()));
    284     assertEquals(serverCerts, toListOrNull(c2.getServerCertificates()));
    285     assertEquals(peerPrincipal, c2.getPeerPrincipal());
    286     assertEquals(localPrincipal, c2.getLocalPrincipal());
    287   }
    288 
    289   @Test public void responseCachingAndRedirects() throws Exception {
    290     server.enqueue(new MockResponse()
    291         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
    292         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    293         .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
    294         .addHeader("Location: /foo"));
    295     server.enqueue(new MockResponse()
    296         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
    297         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    298         .setBody("ABC"));
    299     server.enqueue(new MockResponse()
    300         .setBody("DEF"));
    301 
    302     HttpURLConnection connection = openConnection(server.getUrl("/"));
    303     assertEquals("ABC", readAscii(connection));
    304 
    305     connection = openConnection(server.getUrl("/")); // cached!
    306     assertEquals("ABC", readAscii(connection));
    307   }
    308 
    309   @Test public void redirectToCachedResult() throws Exception {
    310     server.enqueue(new MockResponse()
    311         .addHeader("Cache-Control: max-age=60")
    312         .setBody("ABC"));
    313     server.enqueue(new MockResponse()
    314         .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
    315         .addHeader("Location: /foo"));
    316     server.enqueue(new MockResponse()
    317         .setBody("DEF"));
    318 
    319     assertEquals("ABC", readAscii(openConnection(server.getUrl("/foo"))));
    320     RecordedRequest request1 = server.takeRequest();
    321     assertEquals("GET /foo HTTP/1.1", request1.getRequestLine());
    322     assertEquals(0, request1.getSequenceNumber());
    323 
    324     assertEquals("ABC", readAscii(openConnection(server.getUrl("/bar"))));
    325     RecordedRequest request2 = server.takeRequest();
    326     assertEquals("GET /bar HTTP/1.1", request2.getRequestLine());
    327     assertEquals(1, request2.getSequenceNumber());
    328 
    329     // an unrelated request should reuse the pooled connection
    330     assertEquals("DEF", readAscii(openConnection(server.getUrl("/baz"))));
    331     RecordedRequest request3 = server.takeRequest();
    332     assertEquals("GET /baz HTTP/1.1", request3.getRequestLine());
    333     assertEquals(2, request3.getSequenceNumber());
    334   }
    335 
    336   @Test public void secureResponseCachingAndRedirects() throws IOException {
    337     server.useHttps(sslContext.getSocketFactory(), false);
    338     server.enqueue(new MockResponse()
    339         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
    340         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    341         .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
    342         .addHeader("Location: /foo"));
    343     server.enqueue(new MockResponse()
    344         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
    345         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    346         .setBody("ABC"));
    347     server.enqueue(new MockResponse()
    348         .setBody("DEF"));
    349 
    350     client.setSslSocketFactory(sslContext.getSocketFactory());
    351     client.setHostnameVerifier(hostnameVerifier);
    352 
    353     HttpsURLConnection connection1 = (HttpsURLConnection) openConnection(server.getUrl("/"));
    354     assertEquals("ABC", readAscii(connection1));
    355     assertNotNull(connection1.getCipherSuite());
    356 
    357     // Cached!
    358     HttpsURLConnection connection2 = (HttpsURLConnection) openConnection(server.getUrl("/"));
    359     assertEquals("ABC", readAscii(connection2));
    360     assertNotNull(connection2.getCipherSuite());
    361 
    362     assertEquals(connection1.getCipherSuite(), connection2.getCipherSuite());
    363   }
    364 
    365   /**
    366    * We've had bugs where caching and cross-protocol redirects yield class
    367    * cast exceptions internal to the cache because we incorrectly assumed that
    368    * HttpsURLConnection was always HTTPS and HttpURLConnection was always HTTP;
    369    * in practice redirects mean that each can do either.
    370    *
    371    * https://github.com/square/okhttp/issues/214
    372    */
    373   @Test public void secureResponseCachingAndProtocolRedirects() throws IOException {
    374     server2.useHttps(sslContext.getSocketFactory(), false);
    375     server2.enqueue(new MockResponse()
    376         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
    377         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    378         .setBody("ABC"));
    379     server2.enqueue(new MockResponse()
    380         .setBody("DEF"));
    381 
    382     server.enqueue(new MockResponse()
    383         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
    384         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    385         .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
    386         .addHeader("Location: " + server2.getUrl("/")));
    387 
    388     client.setSslSocketFactory(sslContext.getSocketFactory());
    389     client.setHostnameVerifier(hostnameVerifier);
    390 
    391     HttpURLConnection connection1 = openConnection(server.getUrl("/"));
    392     assertEquals("ABC", readAscii(connection1));
    393 
    394     // Cached!
    395     HttpURLConnection connection2 = openConnection(server.getUrl("/"));
    396     assertEquals("ABC", readAscii(connection2));
    397   }
    398 
    399   @Test public void foundCachedWithExpiresHeader() throws Exception {
    400     temporaryRedirectCachedWithCachingHeader(302, "Expires", formatDate(1, TimeUnit.HOURS));
    401   }
    402 
    403   @Test public void foundCachedWithCacheControlHeader() throws Exception {
    404     temporaryRedirectCachedWithCachingHeader(302, "Cache-Control", "max-age=60");
    405   }
    406 
    407   @Test public void temporaryRedirectCachedWithExpiresHeader() throws Exception {
    408     temporaryRedirectCachedWithCachingHeader(307, "Expires", formatDate(1, TimeUnit.HOURS));
    409   }
    410 
    411   @Test public void temporaryRedirectCachedWithCacheControlHeader() throws Exception {
    412     temporaryRedirectCachedWithCachingHeader(307, "Cache-Control", "max-age=60");
    413   }
    414 
    415   @Test public void foundNotCachedWithoutCacheHeader() throws Exception {
    416     temporaryRedirectNotCachedWithoutCachingHeader(302);
    417   }
    418 
    419   @Test public void temporaryRedirectNotCachedWithoutCacheHeader() throws Exception {
    420     temporaryRedirectNotCachedWithoutCachingHeader(307);
    421   }
    422 
    423   private void temporaryRedirectCachedWithCachingHeader(
    424       int responseCode, String headerName, String headerValue) throws Exception {
    425     server.enqueue(new MockResponse()
    426         .setResponseCode(responseCode)
    427         .addHeader(headerName, headerValue)
    428         .addHeader("Location", "/a"));
    429     server.enqueue(new MockResponse()
    430         .addHeader(headerName, headerValue)
    431         .setBody("a"));
    432     server.enqueue(new MockResponse()
    433         .setBody("b"));
    434     server.enqueue(new MockResponse()
    435         .setBody("c"));
    436 
    437     URL url = server.getUrl("/");
    438     assertEquals("a", readAscii(openConnection(url)));
    439     assertEquals("a", readAscii(openConnection(url)));
    440   }
    441 
    442   private void temporaryRedirectNotCachedWithoutCachingHeader(int responseCode) throws Exception {
    443     server.enqueue(new MockResponse()
    444         .setResponseCode(responseCode)
    445         .addHeader("Location", "/a"));
    446     server.enqueue(new MockResponse()
    447         .setBody("a"));
    448     server.enqueue(new MockResponse()
    449         .setBody("b"));
    450 
    451     URL url = server.getUrl("/");
    452     assertEquals("a", readAscii(openConnection(url)));
    453     assertEquals("b", readAscii(openConnection(url)));
    454   }
    455 
    456   @Test public void serverDisconnectsPrematurelyWithContentLengthHeader() throws IOException {
    457     testServerPrematureDisconnect(TransferKind.FIXED_LENGTH);
    458   }
    459 
    460   @Test public void serverDisconnectsPrematurelyWithChunkedEncoding() throws IOException {
    461     testServerPrematureDisconnect(TransferKind.CHUNKED);
    462   }
    463 
    464   @Test public void serverDisconnectsPrematurelyWithNoLengthHeaders() throws IOException {
    465     // Intentionally empty. This case doesn't make sense because there's no
    466     // such thing as a premature disconnect when the disconnect itself
    467     // indicates the end of the data stream.
    468   }
    469 
    470   private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException {
    471     MockResponse response = new MockResponse();
    472     transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16);
    473     server.enqueue(truncateViolently(response, 16));
    474     server.enqueue(new MockResponse()
    475         .setBody("Request #2"));
    476 
    477     BufferedReader reader = new BufferedReader(
    478         new InputStreamReader(openConnection(server.getUrl("/")).getInputStream()));
    479     assertEquals("ABCDE", reader.readLine());
    480     try {
    481       reader.readLine();
    482       fail("This implementation silently ignored a truncated HTTP body.");
    483     } catch (IOException expected) {
    484     } finally {
    485       reader.close();
    486     }
    487 
    488     URLConnection connection = openConnection(server.getUrl("/"));
    489     assertEquals("Request #2", readAscii(connection));
    490   }
    491 
    492   @Test public void clientPrematureDisconnectWithContentLengthHeader() throws IOException {
    493     testClientPrematureDisconnect(TransferKind.FIXED_LENGTH);
    494   }
    495 
    496   @Test public void clientPrematureDisconnectWithChunkedEncoding() throws IOException {
    497     testClientPrematureDisconnect(TransferKind.CHUNKED);
    498   }
    499 
    500   @Test public void clientPrematureDisconnectWithNoLengthHeaders() throws IOException {
    501     testClientPrematureDisconnect(TransferKind.END_OF_STREAM);
    502   }
    503 
    504   private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException {
    505     // Setting a low transfer speed ensures that stream discarding will time out.
    506     MockResponse response = new MockResponse()
    507         .throttleBody(6, 1, TimeUnit.SECONDS);
    508     transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024);
    509     server.enqueue(response);
    510     server.enqueue(new MockResponse()
    511         .setBody("Request #2"));
    512 
    513     URLConnection connection = openConnection(server.getUrl("/"));
    514     InputStream in = connection.getInputStream();
    515     assertEquals("ABCDE", readAscii(connection, 5));
    516     in.close();
    517     try {
    518       in.read();
    519       fail("Expected an IOException because the stream is closed.");
    520     } catch (IOException expected) {
    521     }
    522 
    523     connection = openConnection(server.getUrl("/"));
    524     assertEquals("Request #2", readAscii(connection));
    525   }
    526 
    527   @Test public void defaultExpirationDateFullyCachedForLessThan24Hours() throws Exception {
    528     //      last modified: 105 seconds ago
    529     //             served:   5 seconds ago
    530     //   default lifetime: (105 - 5) / 10 = 10 seconds
    531     //            expires:  10 seconds from served date = 5 seconds from now
    532     server.enqueue(new MockResponse()
    533         .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS))
    534         .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS))
    535         .setBody("A"));
    536 
    537     URL url = server.getUrl("/");
    538     assertEquals("A", readAscii(openConnection(url)));
    539     URLConnection connection = openConnection(url);
    540     assertEquals("A", readAscii(connection));
    541     assertNull(connection.getHeaderField("Warning"));
    542   }
    543 
    544   @Test public void defaultExpirationDateConditionallyCached() throws Exception {
    545     //      last modified: 115 seconds ago
    546     //             served:  15 seconds ago
    547     //   default lifetime: (115 - 15) / 10 = 10 seconds
    548     //            expires:  10 seconds from served date = 5 seconds ago
    549     String lastModifiedDate = formatDate(-115, TimeUnit.SECONDS);
    550     RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
    551         .addHeader("Last-Modified: " + lastModifiedDate)
    552         .addHeader("Date: " + formatDate(-15, TimeUnit.SECONDS)));
    553     assertEquals(lastModifiedDate, conditionalRequest.getHeader("If-Modified-Since"));
    554   }
    555 
    556   @Test public void defaultExpirationDateFullyCachedForMoreThan24Hours() throws Exception {
    557     //      last modified: 105 days ago
    558     //             served:   5 days ago
    559     //   default lifetime: (105 - 5) / 10 = 10 days
    560     //            expires:  10 days from served date = 5 days from now
    561     server.enqueue(new MockResponse()
    562         .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.DAYS))
    563         .addHeader("Date: " + formatDate(-5, TimeUnit.DAYS))
    564         .setBody("A"));
    565 
    566     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
    567     URLConnection connection = openConnection(server.getUrl("/"));
    568     assertEquals("A", readAscii(connection));
    569     assertEquals("113 HttpURLConnection \"Heuristic expiration\"",
    570         connection.getHeaderField("Warning"));
    571   }
    572 
    573   @Test public void noDefaultExpirationForUrlsWithQueryString() throws Exception {
    574     server.enqueue(new MockResponse()
    575         .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS))
    576         .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS))
    577         .setBody("A"));
    578     server.enqueue(new MockResponse()
    579         .setBody("B"));
    580 
    581     URL url = server.getUrl("/?foo=bar");
    582     assertEquals("A", readAscii(openConnection(url)));
    583     assertEquals("B", readAscii(openConnection(url)));
    584   }
    585 
    586   @Test public void expirationDateInThePastWithLastModifiedHeader() throws Exception {
    587     String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
    588     RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
    589         .addHeader("Last-Modified: " + lastModifiedDate)
    590         .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
    591     assertEquals(lastModifiedDate, conditionalRequest.getHeader("If-Modified-Since"));
    592   }
    593 
    594   @Test public void expirationDateInThePastWithNoLastModifiedHeader() throws Exception {
    595     assertNotCached(new MockResponse()
    596         .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
    597   }
    598 
    599   @Test public void expirationDateInTheFuture() throws Exception {
    600     assertFullyCached(new MockResponse()
    601         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
    602   }
    603 
    604   @Test public void maxAgePreferredWithMaxAgeAndExpires() throws Exception {
    605     assertFullyCached(new MockResponse()
    606         .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
    607         .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))
    608         .addHeader("Cache-Control: max-age=60"));
    609   }
    610 
    611   @Test public void maxAgeInThePastWithDateAndLastModifiedHeaders() throws Exception {
    612     String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
    613     RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
    614         .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS))
    615         .addHeader("Last-Modified: " + lastModifiedDate)
    616         .addHeader("Cache-Control: max-age=60"));
    617     assertEquals(lastModifiedDate, conditionalRequest.getHeader("If-Modified-Since"));
    618   }
    619 
    620   @Test public void maxAgeInThePastWithDateHeaderButNoLastModifiedHeader() throws Exception {
    621     // Chrome interprets max-age relative to the local clock. Both our cache
    622     // and Firefox both use the earlier of the local and server's clock.
    623     assertNotCached(new MockResponse()
    624         .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS))
    625         .addHeader("Cache-Control: max-age=60"));
    626   }
    627 
    628   @Test public void maxAgeInTheFutureWithDateHeader() throws Exception {
    629     assertFullyCached(new MockResponse()
    630         .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
    631         .addHeader("Cache-Control: max-age=60"));
    632   }
    633 
    634   @Test public void maxAgeInTheFutureWithNoDateHeader() throws Exception {
    635     assertFullyCached(new MockResponse()
    636         .addHeader("Cache-Control: max-age=60"));
    637   }
    638 
    639   @Test public void maxAgeWithLastModifiedButNoServedDate() throws Exception {
    640     assertFullyCached(new MockResponse()
    641         .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
    642         .addHeader("Cache-Control: max-age=60"));
    643   }
    644 
    645   @Test public void maxAgeInTheFutureWithDateAndLastModifiedHeaders() throws Exception {
    646     assertFullyCached(new MockResponse()
    647         .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
    648         .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
    649         .addHeader("Cache-Control: max-age=60"));
    650   }
    651 
    652   @Test public void maxAgePreferredOverLowerSharedMaxAge() throws Exception {
    653     assertFullyCached(new MockResponse()
    654         .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
    655         .addHeader("Cache-Control: s-maxage=60")
    656         .addHeader("Cache-Control: max-age=180"));
    657   }
    658 
    659   @Test public void maxAgePreferredOverHigherMaxAge() throws Exception {
    660     assertNotCached(new MockResponse()
    661         .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
    662         .addHeader("Cache-Control: s-maxage=180")
    663         .addHeader("Cache-Control: max-age=60"));
    664   }
    665 
    666   @Test public void requestMethodOptionsIsNotCached() throws Exception {
    667     testRequestMethod("OPTIONS", false);
    668   }
    669 
    670   @Test public void requestMethodGetIsCached() throws Exception {
    671     testRequestMethod("GET", true);
    672   }
    673 
    674   @Test public void requestMethodHeadIsNotCached() throws Exception {
    675     // We could support this but choose not to for implementation simplicity
    676     testRequestMethod("HEAD", false);
    677   }
    678 
    679   @Test public void requestMethodPostIsNotCached() throws Exception {
    680     // We could support this but choose not to for implementation simplicity
    681     testRequestMethod("POST", false);
    682   }
    683 
    684   @Test public void requestMethodPutIsNotCached() throws Exception {
    685     testRequestMethod("PUT", false);
    686   }
    687 
    688   @Test public void requestMethodDeleteIsNotCached() throws Exception {
    689     testRequestMethod("DELETE", false);
    690   }
    691 
    692   @Test public void requestMethodTraceIsNotCached() throws Exception {
    693     testRequestMethod("TRACE", false);
    694   }
    695 
    696   private void testRequestMethod(String requestMethod, boolean expectCached) throws Exception {
    697     // 1. seed the cache (potentially)
    698     // 2. expect a cache hit or miss
    699     server.enqueue(new MockResponse()
    700         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    701         .addHeader("X-Response-ID: 1"));
    702     server.enqueue(new MockResponse()
    703         .addHeader("X-Response-ID: 2"));
    704 
    705     URL url = server.getUrl("/");
    706 
    707     HttpURLConnection request1 = openConnection(url);
    708     request1.setRequestMethod(requestMethod);
    709     addRequestBodyIfNecessary(requestMethod, request1);
    710     request1.getInputStream().close();
    711     assertEquals("1", request1.getHeaderField("X-Response-ID"));
    712 
    713     URLConnection request2 = openConnection(url);
    714     request2.getInputStream().close();
    715     if (expectCached) {
    716       assertEquals("1", request2.getHeaderField("X-Response-ID"));
    717     } else {
    718       assertEquals("2", request2.getHeaderField("X-Response-ID"));
    719     }
    720   }
    721 
    722   private void addRequestBodyIfNecessary(String requestMethod, HttpURLConnection connection)
    723       throws IOException {
    724     if (requestMethod.equals("POST") || requestMethod.equals("PUT")) {
    725       connection.setDoOutput(true);
    726       OutputStream requestBody = connection.getOutputStream();
    727       requestBody.write('x');
    728       requestBody.close();
    729     }
    730   }
    731 
    732   @Test public void postInvalidatesCache() throws Exception {
    733     testMethodInvalidates("POST");
    734   }
    735 
    736   @Test public void putInvalidatesCache() throws Exception {
    737     testMethodInvalidates("PUT");
    738   }
    739 
    740   @Test public void deleteMethodInvalidatesCache() throws Exception {
    741     testMethodInvalidates("DELETE");
    742   }
    743 
    744   private void testMethodInvalidates(String requestMethod) throws Exception {
    745     // 1. seed the cache
    746     // 2. invalidate it
    747     // 3. expect a cache miss
    748     server.enqueue(new MockResponse()
    749         .setBody("A")
    750         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
    751     server.enqueue(new MockResponse()
    752         .setBody("B"));
    753     server.enqueue(new MockResponse()
    754         .setBody("C"));
    755 
    756     URL url = server.getUrl("/");
    757 
    758     assertEquals("A", readAscii(openConnection(url)));
    759 
    760     HttpURLConnection invalidateConnection = openConnection(url);
    761     invalidateConnection.setRequestMethod(requestMethod);
    762     assertEquals("B", readAscii(invalidateConnection));
    763 
    764     assertEquals("C", readAscii(openConnection(url)));
    765   }
    766 
    767   /**
    768    * Equivalent to {@code CacheTest.postInvalidatesCacheWithUncacheableResponse()} but demonstrating
    769    * that {@link ResponseCache} provides no mechanism for cache invalidation as the result of
    770    * locally-made requests. In reality invalidation could take place from other clients at any time.
    771    */
    772   @Test public void postInvalidatesCacheWithUncacheableResponse() throws Exception {
    773     // 1. seed the cache
    774     // 2. invalidate it with uncacheable response
    775     // 3. the cache to return the original value
    776     server.enqueue(new MockResponse()
    777         .setBody("A")
    778         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
    779     server.enqueue(new MockResponse()
    780         .setBody("B")
    781         .setResponseCode(500));
    782 
    783     URL url = server.getUrl("/");
    784 
    785     assertEquals("A", readAscii(openConnection(url)));
    786 
    787     HttpURLConnection invalidate = openConnection(url);
    788     invalidate.setRequestMethod("POST");
    789     addRequestBodyIfNecessary("POST", invalidate);
    790     assertEquals("B", readAscii(invalidate));
    791 
    792     assertEquals("A", readAscii(openConnection(url)));
    793   }
    794 
    795   @Test public void etag() throws Exception {
    796     RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
    797         .addHeader("ETag: v1"));
    798     assertEquals("v1", conditionalRequest.getHeader("If-None-Match"));
    799   }
    800 
    801   /** If both If-Modified-Since and If-None-Match conditions apply, send only If-None-Match. */
    802   @Test public void etagAndExpirationDateInThePast() throws Exception {
    803     String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
    804     RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
    805         .addHeader("ETag: v1")
    806         .addHeader("Last-Modified: " + lastModifiedDate)
    807         .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
    808     assertEquals("v1", conditionalRequest.getHeader("If-None-Match"));
    809     assertNull(conditionalRequest.getHeader("If-Modified-Since"));
    810   }
    811 
    812   @Test public void etagAndExpirationDateInTheFuture() throws Exception {
    813     assertFullyCached(new MockResponse()
    814         .addHeader("ETag: v1")
    815         .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
    816         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
    817   }
    818 
    819   @Test public void cacheControlNoCache() throws Exception {
    820     assertNotCached(new MockResponse()
    821         .addHeader("Cache-Control: no-cache"));
    822   }
    823 
    824   @Test public void cacheControlNoCacheAndExpirationDateInTheFuture() throws Exception {
    825     String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
    826     RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
    827         .addHeader("Last-Modified: " + lastModifiedDate)
    828         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    829         .addHeader("Cache-Control: no-cache"));
    830     assertEquals(lastModifiedDate, conditionalRequest.getHeader("If-Modified-Since"));
    831   }
    832 
    833   @Test public void pragmaNoCache() throws Exception {
    834     assertNotCached(new MockResponse()
    835         .addHeader("Pragma: no-cache"));
    836   }
    837 
    838   @Test public void pragmaNoCacheAndExpirationDateInTheFuture() throws Exception {
    839     String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
    840     RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
    841         .addHeader("Last-Modified: " + lastModifiedDate)
    842         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    843         .addHeader("Pragma: no-cache"));
    844     assertEquals(lastModifiedDate, conditionalRequest.getHeader("If-Modified-Since"));
    845   }
    846 
    847   @Test public void cacheControlNoStore() throws Exception {
    848     assertNotCached(new MockResponse()
    849         .addHeader("Cache-Control: no-store"));
    850   }
    851 
    852   @Test public void cacheControlNoStoreAndExpirationDateInTheFuture() throws Exception {
    853     assertNotCached(new MockResponse()
    854         .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
    855         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    856         .addHeader("Cache-Control: no-store"));
    857   }
    858 
    859   @Test public void partialRangeResponsesDoNotCorruptCache() throws Exception {
    860     // 1. request a range
    861     // 2. request a full document, expecting a cache miss
    862     server.enqueue(new MockResponse()
    863         .setBody("AA")
    864         .setResponseCode(HttpURLConnection.HTTP_PARTIAL)
    865         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
    866         .addHeader("Content-Range: bytes 1000-1001/2000"));
    867     server.enqueue(new MockResponse()
    868         .setBody("BB"));
    869 
    870     URL url = server.getUrl("/");
    871 
    872     HttpURLConnection range = openConnection(url);
    873     range.addRequestProperty("Range", "bytes=1000-1001");
    874     assertEquals("AA", readAscii(range));
    875 
    876     assertEquals("BB", readAscii(openConnection(url)));
    877   }
    878 
    879   @Test public void serverReturnsDocumentOlderThanCache() throws Exception {
    880     server.enqueue(new MockResponse()
    881         .setBody("A")
    882         .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
    883         .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
    884     server.enqueue(new MockResponse()
    885         .setBody("B")
    886         .addHeader("Last-Modified: " + formatDate(-4, TimeUnit.HOURS)));
    887 
    888     URL url = server.getUrl("/");
    889 
    890     assertEquals("A", readAscii(openConnection(url)));
    891     assertEquals("A", readAscii(openConnection(url)));
    892   }
    893 
    894   @Test public void clientSideNoStore() throws Exception {
    895     server.enqueue(new MockResponse()
    896         .addHeader("Cache-Control: max-age=60")
    897         .setBody("A"));
    898     server.enqueue(new MockResponse()
    899         .addHeader("Cache-Control: max-age=60")
    900         .setBody("B"));
    901 
    902     HttpURLConnection connection1 = openConnection(server.getUrl("/"));
    903     connection1.setRequestProperty("Cache-Control", "no-store");
    904     assertEquals("A", readAscii(connection1));
    905 
    906     HttpURLConnection connection2 = openConnection(server.getUrl("/"));
    907     assertEquals("B", readAscii(connection2));
    908   }
    909 
    910   @Test public void nonIdentityEncodingAndConditionalCache() throws Exception {
    911     assertNonIdentityEncodingCached(new MockResponse()
    912         .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
    913         .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
    914   }
    915 
    916   @Test public void nonIdentityEncodingAndFullCache() throws Exception {
    917     assertNonIdentityEncodingCached(new MockResponse()
    918         .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
    919         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
    920   }
    921 
    922   private void assertNonIdentityEncodingCached(MockResponse response) throws Exception {
    923     server.enqueue(response
    924         .setBody(gzip("ABCABCABC"))
    925         .addHeader("Content-Encoding: gzip"));
    926     server.enqueue(new MockResponse()
    927         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
    928     server.enqueue(new MockResponse()
    929         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
    930 
    931     // At least three request/response pairs are required because after the first request is cached
    932     // a different execution path might be taken. Thus modifications to the cache applied during
    933     // the second request might not be visible until another request is performed.
    934     assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
    935     assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
    936     assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
    937   }
    938 
    939   @Test public void notModifiedSpecifiesEncoding() throws Exception {
    940     server.enqueue(new MockResponse()
    941         .setBody(gzip("ABCABCABC"))
    942         .addHeader("Content-Encoding: gzip")
    943         .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
    944         .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
    945     server.enqueue(new MockResponse()
    946         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)
    947         .addHeader("Content-Encoding: gzip"));
    948     server.enqueue(new MockResponse()
    949         .setBody("DEFDEFDEF"));
    950 
    951     assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
    952     assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
    953     assertEquals("DEFDEFDEF", readAscii(openConnection(server.getUrl("/"))));
    954   }
    955 
    956   /** https://github.com/square/okhttp/issues/947 */
    957   @Test public void gzipAndVaryOnAcceptEncoding() throws Exception {
    958     server.enqueue(new MockResponse()
    959         .setBody(gzip("ABCABCABC"))
    960         .addHeader("Content-Encoding: gzip")
    961         .addHeader("Vary: Accept-Encoding")
    962         .addHeader("Cache-Control: max-age=60"));
    963     server.enqueue(new MockResponse()
    964         .setBody("FAIL"));
    965 
    966     assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
    967     assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
    968   }
    969 
    970   @Test public void expiresDateBeforeModifiedDate() throws Exception {
    971     assertConditionallyCached(new MockResponse()
    972         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
    973         .addHeader("Expires: " + formatDate(-2, TimeUnit.HOURS)));
    974   }
    975 
    976   @Test public void requestMaxAge() throws IOException {
    977     server.enqueue(new MockResponse()
    978         .setBody("A")
    979         .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
    980         .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES))
    981         .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
    982     server.enqueue(new MockResponse()
    983         .setBody("B"));
    984 
    985     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
    986 
    987     URLConnection connection = openConnection(server.getUrl("/"));
    988     connection.addRequestProperty("Cache-Control", "max-age=30");
    989     assertEquals("B", readAscii(connection));
    990   }
    991 
    992   @Test public void requestMinFresh() throws IOException {
    993     server.enqueue(new MockResponse()
    994         .setBody("A")
    995         .addHeader("Cache-Control: max-age=60")
    996         .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
    997     server.enqueue(new MockResponse()
    998         .setBody("B"));
    999 
   1000     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1001 
   1002     URLConnection connection = openConnection(server.getUrl("/"));
   1003     connection.addRequestProperty("Cache-Control", "min-fresh=120");
   1004     assertEquals("B", readAscii(connection));
   1005   }
   1006 
   1007   @Test public void requestMaxStale() throws IOException {
   1008     server.enqueue(new MockResponse()
   1009         .setBody("A")
   1010         .addHeader("Cache-Control: max-age=120")
   1011         .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES)));
   1012     server.enqueue(new MockResponse()
   1013         .setBody("B"));
   1014 
   1015     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1016 
   1017     URLConnection connection = openConnection(server.getUrl("/"));
   1018     connection.addRequestProperty("Cache-Control", "max-stale=180");
   1019     assertEquals("A", readAscii(connection));
   1020     assertEquals("110 HttpURLConnection \"Response is stale\"",
   1021         connection.getHeaderField("Warning"));
   1022   }
   1023 
   1024   @Test public void requestMaxStaleDirectiveWithNoValue() throws IOException {
   1025     // Add a stale response to the cache.
   1026     server.enqueue(new MockResponse()
   1027         .setBody("A")
   1028         .addHeader("Cache-Control: max-age=120")
   1029         .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES)));
   1030     server.enqueue(new MockResponse()
   1031         .setBody("B"));
   1032 
   1033     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1034 
   1035     // With max-stale, we'll return that stale response.
   1036     URLConnection maxStaleConnection = openConnection(server.getUrl("/"));
   1037     maxStaleConnection.setRequestProperty("Cache-Control", "max-stale");
   1038     assertEquals("A", readAscii(maxStaleConnection));
   1039     assertEquals("110 HttpURLConnection \"Response is stale\"",
   1040         maxStaleConnection.getHeaderField("Warning"));
   1041   }
   1042 
   1043   @Test public void requestMaxStaleNotHonoredWithMustRevalidate() throws IOException {
   1044     server.enqueue(new MockResponse()
   1045         .setBody("A")
   1046         .addHeader("Cache-Control: max-age=120, must-revalidate")
   1047         .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES)));
   1048     server.enqueue(new MockResponse()
   1049         .setBody("B"));
   1050 
   1051     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1052 
   1053     URLConnection connection = openConnection(server.getUrl("/"));
   1054     connection.addRequestProperty("Cache-Control", "max-stale=180");
   1055     assertEquals("B", readAscii(connection));
   1056   }
   1057 
   1058   @Test public void requestOnlyIfCachedWithNoResponseCached() throws IOException {
   1059     // (no responses enqueued)
   1060 
   1061     HttpURLConnection connection = openConnection(server.getUrl("/"));
   1062     connection.addRequestProperty("Cache-Control", "only-if-cached");
   1063     assertGatewayTimeout(connection);
   1064   }
   1065 
   1066   @Test public void requestOnlyIfCachedWithFullResponseCached() throws IOException {
   1067     server.enqueue(new MockResponse()
   1068         .setBody("A")
   1069         .addHeader("Cache-Control: max-age=30")
   1070         .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
   1071 
   1072     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1073     URLConnection connection = openConnection(server.getUrl("/"));
   1074     connection.addRequestProperty("Cache-Control", "only-if-cached");
   1075     assertEquals("A", readAscii(connection));
   1076   }
   1077 
   1078   @Test public void requestOnlyIfCachedWithConditionalResponseCached() throws IOException {
   1079     server.enqueue(new MockResponse()
   1080         .setBody("A")
   1081         .addHeader("Cache-Control: max-age=30")
   1082         .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES)));
   1083 
   1084     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1085     HttpURLConnection connection = openConnection(server.getUrl("/"));
   1086     connection.addRequestProperty("Cache-Control", "only-if-cached");
   1087     assertGatewayTimeout(connection);
   1088   }
   1089 
   1090   @Test public void requestOnlyIfCachedWithUnhelpfulResponseCached() throws IOException {
   1091     server.enqueue(new MockResponse()
   1092         .setBody("A"));
   1093 
   1094     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1095     HttpURLConnection connection = openConnection(server.getUrl("/"));
   1096     connection.addRequestProperty("Cache-Control", "only-if-cached");
   1097     assertGatewayTimeout(connection);
   1098   }
   1099 
   1100   @Test public void requestCacheControlNoCache() throws Exception {
   1101     server.enqueue(new MockResponse()
   1102         .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
   1103         .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
   1104         .addHeader("Cache-Control: max-age=60")
   1105         .setBody("A"));
   1106     server.enqueue(new MockResponse().setBody("B"));
   1107 
   1108     URL url = server.getUrl("/");
   1109     assertEquals("A", readAscii(openConnection(url)));
   1110     URLConnection connection = openConnection(url);
   1111     connection.setRequestProperty("Cache-Control", "no-cache");
   1112     assertEquals("B", readAscii(connection));
   1113   }
   1114 
   1115   @Test public void requestPragmaNoCache() throws Exception {
   1116     server.enqueue(new MockResponse()
   1117         .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
   1118         .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
   1119         .addHeader("Cache-Control: max-age=60")
   1120         .setBody("A"));
   1121     server.enqueue(new MockResponse().setBody("B"));
   1122 
   1123     URL url = server.getUrl("/");
   1124     assertEquals("A", readAscii(openConnection(url)));
   1125     URLConnection connection = openConnection(url);
   1126     connection.setRequestProperty("Pragma", "no-cache");
   1127     assertEquals("B", readAscii(connection));
   1128   }
   1129 
   1130   @Test public void clientSuppliedIfModifiedSinceWithCachedResult() throws Exception {
   1131     MockResponse response = new MockResponse()
   1132         .addHeader("ETag: v3")
   1133         .addHeader("Cache-Control: max-age=0");
   1134     String ifModifiedSinceDate = formatDate(-24, TimeUnit.HOURS);
   1135     RecordedRequest request =
   1136         assertClientSuppliedCondition(response, "If-Modified-Since", ifModifiedSinceDate);
   1137     assertEquals(ifModifiedSinceDate, request.getHeader("If-Modified-Since"));
   1138     assertNull(request.getHeader("If-None-Match"));
   1139   }
   1140 
   1141   @Test public void clientSuppliedIfNoneMatchSinceWithCachedResult() throws Exception {
   1142     String lastModifiedDate = formatDate(-3, TimeUnit.MINUTES);
   1143     MockResponse response = new MockResponse()
   1144         .addHeader("Last-Modified: " + lastModifiedDate)
   1145         .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
   1146         .addHeader("Cache-Control: max-age=0");
   1147     RecordedRequest request = assertClientSuppliedCondition(response, "If-None-Match", "v1");
   1148     assertEquals("v1", request.getHeader("If-None-Match"));
   1149     assertNull(request.getHeader("If-Modified-Since"));
   1150   }
   1151 
   1152   private RecordedRequest assertClientSuppliedCondition(MockResponse seed, String conditionName,
   1153       String conditionValue) throws Exception {
   1154     server.enqueue(seed.setBody("A"));
   1155     server.enqueue(new MockResponse()
   1156         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
   1157 
   1158     URL url = server.getUrl("/");
   1159     assertEquals("A", readAscii(openConnection(url)));
   1160 
   1161     HttpURLConnection connection = openConnection(url);
   1162     connection.addRequestProperty(conditionName, conditionValue);
   1163     assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode());
   1164     assertEquals("", readAscii(connection));
   1165 
   1166     server.takeRequest(); // seed
   1167     return server.takeRequest();
   1168   }
   1169 
   1170   /**
   1171    * For Last-Modified and Date headers, we should echo the date back in the
   1172    * exact format we were served.
   1173    */
   1174   @Test public void retainServedDateFormat() throws Exception {
   1175     // Serve a response with a non-standard date format that OkHttp supports.
   1176     Date lastModifiedDate = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(-1));
   1177     Date servedDate = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(-2));
   1178     DateFormat dateFormat = new SimpleDateFormat("EEE dd-MMM-yyyy HH:mm:ss z", Locale.US);
   1179     dateFormat.setTimeZone(TimeZone.getTimeZone("EDT"));
   1180     String lastModifiedString = dateFormat.format(lastModifiedDate);
   1181     String servedString = dateFormat.format(servedDate);
   1182 
   1183     // This response should be conditionally cached.
   1184     server.enqueue(new MockResponse()
   1185         .addHeader("Last-Modified: " + lastModifiedString)
   1186         .addHeader("Expires: " + servedString)
   1187         .setBody("A"));
   1188     server.enqueue(new MockResponse()
   1189         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
   1190 
   1191     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1192     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1193 
   1194     // The first request has no conditions.
   1195     RecordedRequest request1 = server.takeRequest();
   1196     assertNull(request1.getHeader("If-Modified-Since"));
   1197 
   1198     // The 2nd request uses the server's date format.
   1199     RecordedRequest request2 = server.takeRequest();
   1200     assertEquals(lastModifiedString, request2.getHeader("If-Modified-Since"));
   1201   }
   1202 
   1203   @Test public void clientSuppliedConditionWithoutCachedResult() throws Exception {
   1204     server.enqueue(new MockResponse()
   1205         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
   1206 
   1207     HttpURLConnection connection = openConnection(server.getUrl("/"));
   1208     String clientIfModifiedSince = formatDate(-24, TimeUnit.HOURS);
   1209     connection.addRequestProperty("If-Modified-Since", clientIfModifiedSince);
   1210     assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode());
   1211     assertEquals("", readAscii(connection));
   1212   }
   1213 
   1214   @Test public void authorizationRequestFullyCached() throws Exception {
   1215     server.enqueue(new MockResponse()
   1216         .addHeader("Cache-Control: max-age=60")
   1217         .setBody("A"));
   1218     server.enqueue(new MockResponse()
   1219         .setBody("B"));
   1220 
   1221     URL url = server.getUrl("/");
   1222     URLConnection connection = openConnection(url);
   1223     connection.addRequestProperty("Authorization", "password");
   1224     assertEquals("A", readAscii(connection));
   1225     assertEquals("A", readAscii(openConnection(url)));
   1226   }
   1227 
   1228   @Test public void contentLocationDoesNotPopulateCache() throws Exception {
   1229     server.enqueue(new MockResponse()
   1230         .addHeader("Cache-Control: max-age=60")
   1231         .addHeader("Content-Location: /bar")
   1232         .setBody("A"));
   1233     server.enqueue(new MockResponse()
   1234         .setBody("B"));
   1235 
   1236     assertEquals("A", readAscii(openConnection(server.getUrl("/foo"))));
   1237     assertEquals("B", readAscii(openConnection(server.getUrl("/bar"))));
   1238   }
   1239 
   1240   @Test public void connectionIsReturnedToPoolAfterConditionalSuccess() throws Exception {
   1241     server.enqueue(new MockResponse()
   1242         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
   1243         .addHeader("Cache-Control: max-age=0")
   1244         .setBody("A"));
   1245     server.enqueue(new MockResponse()
   1246         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
   1247     server.enqueue(new MockResponse()
   1248         .setBody("B"));
   1249 
   1250     assertEquals("A", readAscii(openConnection(server.getUrl("/a"))));
   1251     assertEquals("A", readAscii(openConnection(server.getUrl("/a"))));
   1252     assertEquals("B", readAscii(openConnection(server.getUrl("/b"))));
   1253 
   1254     assertEquals(0, server.takeRequest().getSequenceNumber());
   1255     assertEquals(1, server.takeRequest().getSequenceNumber());
   1256     assertEquals(2, server.takeRequest().getSequenceNumber());
   1257   }
   1258 
   1259   @Test public void varyMatchesChangedRequestHeaderField() throws Exception {
   1260     server.enqueue(new MockResponse()
   1261         .addHeader("Cache-Control: max-age=60")
   1262         .addHeader("Vary: Accept-Language")
   1263         .setBody("A"));
   1264     server.enqueue(new MockResponse()
   1265         .setBody("B"));
   1266 
   1267     URL url = server.getUrl("/");
   1268     HttpURLConnection frenchConnection = openConnection(url);
   1269     frenchConnection.setRequestProperty("Accept-Language", "fr-CA");
   1270     assertEquals("A", readAscii(frenchConnection));
   1271 
   1272     HttpURLConnection englishConnection = openConnection(url);
   1273     englishConnection.setRequestProperty("Accept-Language", "en-US");
   1274     assertEquals("B", readAscii(englishConnection));
   1275   }
   1276 
   1277   @Test public void varyMatchesUnchangedRequestHeaderField() throws Exception {
   1278     server.enqueue(new MockResponse()
   1279         .addHeader("Cache-Control: max-age=60")
   1280         .addHeader("Vary: Accept-Language")
   1281         .setBody("A"));
   1282     server.enqueue(new MockResponse()
   1283         .setBody("B"));
   1284 
   1285     URL url = server.getUrl("/");
   1286     HttpURLConnection frenchConnection1 = openConnection(url);
   1287     frenchConnection1.setRequestProperty("Accept-Language", "fr-CA");
   1288     assertEquals("A", readAscii(frenchConnection1));
   1289 
   1290     HttpURLConnection frenchConnection2 = openConnection(url);
   1291     frenchConnection2.setRequestProperty("Accept-Language", "fr-CA");
   1292     assertEquals("A", readAscii(frenchConnection2));
   1293   }
   1294 
   1295   @Test public void varyMatchesAbsentRequestHeaderField() throws Exception {
   1296     server.enqueue(new MockResponse()
   1297         .addHeader("Cache-Control: max-age=60")
   1298         .addHeader("Vary: Foo")
   1299         .setBody("A"));
   1300     server.enqueue(new MockResponse()
   1301         .setBody("B"));
   1302 
   1303     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1304     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1305   }
   1306 
   1307   @Test public void varyMatchesAddedRequestHeaderField() throws Exception {
   1308     server.enqueue(new MockResponse()
   1309         .addHeader("Cache-Control: max-age=60")
   1310         .addHeader("Vary: Foo")
   1311         .setBody("A"));
   1312     server.enqueue(new MockResponse()
   1313         .setBody("B"));
   1314 
   1315     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1316     HttpURLConnection connection2 = openConnection(server.getUrl("/"));
   1317     connection2.setRequestProperty("Foo", "bar");
   1318     assertEquals("B", readAscii(connection2));
   1319   }
   1320 
   1321   @Test public void varyMatchesRemovedRequestHeaderField() throws Exception {
   1322     server.enqueue(new MockResponse()
   1323         .addHeader("Cache-Control: max-age=60")
   1324         .addHeader("Vary: Foo")
   1325         .setBody("A"));
   1326     server.enqueue(new MockResponse()
   1327         .setBody("B"));
   1328 
   1329     HttpURLConnection connection1 = openConnection(server.getUrl("/"));
   1330     connection1.setRequestProperty("Foo", "bar");
   1331     assertEquals("A", readAscii(connection1));
   1332     assertEquals("B", readAscii(openConnection(server.getUrl("/"))));
   1333   }
   1334 
   1335   @Test public void varyFieldsAreCaseInsensitive() throws Exception {
   1336     server.enqueue(new MockResponse()
   1337         .addHeader("Cache-Control: max-age=60")
   1338         .addHeader("Vary: ACCEPT-LANGUAGE")
   1339         .setBody("A"));
   1340     server.enqueue(new MockResponse()
   1341         .setBody("B"));
   1342 
   1343     URL url = server.getUrl("/");
   1344     HttpURLConnection frenchConnection1 = openConnection(url);
   1345     frenchConnection1.setRequestProperty("Accept-Language", "fr-CA");
   1346     assertEquals("A", readAscii(frenchConnection1));
   1347     HttpURLConnection frenchConnection2 = openConnection(url);
   1348     frenchConnection2.setRequestProperty("accept-language", "fr-CA");
   1349     assertEquals("A", readAscii(frenchConnection2));
   1350   }
   1351 
   1352   @Test public void varyMultipleFieldsWithMatch() throws Exception {
   1353     server.enqueue(new MockResponse()
   1354         .addHeader("Cache-Control: max-age=60")
   1355         .addHeader("Vary: Accept-Language, Accept-Charset")
   1356         .addHeader("Vary: Accept-Encoding")
   1357         .setBody("A"));
   1358     server.enqueue(new MockResponse()
   1359         .setBody("B"));
   1360 
   1361     URL url = server.getUrl("/");
   1362     HttpURLConnection frenchConnection1 = openConnection(url);
   1363     frenchConnection1.setRequestProperty("Accept-Language", "fr-CA");
   1364     frenchConnection1.setRequestProperty("Accept-Charset", "UTF-8");
   1365     frenchConnection1.setRequestProperty("Accept-Encoding", "identity");
   1366     assertEquals("A", readAscii(frenchConnection1));
   1367     HttpURLConnection frenchConnection2 = openConnection(url);
   1368     frenchConnection2.setRequestProperty("Accept-Language", "fr-CA");
   1369     frenchConnection2.setRequestProperty("Accept-Charset", "UTF-8");
   1370     frenchConnection2.setRequestProperty("Accept-Encoding", "identity");
   1371     assertEquals("A", readAscii(frenchConnection2));
   1372   }
   1373 
   1374   @Test public void varyMultipleFieldsWithNoMatch() throws Exception {
   1375     server.enqueue(new MockResponse()
   1376         .addHeader("Cache-Control: max-age=60")
   1377         .addHeader("Vary: Accept-Language, Accept-Charset")
   1378         .addHeader("Vary: Accept-Encoding")
   1379         .setBody("A"));
   1380     server.enqueue(new MockResponse()
   1381         .setBody("B"));
   1382 
   1383     URL url = server.getUrl("/");
   1384     HttpURLConnection frenchConnection = openConnection(url);
   1385     frenchConnection.setRequestProperty("Accept-Language", "fr-CA");
   1386     frenchConnection.setRequestProperty("Accept-Charset", "UTF-8");
   1387     frenchConnection.setRequestProperty("Accept-Encoding", "identity");
   1388     assertEquals("A", readAscii(frenchConnection));
   1389     HttpURLConnection englishConnection = openConnection(url);
   1390     englishConnection.setRequestProperty("Accept-Language", "en-CA");
   1391     englishConnection.setRequestProperty("Accept-Charset", "UTF-8");
   1392     englishConnection.setRequestProperty("Accept-Encoding", "identity");
   1393     assertEquals("B", readAscii(englishConnection));
   1394   }
   1395 
   1396   @Test public void varyMultipleFieldValuesWithMatch() throws Exception {
   1397     server.enqueue(new MockResponse()
   1398         .addHeader("Cache-Control: max-age=60")
   1399         .addHeader("Vary: Accept-Language")
   1400         .setBody("A"));
   1401     server.enqueue(new MockResponse()
   1402         .setBody("B"));
   1403 
   1404     URL url = server.getUrl("/");
   1405     HttpURLConnection multiConnection1 = openConnection(url);
   1406     multiConnection1.setRequestProperty("Accept-Language", "fr-CA, fr-FR");
   1407     multiConnection1.addRequestProperty("Accept-Language", "en-US");
   1408     assertEquals("A", readAscii(multiConnection1));
   1409 
   1410     HttpURLConnection multiConnection2 = openConnection(url);
   1411     multiConnection2.setRequestProperty("Accept-Language", "fr-CA, fr-FR");
   1412     multiConnection2.addRequestProperty("Accept-Language", "en-US");
   1413     assertEquals("A", readAscii(multiConnection2));
   1414   }
   1415 
   1416   @Test public void varyMultipleFieldValuesWithNoMatch() throws Exception {
   1417     server.enqueue(new MockResponse()
   1418         .addHeader("Cache-Control: max-age=60")
   1419         .addHeader("Vary: Accept-Language")
   1420         .setBody("A"));
   1421     server.enqueue(new MockResponse()
   1422         .setBody("B"));
   1423 
   1424     URL url = server.getUrl("/");
   1425     HttpURLConnection multiConnection = openConnection(url);
   1426     multiConnection.setRequestProperty("Accept-Language", "fr-CA, fr-FR");
   1427     multiConnection.addRequestProperty("Accept-Language", "en-US");
   1428     assertEquals("A", readAscii(multiConnection));
   1429 
   1430     HttpURLConnection notFrenchConnection = openConnection(url);
   1431     notFrenchConnection.setRequestProperty("Accept-Language", "fr-CA");
   1432     notFrenchConnection.addRequestProperty("Accept-Language", "en-US");
   1433     assertEquals("B", readAscii(notFrenchConnection));
   1434   }
   1435 
   1436   @Test public void varyAsterisk() throws Exception {
   1437     server.enqueue(new MockResponse()
   1438         .addHeader("Cache-Control: max-age=60")
   1439         .addHeader("Vary: *")
   1440         .setBody("A"));
   1441     server.enqueue(new MockResponse()
   1442         .setBody("B"));
   1443 
   1444     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1445     assertEquals("B", readAscii(openConnection(server.getUrl("/"))));
   1446   }
   1447 
   1448   @Test public void varyAndHttps() throws Exception {
   1449     server.useHttps(sslContext.getSocketFactory(), false);
   1450     server.enqueue(new MockResponse()
   1451         .addHeader("Cache-Control: max-age=60")
   1452         .addHeader("Vary: Accept-Language")
   1453         .setBody("A"));
   1454     server.enqueue(new MockResponse()
   1455         .setBody("B"));
   1456 
   1457     client.setSslSocketFactory(sslContext.getSocketFactory());
   1458     client.setHostnameVerifier(hostnameVerifier);
   1459 
   1460     URL url = server.getUrl("/");
   1461     HttpURLConnection connection1 = openConnection(url);
   1462     connection1.setRequestProperty("Accept-Language", "en-US");
   1463     assertEquals("A", readAscii(connection1));
   1464 
   1465     HttpURLConnection connection2 = openConnection(url);
   1466     connection2.setRequestProperty("Accept-Language", "en-US");
   1467     assertEquals("A", readAscii(connection2));
   1468   }
   1469 
   1470   @Test public void cachePlusCookies() throws Exception {
   1471     server.enqueue(new MockResponse()
   1472         .addHeader("Set-Cookie: a=FIRST; domain=" + server.getCookieDomain() + ";")
   1473         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
   1474         .addHeader("Cache-Control: max-age=0")
   1475         .setBody("A"));
   1476     server.enqueue(new MockResponse()
   1477         .addHeader("Set-Cookie: a=SECOND; domain=" + server.getCookieDomain() + ";")
   1478         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
   1479 
   1480     URL url = server.getUrl("/");
   1481     assertEquals("A", readAscii(openConnection(url)));
   1482     assertCookies(url, "a=FIRST");
   1483     assertEquals("A", readAscii(openConnection(url)));
   1484     assertCookies(url, "a=SECOND");
   1485   }
   1486 
   1487   @Test public void getHeadersReturnsNetworkEndToEndHeaders() throws Exception {
   1488     server.enqueue(new MockResponse()
   1489         .addHeader("Allow: GET, HEAD")
   1490         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
   1491         .addHeader("Cache-Control: max-age=0")
   1492         .setBody("A"));
   1493     server.enqueue(new MockResponse()
   1494         .addHeader("Allow: GET, HEAD, PUT")
   1495         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
   1496 
   1497     URLConnection connection1 = openConnection(server.getUrl("/"));
   1498     assertEquals("A", readAscii(connection1));
   1499     assertEquals("GET, HEAD", connection1.getHeaderField("Allow"));
   1500 
   1501     URLConnection connection2 = openConnection(server.getUrl("/"));
   1502     assertEquals("A", readAscii(connection2));
   1503     assertEquals("GET, HEAD, PUT", connection2.getHeaderField("Allow"));
   1504   }
   1505 
   1506   @Test public void getHeadersReturnsCachedHopByHopHeaders() throws Exception {
   1507     server.enqueue(new MockResponse()
   1508         .addHeader("Transfer-Encoding: identity")
   1509         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
   1510         .addHeader("Cache-Control: max-age=0")
   1511         .setBody("A"));
   1512     server.enqueue(new MockResponse()
   1513         .addHeader("Transfer-Encoding: none")
   1514         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
   1515 
   1516     URLConnection connection1 = openConnection(server.getUrl("/"));
   1517     assertEquals("A", readAscii(connection1));
   1518     assertEquals("identity", connection1.getHeaderField("Transfer-Encoding"));
   1519 
   1520     URLConnection connection2 = openConnection(server.getUrl("/"));
   1521     assertEquals("A", readAscii(connection2));
   1522     assertEquals("identity", connection2.getHeaderField("Transfer-Encoding"));
   1523   }
   1524 
   1525   @Test public void getHeadersDeletesCached100LevelWarnings() throws Exception {
   1526     server.enqueue(new MockResponse()
   1527         .addHeader("Warning: 199 test danger")
   1528         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
   1529         .addHeader("Cache-Control: max-age=0")
   1530         .setBody("A"));
   1531     server.enqueue(new MockResponse()
   1532         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
   1533 
   1534     URLConnection connection1 = openConnection(server.getUrl("/"));
   1535     assertEquals("A", readAscii(connection1));
   1536     assertEquals("199 test danger", connection1.getHeaderField("Warning"));
   1537 
   1538     URLConnection connection2 = openConnection(server.getUrl("/"));
   1539     assertEquals("A", readAscii(connection2));
   1540     assertEquals(null, connection2.getHeaderField("Warning"));
   1541   }
   1542 
   1543   @Test public void getHeadersRetainsCached200LevelWarnings() throws Exception {
   1544     server.enqueue(new MockResponse()
   1545         .addHeader("Warning: 299 test danger")
   1546         .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
   1547         .addHeader("Cache-Control: max-age=0")
   1548         .setBody("A"));
   1549     server.enqueue(new MockResponse()
   1550         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
   1551 
   1552     URLConnection connection1 = openConnection(server.getUrl("/"));
   1553     assertEquals("A", readAscii(connection1));
   1554     assertEquals("299 test danger", connection1.getHeaderField("Warning"));
   1555 
   1556     URLConnection connection2 = openConnection(server.getUrl("/"));
   1557     assertEquals("A", readAscii(connection2));
   1558     assertEquals("299 test danger", connection2.getHeaderField("Warning"));
   1559   }
   1560 
   1561   public void assertCookies(URL url, String... expectedCookies) throws Exception {
   1562     List<String> actualCookies = new ArrayList<>();
   1563     for (HttpCookie cookie : cookieManager.getCookieStore().get(url.toURI())) {
   1564       actualCookies.add(cookie.toString());
   1565     }
   1566     assertEquals(Arrays.asList(expectedCookies), actualCookies);
   1567   }
   1568 
   1569   @Test public void doNotCachePartialResponse() throws Exception  {
   1570     assertNotCached(new MockResponse()
   1571         .setResponseCode(HttpURLConnection.HTTP_PARTIAL)
   1572         .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
   1573         .addHeader("Content-Range: bytes 100-100/200")
   1574         .addHeader("Cache-Control: max-age=60"));
   1575   }
   1576 
   1577   /**
   1578    * Equivalent to {@code CacheTest.conditionalHitUpdatesCache()}, except a Java standard cache has
   1579    * no means to update the headers for an existing entry so the behavior is different.
   1580    */
   1581   @Test public void conditionalHitDoesNotUpdateCache() throws Exception {
   1582     // A response that is cacheable, but with a short life.
   1583     server.enqueue(new MockResponse()
   1584         .addHeader("Last-Modified: " + formatDate(0, TimeUnit.SECONDS))
   1585         .addHeader("Cache-Control: max-age=0")
   1586         .setBody("A"));
   1587     // A response that refers to the previous response, but is cacheable with a long life.
   1588     // Contains a header we can recognize as having come from the server.
   1589     server.enqueue(new MockResponse()
   1590         .addHeader("Cache-Control: max-age=30")
   1591         .addHeader("Allow: GET, HEAD")
   1592         .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
   1593     // A response that is cacheable with a long life.
   1594     server.enqueue(new MockResponse()
   1595         .setBody("B")
   1596         .addHeader("Cache-Control: max-age=30"));
   1597     // A response that should never be requested.
   1598     server.enqueue(new MockResponse()
   1599         .setBody("C"));
   1600 
   1601     // cache miss; seed the cache with an entry that will require a network hit to be sure it is
   1602     // still valid
   1603     HttpURLConnection connection1 = openConnection(server.getUrl("/a"));
   1604     assertEquals("A", readAscii(connection1));
   1605     assertEquals(null, connection1.getHeaderField("Allow"));
   1606 
   1607     // conditional cache hit; The cached data should be returned, but the cache is not updated.
   1608     HttpURLConnection connection2 = openConnection(server.getUrl("/a"));
   1609     assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode());
   1610     assertEquals("A", readAscii(connection2));
   1611     assertEquals("GET, HEAD", connection2.getHeaderField("Allow"));
   1612 
   1613     // conditional cache hit; The server responds with new data. The cache is updated.
   1614     HttpURLConnection connection3 = openConnection(server.getUrl("/a"));
   1615     assertEquals("B", readAscii(connection3));
   1616 
   1617     // full cache hit; The data from connection3 has now replaced that from connection 1.
   1618     HttpURLConnection connection4 = openConnection(server.getUrl("/a"));
   1619     assertEquals("B", readAscii(connection4));
   1620 
   1621     assertEquals(3, server.getRequestCount());
   1622   }
   1623 
   1624   @Test public void responseSourceHeaderCached() throws IOException {
   1625     server.enqueue(new MockResponse()
   1626         .setBody("A")
   1627         .addHeader("Cache-Control: max-age=30")
   1628         .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
   1629 
   1630     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1631     URLConnection connection = openConnection(server.getUrl("/"));
   1632     connection.addRequestProperty("Cache-Control", "only-if-cached");
   1633     assertEquals("A", readAscii(connection));
   1634   }
   1635 
   1636   @Test public void responseSourceHeaderConditionalCacheFetched() throws IOException {
   1637     server.enqueue(new MockResponse()
   1638         .setBody("A")
   1639         .addHeader("Cache-Control: max-age=30")
   1640         .addHeader("Date: " + formatDate(-31, TimeUnit.MINUTES)));
   1641     server.enqueue(new MockResponse()
   1642         .setBody("B")
   1643         .addHeader("Cache-Control: max-age=30")
   1644         .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
   1645 
   1646     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1647     HttpURLConnection connection = openConnection(server.getUrl("/"));
   1648     assertEquals("B", readAscii(connection));
   1649   }
   1650 
   1651   @Test public void responseSourceHeaderConditionalCacheNotFetched() throws IOException {
   1652     server.enqueue(new MockResponse()
   1653         .setBody("A")
   1654         .addHeader("Cache-Control: max-age=0")
   1655         .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
   1656     server.enqueue(new MockResponse()
   1657         .setResponseCode(304));
   1658 
   1659     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   1660     HttpURLConnection connection = openConnection(server.getUrl("/"));
   1661     assertEquals("A", readAscii(connection));
   1662   }
   1663 
   1664   @Test public void responseSourceHeaderFetched() throws IOException {
   1665     server.enqueue(new MockResponse()
   1666         .setBody("A"));
   1667 
   1668     URLConnection connection = openConnection(server.getUrl("/"));
   1669     assertEquals("A", readAscii(connection));
   1670   }
   1671 
   1672   @Test public void emptyResponseHeaderNameFromCacheIsLenient() throws Exception {
   1673     Headers.Builder headers = new Headers.Builder()
   1674         .add("Cache-Control: max-age=120");
   1675     Internal.instance.addLenient(headers, ": A");
   1676     server.enqueue(new MockResponse()
   1677         .setHeaders(headers.build())
   1678         .setBody("body"));
   1679 
   1680     HttpURLConnection connection = openConnection(server.getUrl("/"));
   1681     assertEquals("A", connection.getHeaderField(""));
   1682   }
   1683 
   1684   /**
   1685    * @param delta the offset from the current date to use. Negative
   1686    * values yield dates in the past; positive values yield dates in the
   1687    * future.
   1688    */
   1689   private String formatDate(long delta, TimeUnit timeUnit) {
   1690     return formatDate(new Date(System.currentTimeMillis() + timeUnit.toMillis(delta)));
   1691   }
   1692 
   1693   private String formatDate(Date date) {
   1694     DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
   1695     rfc1123.setTimeZone(TimeZone.getTimeZone("GMT"));
   1696     return rfc1123.format(date);
   1697   }
   1698 
   1699   private void assertNotCached(MockResponse response) throws Exception {
   1700     server.enqueue(response.setBody("A"));
   1701     server.enqueue(new MockResponse()
   1702         .setBody("B"));
   1703 
   1704     URL url = server.getUrl("/");
   1705     assertEquals("A", readAscii(openConnection(url)));
   1706     assertEquals("B", readAscii(openConnection(url)));
   1707   }
   1708 
   1709   /** @return the request with the conditional get headers. */
   1710   private RecordedRequest assertConditionallyCached(MockResponse response) throws Exception {
   1711     // scenario 1: condition succeeds
   1712     server.enqueue(response.setBody("A").setStatus("HTTP/1.1 200 A-OK"));
   1713     server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
   1714 
   1715     // scenario 2: condition fails
   1716     server.enqueue(response.setBody("B").setStatus("HTTP/1.1 200 B-OK"));
   1717     server.enqueue(new MockResponse().setStatus("HTTP/1.1 200 C-OK").setBody("C"));
   1718 
   1719     URL valid = server.getUrl("/valid");
   1720     HttpURLConnection connection1 = openConnection(valid);
   1721     assertEquals("A", readAscii(connection1));
   1722     assertEquals(HttpURLConnection.HTTP_OK, connection1.getResponseCode());
   1723     assertEquals("A-OK", connection1.getResponseMessage());
   1724     HttpURLConnection connection2 = openConnection(valid);
   1725     assertEquals("A", readAscii(connection2));
   1726     assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode());
   1727     assertEquals("A-OK", connection2.getResponseMessage());
   1728 
   1729     URL invalid = server.getUrl("/invalid");
   1730     HttpURLConnection connection3 = openConnection(invalid);
   1731     assertEquals("B", readAscii(connection3));
   1732     assertEquals(HttpURLConnection.HTTP_OK, connection3.getResponseCode());
   1733     assertEquals("B-OK", connection3.getResponseMessage());
   1734     HttpURLConnection connection4 = openConnection(invalid);
   1735     assertEquals("C", readAscii(connection4));
   1736     assertEquals(HttpURLConnection.HTTP_OK, connection4.getResponseCode());
   1737     assertEquals("C-OK", connection4.getResponseMessage());
   1738 
   1739     server.takeRequest(); // regular get
   1740     return server.takeRequest(); // conditional get
   1741   }
   1742 
   1743   private void assertFullyCached(MockResponse response) throws Exception {
   1744     server.enqueue(response.setBody("A"));
   1745     server.enqueue(response.setBody("B"));
   1746 
   1747     URL url = server.getUrl("/");
   1748     assertEquals("A", readAscii(openConnection(url)));
   1749     assertEquals("A", readAscii(openConnection(url)));
   1750   }
   1751 
   1752   /**
   1753    * Shortens the body of {@code response} but not the corresponding headers.
   1754    * Only useful to test how clients respond to the premature conclusion of
   1755    * the HTTP body.
   1756    */
   1757   private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) {
   1758     response.setSocketPolicy(DISCONNECT_AT_END);
   1759     Headers headers = response.getHeaders();
   1760     Buffer truncatedBody = new Buffer();
   1761     truncatedBody.write(response.getBody(), numBytesToKeep);
   1762     response.setBody(truncatedBody);
   1763     response.setHeaders(headers);
   1764     return response;
   1765   }
   1766 
   1767   enum TransferKind {
   1768     CHUNKED() {
   1769       @Override void setBody(MockResponse response, Buffer content, int chunkSize)
   1770           throws IOException {
   1771         response.setChunkedBody(content, chunkSize);
   1772       }
   1773     },
   1774     FIXED_LENGTH() {
   1775       @Override void setBody(MockResponse response, Buffer content, int chunkSize) {
   1776         response.setBody(content);
   1777       }
   1778     },
   1779     END_OF_STREAM() {
   1780       @Override void setBody(MockResponse response, Buffer content, int chunkSize) {
   1781         response.setBody(content);
   1782         response.setSocketPolicy(DISCONNECT_AT_END);
   1783         response.removeHeader("Content-Length");
   1784       }
   1785     };
   1786 
   1787     abstract void setBody(MockResponse response, Buffer content, int chunkSize) throws IOException;
   1788 
   1789     void setBody(MockResponse response, String content, int chunkSize) throws IOException {
   1790       setBody(response, new Buffer().writeUtf8(content), chunkSize);
   1791     }
   1792   }
   1793 
   1794   /** Returns a gzipped copy of {@code bytes}. */
   1795   public Buffer gzip(String data) throws IOException {
   1796     Buffer result = new Buffer();
   1797     BufferedSink sink = Okio.buffer(new GzipSink(result));
   1798     sink.writeUtf8(data);
   1799     sink.close();
   1800     return result;
   1801   }
   1802 
   1803   /**
   1804    * Reads {@code count} characters from the stream. If the stream is
   1805    * exhausted before {@code count} characters can be read, the remaining
   1806    * characters are returned and the stream is closed.
   1807    */
   1808   private String readAscii(URLConnection connection, int count) throws IOException {
   1809     HttpURLConnection httpConnection = (HttpURLConnection) connection;
   1810     InputStream in = httpConnection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST
   1811         ? connection.getInputStream() : httpConnection.getErrorStream();
   1812     StringBuilder result = new StringBuilder();
   1813     for (int i = 0; i < count; i++) {
   1814       int value = in.read();
   1815       if (value == -1) {
   1816         in.close();
   1817         break;
   1818       }
   1819       result.append((char) value);
   1820     }
   1821     return result.toString();
   1822   }
   1823 
   1824   private String readAscii(URLConnection connection) throws IOException {
   1825     return readAscii(connection, Integer.MAX_VALUE);
   1826   }
   1827 
   1828   private void reliableSkip(InputStream in, int length) throws IOException {
   1829     while (length > 0) {
   1830       length -= in.skip(length);
   1831     }
   1832   }
   1833 
   1834   private void assertGatewayTimeout(HttpURLConnection connection) throws IOException {
   1835     try {
   1836       connection.getInputStream();
   1837       fail();
   1838     } catch (FileNotFoundException expected) {
   1839     }
   1840     assertEquals(504, connection.getResponseCode());
   1841     assertEquals(-1, connection.getErrorStream().read());
   1842   }
   1843 
   1844   private static <T> List<T> toListOrNull(T[] arrayOrNull) {
   1845     return arrayOrNull != null ? Arrays.asList(arrayOrNull) : null;
   1846   }
   1847 
   1848   // Android-added tests.
   1849 
   1850   /**
   1851    * Test that we can interrogate the response when the cache is being
   1852    * populated. http://code.google.com/p/android/issues/detail?id=7787
   1853    */
   1854   @Test public void responseCacheCallbackApis() throws Exception {
   1855     final String body = "ABCDE";
   1856     final AtomicInteger cacheCount = new AtomicInteger();
   1857 
   1858     server.enqueue(new MockResponse()
   1859         .setStatus("HTTP/1.1 200 Fantastic")
   1860         .addHeader("Content-Type: text/plain")
   1861         .addHeader("fgh: ijk")
   1862         .setBody(body));
   1863 
   1864     Internal.instance.setCache(client, new CacheAdapter(new AbstractResponseCache() {
   1865       @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
   1866         HttpURLConnection httpURLConnection = (HttpURLConnection) connection;
   1867         assertEquals(server.getUrl("/"), uri.toURL());
   1868         assertEquals(200, httpURLConnection.getResponseCode());
   1869         try {
   1870           httpURLConnection.getInputStream();
   1871           fail();
   1872         } catch (UnsupportedOperationException expected) {
   1873         }
   1874         assertEquals("5", connection.getHeaderField("Content-Length"));
   1875         assertEquals("text/plain", connection.getHeaderField("Content-Type"));
   1876         assertEquals("ijk", connection.getHeaderField("fgh"));
   1877         cacheCount.incrementAndGet();
   1878         return null;
   1879       }
   1880     }));
   1881 
   1882     URL url = server.getUrl("/");
   1883     HttpURLConnection connection = openConnection(url);
   1884     assertEquals(body, readAscii(connection));
   1885     assertEquals(1, cacheCount.get());
   1886   }
   1887 
   1888   /** Don't explode if the cache returns a null body. http://b/3373699 */
   1889   @Test public void responseCacheReturnsNullOutputStream() throws Exception {
   1890     final AtomicBoolean aborted = new AtomicBoolean();
   1891     Internal.instance.setCache(client, new CacheAdapter(new AbstractResponseCache() {
   1892       @Override public CacheRequest put(URI uri, URLConnection connection) {
   1893         return new CacheRequest() {
   1894           @Override public void abort() {
   1895             aborted.set(true);
   1896           }
   1897 
   1898           @Override public OutputStream getBody() throws IOException {
   1899             return null;
   1900           }
   1901         };
   1902       }
   1903     }));
   1904 
   1905     server.enqueue(new MockResponse().setBody("abcdef"));
   1906 
   1907     HttpURLConnection connection = openConnection(server.getUrl("/"));
   1908     assertEquals("abc", readAscii(connection, 3));
   1909     connection.getInputStream().close();
   1910     assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here
   1911   }
   1912 
   1913   /**
   1914    * Fail if a badly-behaved cache returns a null status line header.
   1915    * https://code.google.com/p/android/issues/detail?id=160522
   1916    */
   1917   @Test public void responseCacheReturnsNullStatusLine() throws Exception {
   1918     String cachedContentString = "Hello";
   1919     final byte[] cachedContent = cachedContentString.getBytes(StandardCharsets.US_ASCII);
   1920 
   1921     Internal.instance.setCache(client, new CacheAdapter(new AbstractResponseCache() {
   1922       @Override
   1923       public CacheResponse get(URI uri, String requestMethod,
   1924           Map<String, List<String>> requestHeaders)
   1925           throws IOException {
   1926         return new CacheResponse() {
   1927           @Override public Map<String, List<String>> getHeaders() throws IOException {
   1928             String contentType = "text/plain";
   1929             Map<String, List<String>> headers = new HashMap<>();
   1930             headers.put("Content-Length", Arrays.asList(Integer.toString(cachedContent.length)));
   1931             headers.put("Content-Type", Arrays.asList(contentType));
   1932             headers.put("Expires", Arrays.asList(formatDate(-1, TimeUnit.HOURS)));
   1933             headers.put("Cache-Control", Arrays.asList("max-age=60"));
   1934             // Crucially, the header with a null key is missing, which renders the cache response
   1935             // unusable because OkHttp only caches responses with cacheable response codes.
   1936             return headers;
   1937           }
   1938 
   1939           @Override public InputStream getBody() throws IOException {
   1940             return new ByteArrayInputStream(cachedContent);
   1941           }
   1942         };
   1943       }
   1944     }));
   1945     HttpURLConnection connection = openConnection(server.getUrl("/"));
   1946     // If there was no status line from the cache an exception will be thrown. No network request
   1947     // should be made.
   1948     try {
   1949       connection.getResponseCode();
   1950       fail();
   1951     } catch (ProtocolException expected) {
   1952     }
   1953   }
   1954 
   1955   private static class InsecureResponseCache extends ResponseCache {
   1956 
   1957     private final ResponseCache delegate;
   1958 
   1959     private InsecureResponseCache(ResponseCache delegate) {
   1960       this.delegate = delegate;
   1961     }
   1962 
   1963     @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
   1964       return delegate.put(uri, connection);
   1965     }
   1966 
   1967     @Override public CacheResponse get(URI uri, String requestMethod,
   1968         Map<String, List<String>> requestHeaders) throws IOException {
   1969       final CacheResponse response = delegate.get(uri, requestMethod, requestHeaders);
   1970       if (response instanceof SecureCacheResponse) {
   1971         return new CacheResponse() {
   1972           @Override public InputStream getBody() throws IOException {
   1973             return response.getBody();
   1974           }
   1975           @Override public Map<String, List<String>> getHeaders() throws IOException {
   1976             return response.getHeaders();
   1977           }
   1978         };
   1979       }
   1980       return response;
   1981     }
   1982   }
   1983 
   1984   @Test public void cacheReturnsInsecureResponseForSecureRequest() throws IOException {
   1985     server.useHttps(sslContext.getSocketFactory(), false);
   1986     server.enqueue(new MockResponse().setBody("ABC"));
   1987     server.enqueue(new MockResponse().setBody("DEF"));
   1988 
   1989     AndroidInternal.setResponseCache(new OkUrlFactory(client), new InsecureResponseCache(cache));
   1990 
   1991     HttpsURLConnection connection1 = (HttpsURLConnection) openConnection(server.getUrl("/"));
   1992     connection1.setSSLSocketFactory(sslContext.getSocketFactory());
   1993     connection1.setHostnameVerifier(hostnameVerifier);
   1994     assertEquals("ABC", readAscii(connection1));
   1995 
   1996     // Not cached!
   1997     HttpsURLConnection connection2 = (HttpsURLConnection) openConnection(server.getUrl("/"));
   1998     connection2.setSSLSocketFactory(sslContext.getSocketFactory());
   1999     connection2.setHostnameVerifier(hostnameVerifier);
   2000     assertEquals("DEF", readAscii(connection2));
   2001   }
   2002 
   2003   @Test public void responseCacheRequestHeaders() throws IOException, URISyntaxException {
   2004     server.enqueue(new MockResponse()
   2005         .setBody("ABC"));
   2006 
   2007     final AtomicReference<Map<String, List<String>>> requestHeadersRef = new AtomicReference<>();
   2008     Internal.instance.setCache(client, new CacheAdapter(new AbstractResponseCache() {
   2009       @Override public CacheResponse get(URI uri, String requestMethod,
   2010           Map<String, List<String>> requestHeaders) throws IOException {
   2011         requestHeadersRef.set(requestHeaders);
   2012         return null;
   2013       }
   2014     }));
   2015 
   2016     URL url = server.getUrl("/");
   2017     URLConnection urlConnection = openConnection(url);
   2018     urlConnection.addRequestProperty("A", "android");
   2019     readAscii(urlConnection);
   2020     assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A"));
   2021   }
   2022 
   2023   @Test public void responseCachingWithoutBody() throws IOException {
   2024     MockResponse response =
   2025         new MockResponse().addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
   2026             .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
   2027             .setStatus("HTTP/1.1 200 Fantastic");
   2028     server.enqueue(response);
   2029 
   2030     HttpURLConnection urlConnection = openConnection(server.getUrl("/"));
   2031     assertEquals(200, urlConnection.getResponseCode());
   2032     assertEquals("Fantastic", urlConnection.getResponseMessage());
   2033     assertTrue(urlConnection.getDoInput());
   2034     InputStream is = urlConnection.getInputStream();
   2035     assertEquals(-1, is.read());
   2036     is.close();
   2037 
   2038     urlConnection = openConnection(server.getUrl("/")); // cached!
   2039     assertTrue(urlConnection.getDoInput());
   2040     InputStream cachedIs = urlConnection.getInputStream();
   2041     assertEquals(-1, cachedIs.read());
   2042     cachedIs.close();
   2043     assertEquals(200, urlConnection.getResponseCode());
   2044     assertEquals("Fantastic", urlConnection.getResponseMessage());
   2045   }
   2046 
   2047   @Test public void useCachesFalseDoesNotWriteToCache() throws Exception {
   2048     server.enqueue(new MockResponse()
   2049         .addHeader("Cache-Control: max-age=60")
   2050         .setBody("A"));
   2051     server.enqueue(new MockResponse()
   2052         .setBody("B"));
   2053 
   2054     URLConnection connection = openConnection(server.getUrl("/"));
   2055     connection.setUseCaches(false);
   2056     assertEquals("A", readAscii(connection));
   2057     assertEquals("B", readAscii(openConnection(server.getUrl("/"))));
   2058   }
   2059 
   2060   @Test public void useCachesFalseDoesNotReadFromCache() throws Exception {
   2061     server.enqueue(new MockResponse()
   2062         .addHeader("Cache-Control: max-age=60")
   2063         .setBody("A"));
   2064     server.enqueue(new MockResponse()
   2065         .setBody("B"));
   2066 
   2067     assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
   2068     URLConnection connection = openConnection(server.getUrl("/"));
   2069     connection.setUseCaches(false);
   2070     assertEquals("B", readAscii(connection));
   2071   }
   2072 
   2073   @Test public void defaultUseCachesSetsInitialValueOnly() throws Exception {
   2074     URL url = new URL("http://localhost/");
   2075     URLConnection c1 = openConnection(url);
   2076     URLConnection c2 = openConnection(url);
   2077     assertTrue(c1.getDefaultUseCaches());
   2078     c1.setDefaultUseCaches(false);
   2079     try {
   2080       assertTrue(c1.getUseCaches());
   2081       assertTrue(c2.getUseCaches());
   2082       URLConnection c3 = openConnection(url);
   2083       assertFalse(c3.getUseCaches());
   2084     } finally {
   2085       c1.setDefaultUseCaches(true);
   2086     }
   2087   }
   2088 
   2089   // Other stacks (e.g. older versions of OkHttp bundled inside Android apps) can interact with the
   2090   // default ResponseCache. We try to keep this case working as much as possible because apps break
   2091   // if we don't.
   2092   @Test public void otherStacks_cacheHitWithoutVary() throws Exception {
   2093     server.enqueue(new MockResponse()
   2094         .addHeader("Cache-Control: max-age=60")
   2095         .setBody("A"));
   2096     server.enqueue(new MockResponse()
   2097         .setBody("FAIL"));
   2098 
   2099     // Set the cache as the shared cache.
   2100     ResponseCache.setDefault(cache);
   2101 
   2102     // Use the platform's HTTP stack.
   2103     URLConnection connection = server.getUrl("/").openConnection();
   2104     assertFalse(connection instanceof HttpURLConnectionImpl);
   2105     assertEquals("A", readAscii(connection));
   2106 
   2107     URLConnection connection2 = server.getUrl("/").openConnection();
   2108     assertFalse(connection2 instanceof HttpURLConnectionImpl);
   2109     assertEquals("A", readAscii(connection2));
   2110   }
   2111 
   2112   // Other stacks (e.g. older versions of OkHttp bundled inside Android apps) can interact with the
   2113   // default ResponseCache. We can't keep the Vary case working because we can't get to the Vary
   2114   // request headers after connect(). Accept-Encoding has special behavior so we test it explicitly.
   2115   @Test public void otherStacks_cacheMissWithVaryAcceptEncoding() throws Exception {
   2116     server.enqueue(new MockResponse()
   2117         .addHeader("Cache-Control: max-age=60")
   2118         .addHeader("Vary: Accept-Encoding")
   2119         .setBody("A"));
   2120     server.enqueue(new MockResponse()
   2121         .setBody("B"));
   2122 
   2123     // Set the cache as the shared cache.
   2124     ResponseCache.setDefault(cache);
   2125 
   2126     // Use the platform's HTTP stack.
   2127     URLConnection connection = server.getUrl("/").openConnection();
   2128     assertFalse(connection instanceof HttpURLConnectionImpl);
   2129     assertEquals("A", readAscii(connection));
   2130 
   2131     URLConnection connection2 = server.getUrl("/").openConnection();
   2132     assertFalse(connection2 instanceof HttpURLConnectionImpl);
   2133     assertEquals("B", readAscii(connection2));
   2134   }
   2135 
   2136   // Other stacks (e.g. older versions of OkHttp bundled inside Android apps) can interact with the
   2137   // default ResponseCache. We can't keep the Vary case working because we can't get to the Vary
   2138   // request headers after connect().
   2139   @Test public void otherStacks_cacheMissWithVary() throws Exception {
   2140     server.enqueue(new MockResponse()
   2141         .addHeader("Cache-Control: max-age=60")
   2142         .addHeader("Vary: Accept-Language")
   2143         .setBody("A"));
   2144     server.enqueue(new MockResponse()
   2145         .setBody("B"));
   2146 
   2147     // Set the cache as the shared cache.
   2148     ResponseCache.setDefault(cache);
   2149 
   2150     // Use the platform's HTTP stack.
   2151     URLConnection connection = server.getUrl("/").openConnection();
   2152     assertFalse(connection instanceof HttpURLConnectionImpl);
   2153     connection.setRequestProperty("Accept-Language", "en-US");
   2154     assertEquals("A", readAscii(connection));
   2155 
   2156     URLConnection connection2 = server.getUrl("/").openConnection();
   2157     assertFalse(connection2 instanceof HttpURLConnectionImpl);
   2158     assertEquals("B", readAscii(connection2));
   2159   }
   2160 
   2161   // Other stacks (e.g. older versions of OkHttp bundled inside Android apps) can interact with the
   2162   // default ResponseCache. We can't keep the Vary case working, because we can't get to the Vary
   2163   // request headers after connect().
   2164   @Test public void otherStacks_cacheMissWithVaryAsterisk() throws Exception {
   2165     server.enqueue(new MockResponse()
   2166         .addHeader("Cache-Control: max-age=60")
   2167         .addHeader("Vary: *")
   2168         .setBody("A"));
   2169     server.enqueue(new MockResponse()
   2170         .setBody("B"));
   2171 
   2172     // Set the cache as the shared cache.
   2173     ResponseCache.setDefault(cache);
   2174 
   2175     // Use the platform's HTTP stack.
   2176     URLConnection connection = server.getUrl("/").openConnection();
   2177     assertFalse(connection instanceof HttpURLConnectionImpl);
   2178     assertEquals("A", readAscii(connection));
   2179 
   2180     URLConnection connection2 = server.getUrl("/").openConnection();
   2181     assertFalse(connection2 instanceof HttpURLConnectionImpl);
   2182     assertEquals("B", readAscii(connection2));
   2183   }
   2184 }
   2185