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