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