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