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