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