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