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