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