HttpResponseCacheTest.java revision 5d7e0fc1af3141aa41e9c21d74da3c36b933517f
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 17package libcore.net.http; 18 19import com.google.mockwebserver.MockResponse; 20import com.google.mockwebserver.MockWebServer; 21import com.google.mockwebserver.RecordedRequest; 22import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END; 23import java.io.BufferedReader; 24import java.io.ByteArrayOutputStream; 25import java.io.File; 26import java.io.FileNotFoundException; 27import java.io.IOException; 28import java.io.InputStream; 29import java.io.InputStreamReader; 30import java.io.OutputStream; 31import java.lang.reflect.InvocationHandler; 32import java.net.CacheRequest; 33import java.net.CacheResponse; 34import java.net.CookieHandler; 35import java.net.CookieManager; 36import java.net.HttpCookie; 37import java.net.HttpURLConnection; 38import java.net.ResponseCache; 39import java.net.SecureCacheResponse; 40import java.net.URI; 41import java.net.URISyntaxException; 42import java.net.URL; 43import java.net.URLConnection; 44import java.security.Principal; 45import java.security.cert.Certificate; 46import java.text.DateFormat; 47import java.text.SimpleDateFormat; 48import java.util.ArrayList; 49import java.util.Arrays; 50import java.util.Collections; 51import java.util.Date; 52import java.util.Deque; 53import java.util.Iterator; 54import java.util.List; 55import java.util.Locale; 56import java.util.Map; 57import java.util.TimeZone; 58import java.util.UUID; 59import java.util.concurrent.TimeUnit; 60import java.util.concurrent.atomic.AtomicInteger; 61import java.util.concurrent.atomic.AtomicReference; 62import java.util.zip.GZIPOutputStream; 63import javax.net.ssl.HttpsURLConnection; 64import junit.framework.TestCase; 65import libcore.javax.net.ssl.TestSSLContext; 66import tests.io.MockOs; 67 68public final class HttpResponseCacheTest extends TestCase { 69 private MockWebServer server = new MockWebServer(); 70 private HttpResponseCache cache; 71 private final MockOs mockOs = new MockOs(); 72 private final CookieManager cookieManager = new CookieManager(); 73 74 @Override protected void setUp() throws Exception { 75 super.setUp(); 76 77 String tmp = System.getProperty("java.io.tmpdir"); 78 File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID()); 79 cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE); 80 ResponseCache.setDefault(cache); 81 mockOs.install(); 82 CookieHandler.setDefault(cookieManager); 83 } 84 85 @Override protected void tearDown() throws Exception { 86 mockOs.uninstall(); 87 server.shutdown(); 88 ResponseCache.setDefault(null); 89 cache.getCache().delete(); 90 CookieHandler.setDefault(null); 91 super.tearDown(); 92 } 93 94 /** 95 * Test that response caching is consistent with the RI and the spec. 96 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4 97 */ 98 public void testResponseCachingByResponseCode() throws Exception { 99 // Test each documented HTTP/1.1 code, plus the first unused value in each range. 100 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 101 102 // We can't test 100 because it's not really a response. 103 // assertCached(false, 100); 104 assertCached(false, 101); 105 assertCached(false, 102); 106 assertCached(true, 200); 107 assertCached(false, 201); 108 assertCached(false, 202); 109 assertCached(true, 203); 110 assertCached(false, 204); 111 assertCached(false, 205); 112 assertCached(false, 206); // we don't cache partial responses 113 assertCached(false, 207); 114 assertCached(true, 300); 115 assertCached(true, 301); 116 for (int i = 302; i <= 308; ++i) { 117 assertCached(false, i); 118 } 119 for (int i = 400; i <= 406; ++i) { 120 assertCached(false, i); 121 } 122 // (See test_responseCaching_407.) 123 assertCached(false, 408); 124 assertCached(false, 409); 125 // (See test_responseCaching_410.) 126 for (int i = 411; i <= 418; ++i) { 127 assertCached(false, i); 128 } 129 for (int i = 500; i <= 506; ++i) { 130 assertCached(false, i); 131 } 132 } 133 134 /** 135 * Response code 407 should only come from proxy servers. Android's client 136 * throws if it is sent by an origin server. 137 */ 138 public void testOriginServerSends407() throws Exception { 139 server.enqueue(new MockResponse().setResponseCode(407)); 140 server.play(); 141 142 URL url = server.getUrl("/"); 143 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 144 try { 145 conn.getResponseCode(); 146 fail(); 147 } catch (IOException expected) { 148 } 149 } 150 151 public void test_responseCaching_410() throws Exception { 152 // the HTTP spec permits caching 410s, but the RI doesn't. 153 assertCached(true, 410); 154 } 155 156 private void assertCached(boolean shouldPut, int responseCode) throws Exception { 157 server = new MockWebServer(); 158 MockResponse response = 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 response.addHeader("Proxy-Authenticate: Basic realm=\"protected area\""); 166 } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { 167 response.addHeader("WWW-Authenticate: Basic realm=\"protected area\""); 168 } 169 server.enqueue(response); 170 server.play(); 171 172 URL url = server.getUrl("/"); 173 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 174 assertEquals(responseCode, conn.getResponseCode()); 175 176 // exhaust the content stream 177 readAscii(conn); 178 179 CacheResponse cached = cache.get(url.toURI(), "GET", 180 Collections.<String, List<String>>emptyMap()); 181 if (shouldPut) { 182 assertNotNull(Integer.toString(responseCode), cached); 183 cached.getBody().close(); 184 } else { 185 assertNull(Integer.toString(responseCode), cached); 186 } 187 server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers 188 } 189 190 /** 191 * Test that we can interrogate the response when the cache is being 192 * populated. http://code.google.com/p/android/issues/detail?id=7787 193 */ 194 public void testResponseCacheCallbackApis() throws Exception { 195 final String body = "ABCDE"; 196 final AtomicInteger cacheCount = new AtomicInteger(); 197 198 server.enqueue(new MockResponse() 199 .setStatus("HTTP/1.1 200 Fantastic") 200 .addHeader("fgh: ijk") 201 .setBody(body)); 202 server.play(); 203 204 ResponseCache.setDefault(new ResponseCache() { 205 @Override public CacheResponse get(URI uri, String requestMethod, 206 Map<String, List<String>> requestHeaders) throws IOException { 207 return null; 208 } 209 @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException { 210 HttpURLConnection httpConnection = (HttpURLConnection) conn; 211 try { 212 httpConnection.getRequestProperties(); 213 fail(); 214 } catch (IllegalStateException expected) { 215 } 216 try { 217 httpConnection.addRequestProperty("K", "V"); 218 fail(); 219 } catch (IllegalStateException expected) { 220 } 221 assertEquals("HTTP/1.1 200 Fantastic", httpConnection.getHeaderField(null)); 222 assertEquals(Arrays.asList("HTTP/1.1 200 Fantastic"), 223 httpConnection.getHeaderFields().get(null)); 224 assertEquals(200, httpConnection.getResponseCode()); 225 assertEquals("Fantastic", httpConnection.getResponseMessage()); 226 assertEquals(body.length(), httpConnection.getContentLength()); 227 assertEquals("ijk", httpConnection.getHeaderField("fgh")); 228 try { 229 httpConnection.getInputStream(); // the RI doesn't forbid this, but it should 230 fail(); 231 } catch (IOException expected) { 232 } 233 cacheCount.incrementAndGet(); 234 return null; 235 } 236 }); 237 238 URL url = server.getUrl("/"); 239 URLConnection connection = url.openConnection(); 240 assertEquals(body, readAscii(connection)); 241 assertEquals(1, cacheCount.get()); 242 } 243 244 245 public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException { 246 testResponseCaching(TransferKind.FIXED_LENGTH); 247 } 248 249 public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException { 250 testResponseCaching(TransferKind.CHUNKED); 251 } 252 253 public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException { 254 testResponseCaching(TransferKind.END_OF_STREAM); 255 } 256 257 /** 258 * HttpURLConnection.getInputStream().skip(long) causes ResponseCache corruption 259 * http://code.google.com/p/android/issues/detail?id=8175 260 */ 261 private void testResponseCaching(TransferKind transferKind) throws IOException { 262 MockResponse response = new MockResponse() 263 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 264 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) 265 .setStatus("HTTP/1.1 200 Fantastic"); 266 transferKind.setBody(response, "I love puppies but hate spiders", 1); 267 server.enqueue(response); 268 server.play(); 269 270 // Make sure that calling skip() doesn't omit bytes from the cache. 271 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 272 InputStream in = urlConnection.getInputStream(); 273 assertEquals("I love ", readAscii(urlConnection, "I love ".length())); 274 reliableSkip(in, "puppies but hate ".length()); 275 assertEquals("spiders", readAscii(urlConnection, "spiders".length())); 276 assertEquals(-1, in.read()); 277 in.close(); 278 assertEquals(1, cache.getWriteSuccessCount()); 279 assertEquals(0, cache.getWriteAbortCount()); 280 281 urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached! 282 in = urlConnection.getInputStream(); 283 assertEquals("I love puppies but hate spiders", 284 readAscii(urlConnection, "I love puppies but hate spiders".length())); 285 assertEquals(200, urlConnection.getResponseCode()); 286 assertEquals("Fantastic", urlConnection.getResponseMessage()); 287 288 assertEquals(-1, in.read()); 289 in.close(); 290 assertEquals(1, cache.getWriteSuccessCount()); 291 assertEquals(0, cache.getWriteAbortCount()); 292 assertEquals(2, cache.getRequestCount()); 293 assertEquals(1, cache.getHitCount()); 294 } 295 296 public void testSecureResponseCaching() throws IOException { 297 TestSSLContext testSSLContext = TestSSLContext.create(); 298 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 299 server.enqueue(new MockResponse() 300 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 301 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) 302 .setBody("ABC")); 303 server.play(); 304 305 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 306 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 307 assertEquals("ABC", readAscii(connection)); 308 309 // OpenJDK 6 fails on this line, complaining that the connection isn't open yet 310 String suite = connection.getCipherSuite(); 311 List<Certificate> localCerts = toListOrNull(connection.getLocalCertificates()); 312 List<Certificate> serverCerts = toListOrNull(connection.getServerCertificates()); 313 Principal peerPrincipal = connection.getPeerPrincipal(); 314 Principal localPrincipal = connection.getLocalPrincipal(); 315 316 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached! 317 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 318 assertEquals("ABC", readAscii(connection)); 319 320 assertEquals(2, cache.getRequestCount()); 321 assertEquals(1, cache.getNetworkCount()); 322 assertEquals(1, cache.getHitCount()); 323 324 assertEquals(suite, connection.getCipherSuite()); 325 assertEquals(localCerts, toListOrNull(connection.getLocalCertificates())); 326 assertEquals(serverCerts, toListOrNull(connection.getServerCertificates())); 327 assertEquals(peerPrincipal, connection.getPeerPrincipal()); 328 assertEquals(localPrincipal, connection.getLocalPrincipal()); 329 } 330 331 public void testCacheReturnsInsecureResponseForSecureRequest() throws IOException { 332 TestSSLContext testSSLContext = TestSSLContext.create(); 333 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 334 server.enqueue(new MockResponse().setBody("ABC")); 335 server.enqueue(new MockResponse().setBody("DEF")); 336 server.play(); 337 338 ResponseCache.setDefault(new InsecureResponseCache()); 339 340 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 341 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 342 assertEquals("ABC", readAscii(connection)); 343 344 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // not cached! 345 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 346 assertEquals("DEF", readAscii(connection)); 347 } 348 349 public void testResponseCachingAndRedirects() throws Exception { 350 server.enqueue(new MockResponse() 351 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 352 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) 353 .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM) 354 .addHeader("Location: /foo")); 355 server.enqueue(new MockResponse() 356 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 357 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) 358 .setBody("ABC")); 359 server.enqueue(new MockResponse().setBody("DEF")); 360 server.play(); 361 362 URLConnection connection = server.getUrl("/").openConnection(); 363 assertEquals("ABC", readAscii(connection)); 364 365 connection = server.getUrl("/").openConnection(); // cached! 366 assertEquals("ABC", readAscii(connection)); 367 368 assertEquals(4, cache.getRequestCount()); // 2 requests + 2 redirects 369 assertEquals(2, cache.getNetworkCount()); 370 assertEquals(2, cache.getHitCount()); 371 } 372 373 public void testRedirectToCachedResult() throws Exception { 374 server.enqueue(new MockResponse() 375 .addHeader("Cache-Control: max-age=60") 376 .setBody("ABC")); 377 server.enqueue(new MockResponse() 378 .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM) 379 .addHeader("Location: /foo")); 380 server.enqueue(new MockResponse().setBody("DEF")); 381 server.play(); 382 383 assertEquals("ABC", readAscii(server.getUrl("/foo").openConnection())); 384 RecordedRequest request1 = server.takeRequest(); 385 assertEquals("GET /foo HTTP/1.1", request1.getRequestLine()); 386 assertEquals(0, request1.getSequenceNumber()); 387 388 assertEquals("ABC", readAscii(server.getUrl("/bar").openConnection())); 389 RecordedRequest request2 = server.takeRequest(); 390 assertEquals("GET /bar HTTP/1.1", request2.getRequestLine()); 391 assertEquals(1, request2.getSequenceNumber()); 392 393 // an unrelated request should reuse the pooled connection 394 assertEquals("DEF", readAscii(server.getUrl("/baz").openConnection())); 395 RecordedRequest request3 = server.takeRequest(); 396 assertEquals("GET /baz HTTP/1.1", request3.getRequestLine()); 397 assertEquals(2, request3.getSequenceNumber()); 398 } 399 400 public void testSecureResponseCachingAndRedirects() throws IOException { 401 TestSSLContext testSSLContext = TestSSLContext.create(); 402 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 403 server.enqueue(new MockResponse() 404 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 405 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) 406 .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM) 407 .addHeader("Location: /foo")); 408 server.enqueue(new MockResponse() 409 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 410 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) 411 .setBody("ABC")); 412 server.enqueue(new MockResponse().setBody("DEF")); 413 server.play(); 414 415 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 416 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 417 assertEquals("ABC", readAscii(connection)); 418 419 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached! 420 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 421 assertEquals("ABC", readAscii(connection)); 422 423 assertEquals(4, cache.getRequestCount()); // 2 direct + 2 redirect = 4 424 assertEquals(2, cache.getHitCount()); 425 } 426 427 public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException { 428 server.enqueue(new MockResponse().setBody("ABC")); 429 server.play(); 430 431 final AtomicReference<Map<String, List<String>>> requestHeadersRef 432 = new AtomicReference<Map<String, List<String>>>(); 433 ResponseCache.setDefault(new ResponseCache() { 434 @Override public CacheResponse get(URI uri, String requestMethod, 435 Map<String, List<String>> requestHeaders) throws IOException { 436 requestHeadersRef.set(requestHeaders); 437 return null; 438 } 439 @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException { 440 return null; 441 } 442 }); 443 444 URL url = server.getUrl("/"); 445 URLConnection urlConnection = url.openConnection(); 446 urlConnection.addRequestProperty("A", "android"); 447 readAscii(urlConnection); 448 assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A")); 449 } 450 451 452 public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException { 453 testServerPrematureDisconnect(TransferKind.FIXED_LENGTH); 454 } 455 456 public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException { 457 testServerPrematureDisconnect(TransferKind.CHUNKED); 458 } 459 460 public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException { 461 /* 462 * Intentionally empty. This case doesn't make sense because there's no 463 * such thing as a premature disconnect when the disconnect itself 464 * indicates the end of the data stream. 465 */ 466 } 467 468 private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException { 469 MockResponse response = new MockResponse(); 470 transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16); 471 server.enqueue(truncateViolently(response, 16)); 472 server.enqueue(new MockResponse().setBody("Request #2")); 473 server.play(); 474 475 BufferedReader reader = new BufferedReader(new InputStreamReader( 476 server.getUrl("/").openConnection().getInputStream())); 477 assertEquals("ABCDE", reader.readLine()); 478 try { 479 reader.readLine(); 480 fail("This implementation silently ignored a truncated HTTP body."); 481 } catch (IOException expected) { 482 } finally { 483 reader.close(); 484 } 485 486 assertEquals(1, cache.getWriteAbortCount()); 487 assertEquals(0, cache.getWriteSuccessCount()); 488 URLConnection connection = server.getUrl("/").openConnection(); 489 assertEquals("Request #2", readAscii(connection)); 490 assertEquals(1, cache.getWriteAbortCount()); 491 assertEquals(1, cache.getWriteSuccessCount()); 492 } 493 494 public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException { 495 testClientPrematureDisconnect(TransferKind.FIXED_LENGTH); 496 } 497 498 public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException { 499 testClientPrematureDisconnect(TransferKind.CHUNKED); 500 } 501 502 public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException { 503 testClientPrematureDisconnect(TransferKind.END_OF_STREAM); 504 } 505 506 private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException { 507 MockResponse response = new MockResponse(); 508 transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024); 509 server.enqueue(response); 510 server.enqueue(new MockResponse().setBody("Request #2")); 511 server.play(); 512 513 URLConnection connection = server.getUrl("/").openConnection(); 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 assertEquals(1, cache.getWriteAbortCount()); 524 assertEquals(0, cache.getWriteSuccessCount()); 525 connection = server.getUrl("/").openConnection(); 526 assertEquals("Request #2", readAscii(connection)); 527 assertEquals(1, cache.getWriteAbortCount()); 528 assertEquals(1, cache.getWriteSuccessCount()); 529 } 530 531 public void testDefaultExpirationDateFullyCachedForLessThan24Hours() throws Exception { 532 // last modified: 105 seconds ago 533 // served: 5 seconds ago 534 // default lifetime: (105 - 5) / 10 = 10 seconds 535 // expires: 10 seconds from served date = 5 seconds from now 536 server.enqueue(new MockResponse() 537 .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS)) 538 .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS)) 539 .setBody("A")); 540 server.play(); 541 542 URL url = server.getUrl("/"); 543 assertEquals("A", readAscii(url.openConnection())); 544 URLConnection connection = url.openConnection(); 545 assertEquals("A", readAscii(connection)); 546 assertNull(connection.getHeaderField("Warning")); 547 } 548 549 public void testDefaultExpirationDateConditionallyCached() throws Exception { 550 // last modified: 115 seconds ago 551 // served: 15 seconds ago 552 // default lifetime: (115 - 15) / 10 = 10 seconds 553 // expires: 10 seconds from served date = 5 seconds ago 554 String lastModifiedDate = formatDate(-115, TimeUnit.SECONDS); 555 RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() 556 .addHeader("Last-Modified: " + lastModifiedDate) 557 .addHeader("Date: " + formatDate(-15, TimeUnit.SECONDS))); 558 List<String> headers = conditionalRequest.getHeaders(); 559 assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate)); 560 } 561 562 public void testDefaultExpirationDateFullyCachedForMoreThan24Hours() throws Exception { 563 // last modified: 105 days ago 564 // served: 5 days ago 565 // default lifetime: (105 - 5) / 10 = 10 days 566 // expires: 10 days from served date = 5 days from now 567 server.enqueue(new MockResponse() 568 .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.DAYS)) 569 .addHeader("Date: " + formatDate(-5, TimeUnit.DAYS)) 570 .setBody("A")); 571 server.play(); 572 573 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 574 URLConnection connection = server.getUrl("/").openConnection(); 575 assertEquals("A", readAscii(connection)); 576 assertEquals("113 HttpURLConnection \"Heuristic expiration\"", 577 connection.getHeaderField("Warning")); 578 } 579 580 public void testNoDefaultExpirationForUrlsWithQueryString() throws Exception { 581 server.enqueue(new MockResponse() 582 .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS)) 583 .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS)) 584 .setBody("A")); 585 server.enqueue(new MockResponse().setBody("B")); 586 server.play(); 587 588 URL url = server.getUrl("/?foo=bar"); 589 assertEquals("A", readAscii(url.openConnection())); 590 assertEquals("B", readAscii(url.openConnection())); 591 } 592 593 public void testExpirationDateInThePastWithLastModifiedHeader() throws Exception { 594 String lastModifiedDate = formatDate(-2, TimeUnit.HOURS); 595 RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() 596 .addHeader("Last-Modified: " + lastModifiedDate) 597 .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); 598 List<String> headers = conditionalRequest.getHeaders(); 599 assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate)); 600 } 601 602 public void testExpirationDateInThePastWithNoLastModifiedHeader() throws Exception { 603 assertNotCached(new MockResponse() 604 .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); 605 } 606 607 public void testExpirationDateInTheFuture() throws Exception { 608 assertFullyCached(new MockResponse() 609 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); 610 } 611 612 public void testMaxAgePreferredWithMaxAgeAndExpires() throws Exception { 613 assertFullyCached(new MockResponse() 614 .addHeader("Date: " + formatDate(0, TimeUnit.HOURS)) 615 .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)) 616 .addHeader("Cache-Control: max-age=60")); 617 } 618 619 public void testMaxAgeInThePastWithDateAndLastModifiedHeaders() throws Exception { 620 String lastModifiedDate = formatDate(-2, TimeUnit.HOURS); 621 RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() 622 .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS)) 623 .addHeader("Last-Modified: " + lastModifiedDate) 624 .addHeader("Cache-Control: max-age=60")); 625 List<String> headers = conditionalRequest.getHeaders(); 626 assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate)); 627 } 628 629 public void testMaxAgeInThePastWithDateHeaderButNoLastModifiedHeader() throws Exception { 630 /* 631 * Chrome interprets max-age relative to the local clock. Both our cache 632 * and Firefox both use the earlier of the local and server's clock. 633 */ 634 assertNotCached(new MockResponse() 635 .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS)) 636 .addHeader("Cache-Control: max-age=60")); 637 } 638 639 public void testMaxAgeInTheFutureWithDateHeader() throws Exception { 640 assertFullyCached(new MockResponse() 641 .addHeader("Date: " + formatDate(0, TimeUnit.HOURS)) 642 .addHeader("Cache-Control: max-age=60")); 643 } 644 645 public void testMaxAgeInTheFutureWithNoDateHeader() throws Exception { 646 assertFullyCached(new MockResponse() 647 .addHeader("Cache-Control: max-age=60")); 648 } 649 650 public void testMaxAgeWithLastModifiedButNoServedDate() throws Exception { 651 assertFullyCached(new MockResponse() 652 .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS)) 653 .addHeader("Cache-Control: max-age=60")); 654 } 655 656 public void testMaxAgeInTheFutureWithDateAndLastModifiedHeaders() throws Exception { 657 assertFullyCached(new MockResponse() 658 .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS)) 659 .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS)) 660 .addHeader("Cache-Control: max-age=60")); 661 } 662 663 public void testMaxAgePreferredOverLowerSharedMaxAge() throws Exception { 664 assertFullyCached(new MockResponse() 665 .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES)) 666 .addHeader("Cache-Control: s-maxage=60") 667 .addHeader("Cache-Control: max-age=180")); 668 } 669 670 public void testMaxAgePreferredOverHigherMaxAge() throws Exception { 671 assertNotCached(new MockResponse() 672 .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES)) 673 .addHeader("Cache-Control: s-maxage=180") 674 .addHeader("Cache-Control: max-age=60")); 675 } 676 677 public void testRequestMethodOptionsIsNotCached() throws Exception { 678 testRequestMethod("OPTIONS", false); 679 } 680 681 public void testRequestMethodGetIsCached() throws Exception { 682 testRequestMethod("GET", true); 683 } 684 685 public void testRequestMethodHeadIsNotCached() throws Exception { 686 // We could support this but choose not to for implementation simplicity 687 testRequestMethod("HEAD", false); 688 } 689 690 public void testRequestMethodPostIsNotCached() throws Exception { 691 // We could support this but choose not to for implementation simplicity 692 testRequestMethod("POST", false); 693 } 694 695 public void testRequestMethodPutIsNotCached() throws Exception { 696 testRequestMethod("PUT", false); 697 } 698 699 public void testRequestMethodDeleteIsNotCached() throws Exception { 700 testRequestMethod("DELETE", false); 701 } 702 703 public void testRequestMethodTraceIsNotCached() throws Exception { 704 testRequestMethod("TRACE", false); 705 } 706 707 private void testRequestMethod(String requestMethod, boolean expectCached) throws Exception { 708 /* 709 * 1. seed the cache (potentially) 710 * 2. expect a cache hit or miss 711 */ 712 server.enqueue(new MockResponse() 713 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) 714 .addHeader("X-Response-ID: 1")); 715 server.enqueue(new MockResponse() 716 .addHeader("X-Response-ID: 2")); 717 server.play(); 718 719 URL url = server.getUrl("/"); 720 721 HttpURLConnection request1 = (HttpURLConnection) url.openConnection(); 722 request1.setRequestMethod(requestMethod); 723 addRequestBodyIfNecessary(requestMethod, request1); 724 assertEquals("1", request1.getHeaderField("X-Response-ID")); 725 726 URLConnection request2 = url.openConnection(); 727 if (expectCached) { 728 assertEquals("1", request1.getHeaderField("X-Response-ID")); 729 } else { 730 assertEquals("2", request2.getHeaderField("X-Response-ID")); 731 } 732 } 733 734 public void testPostInvalidatesCache() throws Exception { 735 testMethodInvalidates("POST"); 736 } 737 738 public void testPutInvalidatesCache() throws Exception { 739 testMethodInvalidates("PUT"); 740 } 741 742 public void testDeleteMethodInvalidatesCache() throws Exception { 743 testMethodInvalidates("DELETE"); 744 } 745 746 private void testMethodInvalidates(String requestMethod) throws Exception { 747 /* 748 * 1. seed the cache 749 * 2. invalidate it 750 * 3. expect a cache miss 751 */ 752 server.enqueue(new MockResponse().setBody("A") 753 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); 754 server.enqueue(new MockResponse().setBody("B")); 755 server.enqueue(new MockResponse().setBody("C")); 756 server.play(); 757 758 URL url = server.getUrl("/"); 759 760 assertEquals("A", readAscii(url.openConnection())); 761 762 HttpURLConnection invalidate = (HttpURLConnection) url.openConnection(); 763 invalidate.setRequestMethod(requestMethod); 764 addRequestBodyIfNecessary(requestMethod, invalidate); 765 assertEquals("B", readAscii(invalidate)); 766 767 assertEquals("C", readAscii(url.openConnection())); 768 } 769 770 public void testEtag() throws Exception { 771 RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() 772 .addHeader("ETag: v1")); 773 assertTrue(conditionalRequest.getHeaders().contains("If-None-Match: v1")); 774 } 775 776 public void testEtagAndExpirationDateInThePast() throws Exception { 777 String lastModifiedDate = formatDate(-2, TimeUnit.HOURS); 778 RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() 779 .addHeader("ETag: v1") 780 .addHeader("Last-Modified: " + lastModifiedDate) 781 .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); 782 List<String> headers = conditionalRequest.getHeaders(); 783 assertTrue(headers.contains("If-None-Match: v1")); 784 assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate)); 785 } 786 787 public void testEtagAndExpirationDateInTheFuture() throws Exception { 788 assertFullyCached(new MockResponse() 789 .addHeader("ETag: v1") 790 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) 791 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); 792 } 793 794 public void testCacheControlNoCache() throws Exception { 795 assertNotCached(new MockResponse().addHeader("Cache-Control: no-cache")); 796 } 797 798 public void testCacheControlNoCacheAndExpirationDateInTheFuture() throws Exception { 799 String lastModifiedDate = formatDate(-2, TimeUnit.HOURS); 800 RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() 801 .addHeader("Last-Modified: " + lastModifiedDate) 802 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) 803 .addHeader("Cache-Control: no-cache")); 804 List<String> headers = conditionalRequest.getHeaders(); 805 assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate)); 806 } 807 808 public void testPragmaNoCache() throws Exception { 809 assertNotCached(new MockResponse().addHeader("Pragma: no-cache")); 810 } 811 812 public void testPragmaNoCacheAndExpirationDateInTheFuture() throws Exception { 813 String lastModifiedDate = formatDate(-2, TimeUnit.HOURS); 814 RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() 815 .addHeader("Last-Modified: " + lastModifiedDate) 816 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) 817 .addHeader("Pragma: no-cache")); 818 List<String> headers = conditionalRequest.getHeaders(); 819 assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate)); 820 } 821 822 public void testCacheControlNoStore() throws Exception { 823 assertNotCached(new MockResponse().addHeader("Cache-Control: no-store")); 824 } 825 826 public void testCacheControlNoStoreAndExpirationDateInTheFuture() throws Exception { 827 assertNotCached(new MockResponse() 828 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) 829 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) 830 .addHeader("Cache-Control: no-store")); 831 } 832 833 public void testPartialRangeResponsesDoNotCorruptCache() throws Exception { 834 /* 835 * 1. request a range 836 * 2. request a full document, expecting a cache miss 837 */ 838 server.enqueue(new MockResponse().setBody("AA") 839 .setResponseCode(HttpURLConnection.HTTP_PARTIAL) 840 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) 841 .addHeader("Content-Range: bytes 1000-1001/2000")); 842 server.enqueue(new MockResponse().setBody("BB")); 843 server.play(); 844 845 URL url = server.getUrl("/"); 846 847 URLConnection range = url.openConnection(); 848 range.addRequestProperty("Range", "bytes=1000-1001"); 849 assertEquals("AA", readAscii(range)); 850 851 assertEquals("BB", readAscii(url.openConnection())); 852 } 853 854 public void testServerReturnsDocumentOlderThanCache() throws Exception { 855 server.enqueue(new MockResponse().setBody("A") 856 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) 857 .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); 858 server.enqueue(new MockResponse().setBody("B") 859 .addHeader("Last-Modified: " + formatDate(-4, TimeUnit.HOURS))); 860 server.play(); 861 862 URL url = server.getUrl("/"); 863 864 assertEquals("A", readAscii(url.openConnection())); 865 assertEquals("A", readAscii(url.openConnection())); 866 } 867 868 public void testNonIdentityEncodingAndConditionalCache() throws Exception { 869 assertNonIdentityEncodingCached(new MockResponse() 870 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) 871 .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); 872 } 873 874 public void testNonIdentityEncodingAndFullCache() throws Exception { 875 assertNonIdentityEncodingCached(new MockResponse() 876 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) 877 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); 878 } 879 880 private void assertNonIdentityEncodingCached(MockResponse response) throws Exception { 881 server.enqueue(response 882 .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) 883 .addHeader("Content-Encoding: gzip")); 884 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 885 886 server.play(); 887 assertEquals("ABCABCABC", readAscii(server.getUrl("/").openConnection())); 888 assertEquals("ABCABCABC", readAscii(server.getUrl("/").openConnection())); 889 } 890 891 public void testExpiresDateBeforeModifiedDate() throws Exception { 892 assertConditionallyCached(new MockResponse() 893 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 894 .addHeader("Expires: " + formatDate(-2, TimeUnit.HOURS))); 895 } 896 897 public void testRequestMaxAge() throws IOException { 898 server.enqueue(new MockResponse().setBody("A") 899 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) 900 .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES)) 901 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); 902 server.enqueue(new MockResponse().setBody("B")); 903 904 server.play(); 905 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 906 907 URLConnection connection = server.getUrl("/").openConnection(); 908 connection.addRequestProperty("Cache-Control", "max-age=30"); 909 assertEquals("B", readAscii(connection)); 910 } 911 912 public void testRequestMinFresh() throws IOException { 913 server.enqueue(new MockResponse().setBody("A") 914 .addHeader("Cache-Control: max-age=60") 915 .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES))); 916 server.enqueue(new MockResponse().setBody("B")); 917 918 server.play(); 919 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 920 921 URLConnection connection = server.getUrl("/").openConnection(); 922 connection.addRequestProperty("Cache-Control", "min-fresh=120"); 923 assertEquals("B", readAscii(connection)); 924 } 925 926 public void testRequestMaxStale() throws IOException { 927 server.enqueue(new MockResponse().setBody("A") 928 .addHeader("Cache-Control: max-age=120") 929 .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES))); 930 server.enqueue(new MockResponse().setBody("B")); 931 932 server.play(); 933 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 934 935 URLConnection connection = server.getUrl("/").openConnection(); 936 connection.addRequestProperty("Cache-Control", "max-stale=180"); 937 assertEquals("A", readAscii(connection)); 938 assertEquals("110 HttpURLConnection \"Response is stale\"", 939 connection.getHeaderField("Warning")); 940 } 941 942 public void testRequestMaxStaleNotHonoredWithMustRevalidate() throws IOException { 943 server.enqueue(new MockResponse().setBody("A") 944 .addHeader("Cache-Control: max-age=120, must-revalidate") 945 .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES))); 946 server.enqueue(new MockResponse().setBody("B")); 947 948 server.play(); 949 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 950 951 URLConnection connection = server.getUrl("/").openConnection(); 952 connection.addRequestProperty("Cache-Control", "max-stale=180"); 953 assertEquals("B", readAscii(connection)); 954 } 955 956 public void testRequestOnlyIfCachedWithNoResponseCached() throws IOException { 957 // (no responses enqueued) 958 server.play(); 959 960 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 961 connection.addRequestProperty("Cache-Control", "only-if-cached"); 962 assertBadGateway(connection); 963 } 964 965 public void testRequestOnlyIfCachedWithFullResponseCached() throws IOException { 966 server.enqueue(new MockResponse().setBody("A") 967 .addHeader("Cache-Control: max-age=30") 968 .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES))); 969 server.play(); 970 971 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 972 URLConnection connection = server.getUrl("/").openConnection(); 973 connection.addRequestProperty("Cache-Control", "only-if-cached"); 974 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 975 } 976 977 public void testRequestOnlyIfCachedWithConditionalResponseCached() throws IOException { 978 server.enqueue(new MockResponse().setBody("A") 979 .addHeader("Cache-Control: max-age=30") 980 .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES))); 981 server.play(); 982 983 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 984 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 985 connection.addRequestProperty("Cache-Control", "only-if-cached"); 986 assertBadGateway(connection); 987 } 988 989 public void testRequestOnlyIfCachedWithUnhelpfulResponseCached() throws IOException { 990 server.enqueue(new MockResponse().setBody("A")); 991 server.play(); 992 993 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 994 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 995 connection.addRequestProperty("Cache-Control", "only-if-cached"); 996 assertBadGateway(connection); 997 } 998 999 public void testRequestCacheControlNoCache() throws Exception { 1000 server.enqueue(new MockResponse() 1001 .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS)) 1002 .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS)) 1003 .addHeader("Cache-Control: max-age=60") 1004 .setBody("A")); 1005 server.enqueue(new MockResponse().setBody("B")); 1006 server.play(); 1007 1008 URL url = server.getUrl("/"); 1009 assertEquals("A", readAscii(url.openConnection())); 1010 URLConnection connection = url.openConnection(); 1011 connection.setRequestProperty("Cache-Control", "no-cache"); 1012 assertEquals("B", readAscii(connection)); 1013 } 1014 1015 public void testRequestPragmaNoCache() throws Exception { 1016 server.enqueue(new MockResponse() 1017 .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS)) 1018 .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS)) 1019 .addHeader("Cache-Control: max-age=60") 1020 .setBody("A")); 1021 server.enqueue(new MockResponse().setBody("B")); 1022 server.play(); 1023 1024 URL url = server.getUrl("/"); 1025 assertEquals("A", readAscii(url.openConnection())); 1026 URLConnection connection = url.openConnection(); 1027 connection.setRequestProperty("Pragma", "no-cache"); 1028 assertEquals("B", readAscii(connection)); 1029 } 1030 1031 public void testClientSuppliedIfModifiedSinceWithCachedResult() throws Exception { 1032 MockResponse response = new MockResponse() 1033 .addHeader("ETag: v3") 1034 .addHeader("Cache-Control: max-age=0"); 1035 String ifModifiedSinceDate = formatDate(-24, TimeUnit.HOURS); 1036 RecordedRequest request = assertClientSuppliedCondition( 1037 response, "If-Modified-Since", ifModifiedSinceDate); 1038 List<String> headers = request.getHeaders(); 1039 assertTrue(headers.contains("If-Modified-Since: " + ifModifiedSinceDate)); 1040 assertFalse(headers.contains("If-None-Match: v3")); 1041 } 1042 1043 public void testClientSuppliedIfNoneMatchSinceWithCachedResult() throws Exception { 1044 String lastModifiedDate = formatDate(-3, TimeUnit.MINUTES); 1045 MockResponse response = new MockResponse() 1046 .addHeader("Last-Modified: " + lastModifiedDate) 1047 .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES)) 1048 .addHeader("Cache-Control: max-age=0"); 1049 RecordedRequest request = assertClientSuppliedCondition( 1050 response, "If-None-Match", "v1"); 1051 List<String> headers = request.getHeaders(); 1052 assertTrue(headers.contains("If-None-Match: v1")); 1053 assertFalse(headers.contains("If-Modified-Since: " + lastModifiedDate)); 1054 } 1055 1056 private RecordedRequest assertClientSuppliedCondition(MockResponse seed, String conditionName, 1057 String conditionValue) throws Exception { 1058 server.enqueue(seed.setBody("A")); 1059 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 1060 server.play(); 1061 1062 URL url = server.getUrl("/"); 1063 assertEquals("A", readAscii(url.openConnection())); 1064 1065 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 1066 connection.addRequestProperty(conditionName, conditionValue); 1067 assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode()); 1068 assertEquals("", readAscii(connection)); 1069 1070 server.takeRequest(); // seed 1071 return server.takeRequest(); 1072 } 1073 1074 public void testSetIfModifiedSince() throws Exception { 1075 Date since = new Date(); 1076 server.enqueue(new MockResponse().setBody("A")); 1077 server.play(); 1078 1079 URL url = server.getUrl("/"); 1080 URLConnection connection = url.openConnection(); 1081 connection.setIfModifiedSince(since.getTime()); 1082 assertEquals("A", readAscii(connection)); 1083 RecordedRequest request = server.takeRequest(); 1084 assertTrue(request.getHeaders().contains("If-Modified-Since: " + formatDate(since))); 1085 } 1086 1087 public void testClientSuppliedConditionWithoutCachedResult() throws Exception { 1088 server.enqueue(new MockResponse() 1089 .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 1090 server.play(); 1091 1092 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1093 String clientIfModifiedSince = formatDate(-24, TimeUnit.HOURS); 1094 connection.addRequestProperty("If-Modified-Since", clientIfModifiedSince); 1095 assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode()); 1096 assertEquals("", readAscii(connection)); 1097 } 1098 1099 public void testAuthorizationRequestHeaderPreventsCaching() throws Exception { 1100 server.enqueue(new MockResponse() 1101 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.MINUTES)) 1102 .addHeader("Cache-Control: max-age=60") 1103 .setBody("A")); 1104 server.enqueue(new MockResponse().setBody("B")); 1105 server.play(); 1106 1107 URL url = server.getUrl("/"); 1108 URLConnection connection = url.openConnection(); 1109 connection.addRequestProperty("Authorization", "password"); 1110 assertEquals("A", readAscii(connection)); 1111 assertEquals("B", readAscii(url.openConnection())); 1112 } 1113 1114 public void testAuthorizationResponseCachedWithSMaxAge() throws Exception { 1115 assertAuthorizationRequestFullyCached(new MockResponse() 1116 .addHeader("Cache-Control: s-maxage=60")); 1117 } 1118 1119 public void testAuthorizationResponseCachedWithPublic() throws Exception { 1120 assertAuthorizationRequestFullyCached(new MockResponse() 1121 .addHeader("Cache-Control: public")); 1122 } 1123 1124 public void testAuthorizationResponseCachedWithMustRevalidate() throws Exception { 1125 assertAuthorizationRequestFullyCached(new MockResponse() 1126 .addHeader("Cache-Control: must-revalidate")); 1127 } 1128 1129 public void assertAuthorizationRequestFullyCached(MockResponse response) throws Exception { 1130 server.enqueue(response 1131 .addHeader("Cache-Control: max-age=60") 1132 .setBody("A")); 1133 server.enqueue(new MockResponse().setBody("B")); 1134 server.play(); 1135 1136 URL url = server.getUrl("/"); 1137 URLConnection connection = url.openConnection(); 1138 connection.addRequestProperty("Authorization", "password"); 1139 assertEquals("A", readAscii(connection)); 1140 assertEquals("A", readAscii(url.openConnection())); 1141 } 1142 1143 public void testContentLocationDoesNotPopulateCache() throws Exception { 1144 server.enqueue(new MockResponse() 1145 .addHeader("Cache-Control: max-age=60") 1146 .addHeader("Content-Location: /bar") 1147 .setBody("A")); 1148 server.enqueue(new MockResponse().setBody("B")); 1149 server.play(); 1150 1151 assertEquals("A", readAscii(server.getUrl("/foo").openConnection())); 1152 assertEquals("B", readAscii(server.getUrl("/bar").openConnection())); 1153 } 1154 1155 public void testUseCachesFalseDoesNotWriteToCache() throws Exception { 1156 server.enqueue(new MockResponse() 1157 .addHeader("Cache-Control: max-age=60") 1158 .setBody("A").setBody("A")); 1159 server.enqueue(new MockResponse().setBody("B")); 1160 server.play(); 1161 1162 URLConnection connection = server.getUrl("/").openConnection(); 1163 connection.setUseCaches(false); 1164 assertEquals("A", readAscii(connection)); 1165 assertEquals("B", readAscii(server.getUrl("/").openConnection())); 1166 } 1167 1168 public void testUseCachesFalseDoesNotReadFromCache() throws Exception { 1169 server.enqueue(new MockResponse() 1170 .addHeader("Cache-Control: max-age=60") 1171 .setBody("A").setBody("A")); 1172 server.enqueue(new MockResponse().setBody("B")); 1173 server.play(); 1174 1175 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 1176 URLConnection connection = server.getUrl("/").openConnection(); 1177 connection.setUseCaches(false); 1178 assertEquals("B", readAscii(connection)); 1179 } 1180 1181 public void testDefaultUseCachesSetsInitialValueOnly() throws Exception { 1182 URL url = new URL("http://localhost/"); 1183 URLConnection c1 = url.openConnection(); 1184 URLConnection c2 = url.openConnection(); 1185 assertTrue(c1.getDefaultUseCaches()); 1186 c1.setDefaultUseCaches(false); 1187 try { 1188 assertTrue(c1.getUseCaches()); 1189 assertTrue(c2.getUseCaches()); 1190 URLConnection c3 = url.openConnection(); 1191 assertFalse(c3.getUseCaches()); 1192 } finally { 1193 c1.setDefaultUseCaches(true); 1194 } 1195 } 1196 1197 public void testConnectionIsReturnedToPoolAfterConditionalSuccess() throws Exception { 1198 server.enqueue(new MockResponse() 1199 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 1200 .addHeader("Cache-Control: max-age=0") 1201 .setBody("A")); 1202 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 1203 server.enqueue(new MockResponse().setBody("B")); 1204 server.play(); 1205 1206 assertEquals("A", readAscii(server.getUrl("/a").openConnection())); 1207 assertEquals("A", readAscii(server.getUrl("/a").openConnection())); 1208 assertEquals("B", readAscii(server.getUrl("/b").openConnection())); 1209 1210 assertEquals(0, server.takeRequest().getSequenceNumber()); 1211 assertEquals(1, server.takeRequest().getSequenceNumber()); 1212 assertEquals(2, server.takeRequest().getSequenceNumber()); 1213 } 1214 1215 public void testStatisticsConditionalCacheMiss() throws Exception { 1216 server.enqueue(new MockResponse() 1217 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 1218 .addHeader("Cache-Control: max-age=0") 1219 .setBody("A")); 1220 server.enqueue(new MockResponse().setBody("B")); 1221 server.enqueue(new MockResponse().setBody("C")); 1222 server.play(); 1223 1224 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 1225 assertEquals(1, cache.getRequestCount()); 1226 assertEquals(1, cache.getNetworkCount()); 1227 assertEquals(0, cache.getHitCount()); 1228 assertEquals("B", readAscii(server.getUrl("/").openConnection())); 1229 assertEquals("C", readAscii(server.getUrl("/").openConnection())); 1230 assertEquals(3, cache.getRequestCount()); 1231 assertEquals(3, cache.getNetworkCount()); 1232 assertEquals(0, cache.getHitCount()); 1233 } 1234 1235 public void testStatisticsConditionalCacheHit() throws Exception { 1236 server.enqueue(new MockResponse() 1237 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 1238 .addHeader("Cache-Control: max-age=0") 1239 .setBody("A")); 1240 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 1241 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 1242 server.play(); 1243 1244 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 1245 assertEquals(1, cache.getRequestCount()); 1246 assertEquals(1, cache.getNetworkCount()); 1247 assertEquals(0, cache.getHitCount()); 1248 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 1249 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 1250 assertEquals(3, cache.getRequestCount()); 1251 assertEquals(3, cache.getNetworkCount()); 1252 assertEquals(2, cache.getHitCount()); 1253 } 1254 1255 public void testStatisticsFullCacheHit() throws Exception { 1256 server.enqueue(new MockResponse() 1257 .addHeader("Cache-Control: max-age=60") 1258 .setBody("A")); 1259 server.play(); 1260 1261 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 1262 assertEquals(1, cache.getRequestCount()); 1263 assertEquals(1, cache.getNetworkCount()); 1264 assertEquals(0, cache.getHitCount()); 1265 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 1266 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 1267 assertEquals(3, cache.getRequestCount()); 1268 assertEquals(1, cache.getNetworkCount()); 1269 assertEquals(2, cache.getHitCount()); 1270 } 1271 1272 public void testVaryMatchesChangedRequestHeaderField() throws Exception { 1273 server.enqueue(new MockResponse() 1274 .addHeader("Cache-Control: max-age=60") 1275 .addHeader("Vary: Accept-Language") 1276 .setBody("A")); 1277 server.enqueue(new MockResponse().setBody("B")); 1278 server.play(); 1279 1280 URL url = server.getUrl("/"); 1281 HttpURLConnection frConnection = (HttpURLConnection) url.openConnection(); 1282 frConnection.addRequestProperty("Accept-Language", "fr-CA"); 1283 assertEquals("A", readAscii(frConnection)); 1284 1285 HttpURLConnection enConnection = (HttpURLConnection) url.openConnection(); 1286 enConnection.addRequestProperty("Accept-Language", "en-US"); 1287 assertEquals("B", readAscii(enConnection)); 1288 } 1289 1290 public void testVaryMatchesUnchangedRequestHeaderField() throws Exception { 1291 server.enqueue(new MockResponse() 1292 .addHeader("Cache-Control: max-age=60") 1293 .addHeader("Vary: Accept-Language") 1294 .setBody("A")); 1295 server.enqueue(new MockResponse().setBody("B")); 1296 server.play(); 1297 1298 URL url = server.getUrl("/"); 1299 URLConnection connection1 = url.openConnection(); 1300 connection1.addRequestProperty("Accept-Language", "fr-CA"); 1301 assertEquals("A", readAscii(connection1)); 1302 URLConnection connection2 = url.openConnection(); 1303 connection2.addRequestProperty("Accept-Language", "fr-CA"); 1304 assertEquals("A", readAscii(connection2)); 1305 } 1306 1307 public void testVaryMatchesAbsentRequestHeaderField() 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().setBody("B")); 1313 server.play(); 1314 1315 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 1316 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 1317 } 1318 1319 public void testVaryMatchesAddedRequestHeaderField() throws Exception { 1320 server.enqueue(new MockResponse() 1321 .addHeader("Cache-Control: max-age=60") 1322 .addHeader("Vary: Foo") 1323 .setBody("A")); 1324 server.enqueue(new MockResponse().setBody("B")); 1325 server.play(); 1326 1327 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 1328 URLConnection fooConnection = server.getUrl("/").openConnection(); 1329 fooConnection.addRequestProperty("Foo", "bar"); 1330 assertEquals("B", readAscii(fooConnection)); 1331 } 1332 1333 public void testVaryMatchesRemovedRequestHeaderField() throws Exception { 1334 server.enqueue(new MockResponse() 1335 .addHeader("Cache-Control: max-age=60") 1336 .addHeader("Vary: Foo") 1337 .setBody("A")); 1338 server.enqueue(new MockResponse().setBody("B")); 1339 server.play(); 1340 1341 URLConnection fooConnection = server.getUrl("/").openConnection(); 1342 fooConnection.addRequestProperty("Foo", "bar"); 1343 assertEquals("A", readAscii(fooConnection)); 1344 assertEquals("B", readAscii(server.getUrl("/").openConnection())); 1345 } 1346 1347 public void testVaryFieldsAreCaseInsensitive() throws Exception { 1348 server.enqueue(new MockResponse() 1349 .addHeader("Cache-Control: max-age=60") 1350 .addHeader("Vary: ACCEPT-LANGUAGE") 1351 .setBody("A")); 1352 server.enqueue(new MockResponse().setBody("B")); 1353 server.play(); 1354 1355 URL url = server.getUrl("/"); 1356 URLConnection connection1 = url.openConnection(); 1357 connection1.addRequestProperty("Accept-Language", "fr-CA"); 1358 assertEquals("A", readAscii(connection1)); 1359 URLConnection connection2 = url.openConnection(); 1360 connection2.addRequestProperty("accept-language", "fr-CA"); 1361 assertEquals("A", readAscii(connection2)); 1362 } 1363 1364 public void testVaryMultipleFieldsWithMatch() throws Exception { 1365 server.enqueue(new MockResponse() 1366 .addHeader("Cache-Control: max-age=60") 1367 .addHeader("Vary: Accept-Language, Accept-Charset") 1368 .addHeader("Vary: Accept-Encoding") 1369 .setBody("A")); 1370 server.enqueue(new MockResponse().setBody("B")); 1371 server.play(); 1372 1373 URL url = server.getUrl("/"); 1374 URLConnection connection1 = url.openConnection(); 1375 connection1.addRequestProperty("Accept-Language", "fr-CA"); 1376 connection1.addRequestProperty("Accept-Charset", "UTF-8"); 1377 connection1.addRequestProperty("Accept-Encoding", "identity"); 1378 assertEquals("A", readAscii(connection1)); 1379 URLConnection connection2 = url.openConnection(); 1380 connection2.addRequestProperty("Accept-Language", "fr-CA"); 1381 connection2.addRequestProperty("Accept-Charset", "UTF-8"); 1382 connection2.addRequestProperty("Accept-Encoding", "identity"); 1383 assertEquals("A", readAscii(connection2)); 1384 } 1385 1386 public void testVaryMultipleFieldsWithNoMatch() throws Exception { 1387 server.enqueue(new MockResponse() 1388 .addHeader("Cache-Control: max-age=60") 1389 .addHeader("Vary: Accept-Language, Accept-Charset") 1390 .addHeader("Vary: Accept-Encoding") 1391 .setBody("A")); 1392 server.enqueue(new MockResponse().setBody("B")); 1393 server.play(); 1394 1395 URL url = server.getUrl("/"); 1396 URLConnection frConnection = url.openConnection(); 1397 frConnection.addRequestProperty("Accept-Language", "fr-CA"); 1398 frConnection.addRequestProperty("Accept-Charset", "UTF-8"); 1399 frConnection.addRequestProperty("Accept-Encoding", "identity"); 1400 assertEquals("A", readAscii(frConnection)); 1401 URLConnection enConnection = url.openConnection(); 1402 enConnection.addRequestProperty("Accept-Language", "en-CA"); 1403 enConnection.addRequestProperty("Accept-Charset", "UTF-8"); 1404 enConnection.addRequestProperty("Accept-Encoding", "identity"); 1405 assertEquals("B", readAscii(enConnection)); 1406 } 1407 1408 public void testVaryMultipleFieldValuesWithMatch() throws Exception { 1409 server.enqueue(new MockResponse() 1410 .addHeader("Cache-Control: max-age=60") 1411 .addHeader("Vary: Accept-Language") 1412 .setBody("A")); 1413 server.enqueue(new MockResponse().setBody("B")); 1414 server.play(); 1415 1416 URL url = server.getUrl("/"); 1417 URLConnection connection1 = url.openConnection(); 1418 connection1.addRequestProperty("Accept-Language", "fr-CA, fr-FR"); 1419 connection1.addRequestProperty("Accept-Language", "en-US"); 1420 assertEquals("A", readAscii(connection1)); 1421 1422 URLConnection connection2 = url.openConnection(); 1423 connection2.addRequestProperty("Accept-Language", "fr-CA, fr-FR"); 1424 connection2.addRequestProperty("Accept-Language", "en-US"); 1425 assertEquals("A", readAscii(connection2)); 1426 } 1427 1428 public void testVaryMultipleFieldValuesWithNoMatch() throws Exception { 1429 server.enqueue(new MockResponse() 1430 .addHeader("Cache-Control: max-age=60") 1431 .addHeader("Vary: Accept-Language") 1432 .setBody("A")); 1433 server.enqueue(new MockResponse().setBody("B")); 1434 server.play(); 1435 1436 URL url = server.getUrl("/"); 1437 URLConnection connection1 = url.openConnection(); 1438 connection1.addRequestProperty("Accept-Language", "fr-CA, fr-FR"); 1439 connection1.addRequestProperty("Accept-Language", "en-US"); 1440 assertEquals("A", readAscii(connection1)); 1441 1442 URLConnection connection2 = url.openConnection(); 1443 connection2.addRequestProperty("Accept-Language", "fr-CA"); 1444 connection2.addRequestProperty("Accept-Language", "en-US"); 1445 assertEquals("B", readAscii(connection2)); 1446 } 1447 1448 public void testVaryAsterisk() throws Exception { 1449 server.enqueue(new MockResponse() 1450 .addHeader("Cache-Control: max-age=60") 1451 .addHeader("Vary: *") 1452 .setBody("A")); 1453 server.enqueue(new MockResponse().setBody("B")); 1454 server.play(); 1455 1456 assertEquals("A", readAscii(server.getUrl("/").openConnection())); 1457 assertEquals("B", readAscii(server.getUrl("/").openConnection())); 1458 } 1459 1460 public void testVaryAndHttps() throws Exception { 1461 TestSSLContext testSSLContext = TestSSLContext.create(); 1462 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1463 server.enqueue(new MockResponse() 1464 .addHeader("Cache-Control: max-age=60") 1465 .addHeader("Vary: Accept-Language") 1466 .setBody("A")); 1467 server.enqueue(new MockResponse().setBody("B")); 1468 server.play(); 1469 1470 URL url = server.getUrl("/"); 1471 HttpsURLConnection connection1 = (HttpsURLConnection) url.openConnection(); 1472 connection1.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1473 connection1.addRequestProperty("Accept-Language", "en-US"); 1474 assertEquals("A", readAscii(connection1)); 1475 1476 HttpsURLConnection connection2 = (HttpsURLConnection) url.openConnection(); 1477 connection2.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1478 connection2.addRequestProperty("Accept-Language", "en-US"); 1479 assertEquals("A", readAscii(connection2)); 1480 } 1481 1482 public void testDiskWriteFailureCacheDegradation() throws Exception { 1483 Deque<InvocationHandler> writeHandlers = mockOs.getHandlers("write"); 1484 int i = 0; 1485 boolean hasMoreScenarios = true; 1486 while (hasMoreScenarios) { 1487 mockOs.enqueueNormal("write", i++); 1488 mockOs.enqueueFault("write"); 1489 exercisePossiblyFaultyCache(false); 1490 hasMoreScenarios = writeHandlers.isEmpty(); 1491 writeHandlers.clear(); 1492 } 1493 System.out.println("Exercising the cache performs " + (i - 1) + " writes."); 1494 } 1495 1496 public void testDiskReadFailureCacheDegradation() throws Exception { 1497 Deque<InvocationHandler> readHandlers = mockOs.getHandlers("read"); 1498 int i = 0; 1499 boolean hasMoreScenarios = true; 1500 while (hasMoreScenarios) { 1501 mockOs.enqueueNormal("read", i++); 1502 mockOs.enqueueFault("read"); 1503 exercisePossiblyFaultyCache(true); 1504 hasMoreScenarios = readHandlers.isEmpty(); 1505 readHandlers.clear(); 1506 } 1507 System.out.println("Exercising the cache performs " + (i - 1) + " reads."); 1508 } 1509 1510 public void testCachePlusCookies() throws Exception { 1511 server.enqueue(new MockResponse() 1512 .addHeader("Set-Cookie: a=FIRST; domain=" + server.getCookieDomain() + ";") 1513 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 1514 .addHeader("Cache-Control: max-age=0") 1515 .setBody("A")); 1516 server.enqueue(new MockResponse() 1517 .addHeader("Set-Cookie: a=SECOND; domain=" + server.getCookieDomain() + ";") 1518 .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 1519 server.play(); 1520 1521 URL url = server.getUrl("/"); 1522 assertEquals("A", readAscii(url.openConnection())); 1523 assertCookies(url, "a=FIRST"); 1524 assertEquals("A", readAscii(url.openConnection())); 1525 assertCookies(url, "a=SECOND"); 1526 } 1527 1528 public void testGetHeadersReturnsNetworkEndToEndHeaders() throws Exception { 1529 server.enqueue(new MockResponse() 1530 .addHeader("Allow: GET, HEAD") 1531 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 1532 .addHeader("Cache-Control: max-age=0") 1533 .setBody("A")); 1534 server.enqueue(new MockResponse() 1535 .addHeader("Allow: GET, HEAD, PUT") 1536 .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 1537 server.play(); 1538 1539 URLConnection connection1 = server.getUrl("/").openConnection(); 1540 assertEquals("A", readAscii(connection1)); 1541 assertEquals("GET, HEAD", connection1.getHeaderField("Allow")); 1542 1543 URLConnection connection2 = server.getUrl("/").openConnection(); 1544 assertEquals("A", readAscii(connection2)); 1545 assertEquals("GET, HEAD, PUT", connection2.getHeaderField("Allow")); 1546 } 1547 1548 public void testGetHeadersReturnsCachedHopByHopHeaders() throws Exception { 1549 server.enqueue(new MockResponse() 1550 .addHeader("Transfer-Encoding: identity") 1551 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 1552 .addHeader("Cache-Control: max-age=0") 1553 .setBody("A")); 1554 server.enqueue(new MockResponse() 1555 .addHeader("Transfer-Encoding: none") 1556 .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 1557 server.play(); 1558 1559 URLConnection connection1 = server.getUrl("/").openConnection(); 1560 assertEquals("A", readAscii(connection1)); 1561 assertEquals("identity", connection1.getHeaderField("Transfer-Encoding")); 1562 1563 URLConnection connection2 = server.getUrl("/").openConnection(); 1564 assertEquals("A", readAscii(connection2)); 1565 assertEquals("identity", connection2.getHeaderField("Transfer-Encoding")); 1566 } 1567 1568 public void testGetHeadersDeletesCached100LevelWarnings() throws Exception { 1569 server.enqueue(new MockResponse() 1570 .addHeader("Warning: 199 test danger") 1571 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 1572 .addHeader("Cache-Control: max-age=0") 1573 .setBody("A")); 1574 server.enqueue(new MockResponse() 1575 .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 1576 server.play(); 1577 1578 URLConnection connection1 = server.getUrl("/").openConnection(); 1579 assertEquals("A", readAscii(connection1)); 1580 assertEquals("199 test danger", connection1.getHeaderField("Warning")); 1581 1582 URLConnection connection2 = server.getUrl("/").openConnection(); 1583 assertEquals("A", readAscii(connection2)); 1584 assertEquals(null, connection2.getHeaderField("Warning")); 1585 } 1586 1587 public void testGetHeadersRetainsCached200LevelWarnings() throws Exception { 1588 server.enqueue(new MockResponse() 1589 .addHeader("Warning: 299 test danger") 1590 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) 1591 .addHeader("Cache-Control: max-age=0") 1592 .setBody("A")); 1593 server.enqueue(new MockResponse() 1594 .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 1595 server.play(); 1596 1597 URLConnection connection1 = server.getUrl("/").openConnection(); 1598 assertEquals("A", readAscii(connection1)); 1599 assertEquals("299 test danger", connection1.getHeaderField("Warning")); 1600 1601 URLConnection connection2 = server.getUrl("/").openConnection(); 1602 assertEquals("A", readAscii(connection2)); 1603 assertEquals("299 test danger", connection2.getHeaderField("Warning")); 1604 } 1605 1606 public void assertCookies(URL url, String... expectedCookies) throws Exception { 1607 List<String> actualCookies = new ArrayList<String>(); 1608 for (HttpCookie cookie : cookieManager.getCookieStore().get(url.toURI())) { 1609 actualCookies.add(cookie.toString()); 1610 } 1611 assertEquals(Arrays.asList(expectedCookies), actualCookies); 1612 } 1613 1614 public void testCachePlusRange() throws Exception { 1615 assertNotCached(new MockResponse() 1616 .setResponseCode(HttpURLConnection.HTTP_PARTIAL) 1617 .addHeader("Date: " + formatDate(0, TimeUnit.HOURS)) 1618 .addHeader("Content-Range: bytes 100-100/200") 1619 .addHeader("Cache-Control: max-age=60")); 1620 } 1621 1622 public void testConditionalHitUpdatesCache() throws Exception { 1623 server.enqueue(new MockResponse() 1624 .addHeader("Last-Modified: " + formatDate(0, TimeUnit.SECONDS)) 1625 .addHeader("Cache-Control: max-age=0") 1626 .setBody("A")); 1627 server.enqueue(new MockResponse() 1628 .addHeader("Cache-Control: max-age=30") 1629 .addHeader("Allow: GET, HEAD") 1630 .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 1631 server.enqueue(new MockResponse().setBody("B")); 1632 server.play(); 1633 1634 // cache miss; seed the cache 1635 HttpURLConnection connection1 = (HttpURLConnection) server.getUrl("/a").openConnection(); 1636 assertEquals("A", readAscii(connection1)); 1637 assertEquals(null, connection1.getHeaderField("Allow")); 1638 1639 // conditional cache hit; update the cache 1640 HttpURLConnection connection2 = (HttpURLConnection) server.getUrl("/a").openConnection(); 1641 assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode()); 1642 assertEquals("A", readAscii(connection2)); 1643 assertEquals("GET, HEAD", connection2.getHeaderField("Allow")); 1644 1645 // full cache hit 1646 HttpURLConnection connection3 = (HttpURLConnection) server.getUrl("/a").openConnection(); 1647 assertEquals("A", readAscii(connection3)); 1648 assertEquals("GET, HEAD", connection3.getHeaderField("Allow")); 1649 1650 assertEquals(2, server.getRequestCount()); 1651 } 1652 1653 /** 1654 * @param delta the offset from the current date to use. Negative 1655 * values yield dates in the past; positive values yield dates in the 1656 * future. 1657 */ 1658 private String formatDate(long delta, TimeUnit timeUnit) { 1659 return formatDate(new Date(System.currentTimeMillis() + timeUnit.toMillis(delta))); 1660 } 1661 1662 private String formatDate(Date date) { 1663 DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); 1664 rfc1123.setTimeZone(TimeZone.getTimeZone("UTC")); 1665 return rfc1123.format(date); 1666 } 1667 1668 private void addRequestBodyIfNecessary(String requestMethod, HttpURLConnection invalidate) 1669 throws IOException { 1670 if (requestMethod.equals("POST") || requestMethod.equals("PUT")) { 1671 invalidate.setDoOutput(true); 1672 OutputStream requestBody = invalidate.getOutputStream(); 1673 requestBody.write('x'); 1674 requestBody.close(); 1675 } 1676 } 1677 1678 private void assertNotCached(MockResponse response) throws Exception { 1679 server.enqueue(response.setBody("A")); 1680 server.enqueue(new MockResponse().setBody("B")); 1681 server.play(); 1682 1683 URL url = server.getUrl("/"); 1684 assertEquals("A", readAscii(url.openConnection())); 1685 assertEquals("B", readAscii(url.openConnection())); 1686 } 1687 1688 private void exercisePossiblyFaultyCache(boolean permitReadBodyFailures) throws Exception { 1689 server.shutdown(); 1690 server = new MockWebServer(); 1691 server.enqueue(new MockResponse() 1692 .addHeader("Cache-Control: max-age=60") 1693 .setBody("A")); 1694 server.enqueue(new MockResponse().setBody("B")); 1695 server.play(); 1696 1697 URL url = server.getUrl("/" + UUID.randomUUID()); 1698 assertEquals("A", readAscii(url.openConnection())); 1699 1700 URLConnection connection = url.openConnection(); 1701 InputStream in = connection.getInputStream(); 1702 try { 1703 int bodyChar = in.read(); 1704 assertTrue(bodyChar == 'A' || bodyChar == 'B'); 1705 assertEquals(-1, in.read()); 1706 } catch (IOException e) { 1707 if (!permitReadBodyFailures) { 1708 throw e; 1709 } 1710 } 1711 } 1712 1713 /** 1714 * @return the request with the conditional get headers. 1715 */ 1716 private RecordedRequest assertConditionallyCached(MockResponse response) throws Exception { 1717 // scenario 1: condition succeeds 1718 server.enqueue(response.setBody("A").setStatus("HTTP/1.1 200 A-OK")); 1719 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); 1720 1721 // scenario 2: condition fails 1722 server.enqueue(response.setBody("B").setStatus("HTTP/1.1 200 B-OK")); 1723 server.enqueue(new MockResponse().setStatus("HTTP/1.1 200 C-OK").setBody("C")); 1724 1725 server.play(); 1726 1727 URL valid = server.getUrl("/valid"); 1728 HttpURLConnection connection1 = (HttpURLConnection) valid.openConnection(); 1729 assertEquals("A", readAscii(connection1)); 1730 assertEquals(HttpURLConnection.HTTP_OK, connection1.getResponseCode()); 1731 assertEquals("A-OK", connection1.getResponseMessage()); 1732 HttpURLConnection connection2 = (HttpURLConnection) valid.openConnection(); 1733 assertEquals("A", readAscii(connection2)); 1734 assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode()); 1735 assertEquals("A-OK", connection2.getResponseMessage()); 1736 1737 URL invalid = server.getUrl("/invalid"); 1738 HttpURLConnection connection3 = (HttpURLConnection) invalid.openConnection(); 1739 assertEquals("B", readAscii(connection3)); 1740 assertEquals(HttpURLConnection.HTTP_OK, connection3.getResponseCode()); 1741 assertEquals("B-OK", connection3.getResponseMessage()); 1742 HttpURLConnection connection4 = (HttpURLConnection) invalid.openConnection(); 1743 assertEquals("C", readAscii(connection4)); 1744 assertEquals(HttpURLConnection.HTTP_OK, connection4.getResponseCode()); 1745 assertEquals("C-OK", connection4.getResponseMessage()); 1746 1747 server.takeRequest(); // regular get 1748 return server.takeRequest(); // conditional get 1749 } 1750 1751 private void assertFullyCached(MockResponse response) throws Exception { 1752 server.enqueue(response.setBody("A")); 1753 server.enqueue(response.setBody("B")); 1754 server.play(); 1755 1756 URL url = server.getUrl("/"); 1757 assertEquals("A", readAscii(url.openConnection())); 1758 assertEquals("A", readAscii(url.openConnection())); 1759 } 1760 1761 /** 1762 * Shortens the body of {@code response} but not the corresponding headers. 1763 * Only useful to test how clients respond to the premature conclusion of 1764 * the HTTP body. 1765 */ 1766 private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) { 1767 response.setSocketPolicy(DISCONNECT_AT_END); 1768 List<String> headers = new ArrayList<String>(response.getHeaders()); 1769 response.setBody(Arrays.copyOfRange(response.getBody(), 0, numBytesToKeep)); 1770 response.getHeaders().clear(); 1771 response.getHeaders().addAll(headers); 1772 return response; 1773 } 1774 1775 /** 1776 * Reads {@code count} characters from the stream. If the stream is 1777 * exhausted before {@code count} characters can be read, the remaining 1778 * characters are returned and the stream is closed. 1779 */ 1780 private String readAscii(URLConnection connection, int count) throws IOException { 1781 HttpURLConnection httpConnection = (HttpURLConnection) connection; 1782 InputStream in = httpConnection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST 1783 ? connection.getInputStream() 1784 : httpConnection.getErrorStream(); 1785 StringBuilder result = new StringBuilder(); 1786 for (int i = 0; i < count; i++) { 1787 int value = in.read(); 1788 if (value == -1) { 1789 in.close(); 1790 break; 1791 } 1792 result.append((char) value); 1793 } 1794 return result.toString(); 1795 } 1796 1797 private String readAscii(URLConnection connection) throws IOException { 1798 return readAscii(connection, Integer.MAX_VALUE); 1799 } 1800 1801 private void reliableSkip(InputStream in, int length) throws IOException { 1802 while (length > 0) { 1803 length -= in.skip(length); 1804 } 1805 } 1806 1807 private void assertBadGateway(HttpURLConnection connection) throws IOException { 1808 try { 1809 connection.getInputStream(); 1810 fail(); 1811 } catch (FileNotFoundException expected) { 1812 } 1813 assertEquals(HttpURLConnection.HTTP_BAD_GATEWAY, connection.getResponseCode()); 1814 assertEquals(-1, connection.getErrorStream().read()); 1815 } 1816 1817 enum TransferKind { 1818 CHUNKED() { 1819 @Override void setBody(MockResponse response, byte[] content, int chunkSize) 1820 throws IOException { 1821 response.setChunkedBody(content, chunkSize); 1822 } 1823 }, 1824 FIXED_LENGTH() { 1825 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 1826 response.setBody(content); 1827 } 1828 }, 1829 END_OF_STREAM() { 1830 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 1831 response.setBody(content); 1832 response.setSocketPolicy(DISCONNECT_AT_END); 1833 for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) { 1834 if (h.next().startsWith("Content-Length:")) { 1835 h.remove(); 1836 break; 1837 } 1838 } 1839 } 1840 }; 1841 1842 abstract void setBody(MockResponse response, byte[] content, int chunkSize) 1843 throws IOException; 1844 1845 void setBody(MockResponse response, String content, int chunkSize) throws IOException { 1846 setBody(response, content.getBytes("UTF-8"), chunkSize); 1847 } 1848 } 1849 1850 private <T> List<T> toListOrNull(T[] arrayOrNull) { 1851 return arrayOrNull != null ? Arrays.asList(arrayOrNull) : null; 1852 } 1853 1854 /** 1855 * Returns a gzipped copy of {@code bytes}. 1856 */ 1857 public byte[] gzip(byte[] bytes) throws IOException { 1858 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 1859 OutputStream gzippedOut = new GZIPOutputStream(bytesOut); 1860 gzippedOut.write(bytes); 1861 gzippedOut.close(); 1862 return bytesOut.toByteArray(); 1863 } 1864 1865 private class InsecureResponseCache extends ResponseCache { 1866 @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { 1867 return cache.put(uri, connection); 1868 } 1869 1870 @Override public CacheResponse get(URI uri, String requestMethod, 1871 Map<String, List<String>> requestHeaders) throws IOException { 1872 final CacheResponse response = cache.get(uri, requestMethod, requestHeaders); 1873 if (response instanceof SecureCacheResponse) { 1874 return new CacheResponse() { 1875 @Override public InputStream getBody() throws IOException { 1876 return response.getBody(); 1877 } 1878 @Override public Map<String, List<String>> getHeaders() throws IOException { 1879 return response.getHeaders(); 1880 } 1881 }; 1882 } 1883 return response; 1884 } 1885 } 1886} 1887