HttpResponseCacheTest.java revision a4193d7e636802a2705ffb52cb69c69ff59bfbb2
10c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson/*
20c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * Copyright (C) 2011 The Android Open Source Project
30c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson *
40c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * Licensed under the Apache License, Version 2.0 (the "License");
50c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * you may not use this file except in compliance with the License.
60c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * You may obtain a copy of the License at
70c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson *
80c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson *      http://www.apache.org/licenses/LICENSE-2.0
90c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson *
100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * Unless required by applicable law or agreed to in writing, software
110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * distributed under the License is distributed on an "AS IS" BASIS,
120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * See the License for the specific language governing permissions and
140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson * limitations under the License.
150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson */
160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonpackage libcore.java.net;
180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.io.BufferedReader;
200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.io.ByteArrayOutputStream;
21c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilsonimport java.io.FileNotFoundException;
220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.io.IOException;
230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.io.InputStream;
240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.io.InputStreamReader;
250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.io.OutputStream;
260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.net.CacheRequest;
270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.net.CacheResponse;
280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.net.HttpURLConnection;
290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.net.ResponseCache;
300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.net.SecureCacheResponse;
310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.net.URI;
320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.net.URISyntaxException;
330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.net.URL;
340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.net.URLConnection;
350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.security.Principal;
360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.security.cert.Certificate;
37953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilsonimport java.text.DateFormat;
38953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilsonimport java.text.SimpleDateFormat;
390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.util.ArrayList;
400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.util.Arrays;
410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.util.Collections;
420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.util.Date;
430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.util.Iterator;
440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.util.List;
45953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilsonimport java.util.Locale;
460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.util.Map;
470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.util.Set;
48953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilsonimport java.util.TimeZone;
490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.util.concurrent.TimeUnit;
500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.util.concurrent.atomic.AtomicInteger;
510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.util.concurrent.atomic.AtomicReference;
520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.util.zip.GZIPOutputStream;
530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport javax.net.ssl.HttpsURLConnection;
540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport junit.framework.TestCase;
550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport libcore.javax.net.ssl.TestSSLContext;
560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport libcore.net.http.HttpResponseCache;
570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport tests.http.MockResponse;
580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport tests.http.MockWebServer;
590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport tests.http.RecordedRequest;
600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport static tests.http.SocketPolicy.DISCONNECT_AT_END;
610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonpublic final class HttpResponseCacheTest extends TestCase {
630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private MockWebServer server = new MockWebServer();
640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private HttpResponseCache cache = new HttpResponseCache();
650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    @Override protected void setUp() throws Exception {
670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        super.setUp();
680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        ResponseCache.setDefault(cache);
690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    @Override protected void tearDown() throws Exception {
720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        ResponseCache.setDefault(null);
730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.shutdown();
740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        super.tearDown();
750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Test that response caching is consistent with the RI and the spec.
790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4
800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
8184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    public void testResponseCachingByResponseCode() throws Exception {
820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // Test each documented HTTP/1.1 code, plus the first unused value in each range.
830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // We can't test 100 because it's not really a response.
860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // assertCached(false, 100);
870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(false, 101);
880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(false, 102);
890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(true,  200);
900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(false, 201);
910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(false, 202);
920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(true,  203);
930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(false, 204);
940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(false, 205);
950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(true,  206);
960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(false, 207);
9784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertCached(true,  300);
980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(true,  301);
990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        for (int i = 302; i <= 308; ++i) {
1000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            assertCached(false, i);
1010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
1020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        for (int i = 400; i <= 406; ++i) {
1030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            assertCached(false, i);
1040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
1050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // (See test_responseCaching_407.)
1060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(false, 408);
1070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertCached(false, 409);
1080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // (See test_responseCaching_410.)
1090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        for (int i = 411; i <= 418; ++i) {
1100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            assertCached(false, i);
1110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
1120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        for (int i = 500; i <= 506; ++i) {
1130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            assertCached(false, i);
1140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
1150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
1160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
1180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Response code 407 should only come from proxy servers. Android's client
1190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * throws if it is sent by an origin server.
1200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
1210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testOriginServerSends407() throws Exception {
1220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setResponseCode(407));
1230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
1240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
1260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
1270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        try {
1280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            conn.getResponseCode();
1290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            fail();
1300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        } catch (IOException expected) {
1310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
1320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
1330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void test_responseCaching_410() throws Exception {
1350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // the HTTP spec permits caching 410s, but the RI doesn't.
13684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertCached(true, 410);
1370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
1380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void assertCached(boolean shouldPut, int responseCode) throws Exception {
1400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server = new MockWebServer();
1410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
1420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
1430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
1440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setResponseCode(responseCode)
1450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setBody("ABCDE")
1460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("WWW-Authenticate: challenge"));
1470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
1480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
1500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
1510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(responseCode, conn.getResponseCode());
1520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // exhaust the content stream
15484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        readAscii(conn);
1550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        Set<URI> expectedCachedUris = shouldPut
1570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                ? Collections.singleton(url.toURI())
1580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                : Collections.<URI>emptySet();
1590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(Integer.toString(responseCode),
1600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                expectedCachedUris, cache.getContents().keySet());
1610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers
1620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
1630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
1650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Test that we can interrogate the response when the cache is being
1660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * populated. http://code.google.com/p/android/issues/detail?id=7787
1670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
1680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testResponseCacheCallbackApis() throws Exception {
1690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        final String body = "ABCDE";
1700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        final AtomicInteger cacheCount = new AtomicInteger();
1710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
1730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setStatus("HTTP/1.1 200 Fantastic")
1740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("fgh: ijk")
1750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setBody(body));
1760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
1770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        ResponseCache.setDefault(new ResponseCache() {
1790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            @Override public CacheResponse get(URI uri, String requestMethod,
1800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    Map<String, List<String>> requestHeaders) throws IOException {
1810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                return null;
1820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
1830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
1840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                HttpURLConnection httpConnection = (HttpURLConnection) conn;
185953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                try {
186953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                    httpConnection.getRequestProperties();
187953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                    fail();
188953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                } catch (IllegalStateException expected) {
189953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                }
190953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                try {
191953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                    httpConnection.addRequestProperty("K", "V");
192953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                    fail();
193953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                } catch (IllegalStateException expected) {
194953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                }
1950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                assertEquals("HTTP/1.1 200 Fantastic", httpConnection.getHeaderField(null));
1960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                assertEquals(Arrays.asList("HTTP/1.1 200 Fantastic"),
1970c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                        httpConnection.getHeaderFields().get(null));
1980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                assertEquals(200, httpConnection.getResponseCode());
1990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                assertEquals("Fantastic", httpConnection.getResponseMessage());
2000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                assertEquals(body.length(), httpConnection.getContentLength());
2010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                assertEquals("ijk", httpConnection.getHeaderField("fgh"));
2020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                try {
2030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    httpConnection.getInputStream(); // the RI doesn't forbid this, but it should
2040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    fail();
2050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                } catch (IOException expected) {
2060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                }
2070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                cacheCount.incrementAndGet();
2080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                return null;
2090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
2100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        });
2110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
213953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection = url.openConnection();
2140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(body, readAscii(connection));
2150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cacheCount.get());
2160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
2170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException {
2200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testResponseCaching(TransferKind.FIXED_LENGTH);
2210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
2220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException {
2240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testResponseCaching(TransferKind.CHUNKED);
2250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
2260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException {
2280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testResponseCaching(TransferKind.END_OF_STREAM);
2290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
2300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
2320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * HttpURLConnection.getInputStream().skip(long) causes ResponseCache corruption
2330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * http://code.google.com/p/android/issues/detail?id=8175
2340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
2350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void testResponseCaching(TransferKind transferKind) throws IOException {
2360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        MockResponse response = new MockResponse()
2370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
2380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
2390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setStatus("HTTP/1.1 200 Fantastic");
2400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        transferKind.setBody(response, "I love puppies but hate spiders", 1);
2410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response);
2420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
2430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // Make sure that calling skip() doesn't omit bytes from the cache.
2450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
2460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        InputStream in = urlConnection.getInputStream();
2470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("I love ", readAscii(urlConnection, "I love ".length()));
2480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        reliableSkip(in, "puppies but hate ".length());
2490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("spiders", readAscii(urlConnection, "spiders".length()));
2500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(-1, in.read());
2510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        in.close();
2520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getSuccessCount());
2530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(0, cache.getAbortCount());
2540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached!
2560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        in = urlConnection.getInputStream();
2570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("I love puppies but hate spiders",
2580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                readAscii(urlConnection, "I love puppies but hate spiders".length()));
2590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(200, urlConnection.getResponseCode());
2600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("Fantastic", urlConnection.getResponseMessage());
2610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(-1, in.read());
2630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getMissCount());
2640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getHitCount());
2650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getSuccessCount());
2660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(0, cache.getAbortCount());
2670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
2680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testSecureResponseCaching() throws IOException {
2700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        TestSSLContext testSSLContext = TestSSLContext.create();
2710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
2720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
2730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
2740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
2750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setBody("ABC"));
2760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
2770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
2790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
2800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABC", readAscii(connection));
2810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // OpenJDK 6 fails on this line, complaining that the connection isn't open yet
2830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        String suite = connection.getCipherSuite();
2840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<Certificate> localCerts = toListOrNull(connection.getLocalCertificates());
2850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<Certificate> serverCerts = toListOrNull(connection.getServerCertificates());
2860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        Principal peerPrincipal = connection.getPeerPrincipal();
2870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        Principal localPrincipal = connection.getLocalPrincipal();
2880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
2900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
2910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABC", readAscii(connection));
2920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getMissCount());
2940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getHitCount());
2950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
2960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(suite, connection.getCipherSuite());
2970c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(localCerts, toListOrNull(connection.getLocalCertificates()));
2980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(serverCerts, toListOrNull(connection.getServerCertificates()));
2990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(peerPrincipal, connection.getPeerPrincipal());
3000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(localPrincipal, connection.getLocalPrincipal());
3010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
3020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testCacheReturnsInsecureResponseForSecureRequest() throws IOException {
3040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        TestSSLContext testSSLContext = TestSSLContext.create();
3050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
3060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("ABC"));
3070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("DEF"));
3080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
3090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        ResponseCache.setDefault(new InsecureResponseCache());
3110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
3130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
3140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABC", readAscii(connection));
3150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // not cached!
3170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
3180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("DEF", readAscii(connection));
3190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
3200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
321a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson    public void testResponseCachingAndRedirects() throws Exception {
3220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
3230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
3240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
3250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
3260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Location: /foo"));
3270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
3280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
3290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
3300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setBody("ABC"));
3310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("DEF"));
3320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
3330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
334953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
3350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABC", readAscii(connection));
3360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
337953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        connection = server.getUrl("/").openConnection(); // cached!
3380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABC", readAscii(connection));
3390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
3410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(2, cache.getHitCount());
3420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
3430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
344a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson    public void testRedirectToCachedResult() throws Exception {
345a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        server.enqueue(new MockResponse()
346a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson                .addHeader("Cache-Control: max-age=60")
347a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson                .setBody("ABC"));
348a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        server.enqueue(new MockResponse()
349a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
350a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson                .addHeader("Location: /foo"));
351a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        server.enqueue(new MockResponse().setBody("DEF"));
352a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        server.play();
353a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson
354a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        assertEquals("ABC", readAscii(server.getUrl("/foo").openConnection()));
355a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        RecordedRequest request1 = server.takeRequest();
356a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        assertEquals("GET /foo HTTP/1.1", request1.getRequestLine());
357a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        assertEquals(0, request1.getSequenceNumber());
358a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson
359a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        assertEquals("ABC", readAscii(server.getUrl("/bar").openConnection()));
360a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        RecordedRequest request2 = server.takeRequest();
361a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        assertEquals("GET /bar HTTP/1.1", request2.getRequestLine());
362a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        assertEquals(1, request2.getSequenceNumber());
363a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson
364a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        // an unrelated request should reuse the pooled connection
365a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        assertEquals("DEF", readAscii(server.getUrl("/baz").openConnection()));
366a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        RecordedRequest request3 = server.takeRequest();
367a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        assertEquals("GET /baz HTTP/1.1", request3.getRequestLine());
368a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson        assertEquals(2, request3.getSequenceNumber());
369a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson    }
370a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson
371a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson    // TODO: test connection is returned to pool after conditional success
372a4193d7e636802a2705ffb52cb69c69ff59bfbb2Jesse Wilson
3730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testSecureResponseCachingAndRedirects() throws IOException {
3740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        TestSSLContext testSSLContext = TestSSLContext.create();
3750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
3760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
3770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
3780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
3790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
3800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Location: /foo"));
3810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
3820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
3830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
3840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setBody("ABC"));
3850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("DEF"));
3860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
3870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
3890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
3900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABC", readAscii(connection));
3910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
3930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
3940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABC", readAscii(connection));
3950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
3970c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(2, cache.getHitCount());
3980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
3990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException {
4010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("ABC"));
4020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
4030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        final AtomicReference<Map<String, List<String>>> requestHeadersRef
4050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                = new AtomicReference<Map<String, List<String>>>();
4060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        ResponseCache.setDefault(new ResponseCache() {
4070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            @Override public CacheResponse get(URI uri, String requestMethod,
4080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    Map<String, List<String>> requestHeaders) throws IOException {
4090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                requestHeadersRef.set(requestHeaders);
4100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                return null;
4110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
4120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
4130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                return null;
4140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
4150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        });
4160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
4180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URLConnection urlConnection = url.openConnection();
4190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        urlConnection.addRequestProperty("A", "android");
4200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        readAscii(urlConnection);
4210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A"));
4220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException {
4260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testServerPrematureDisconnect(TransferKind.FIXED_LENGTH);
4270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException {
4300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testServerPrematureDisconnect(TransferKind.CHUNKED);
4310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException {
4340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        /*
4350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * Intentionally empty. This case doesn't make sense because there's no
4360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * such thing as a premature disconnect when the disconnect itself
4370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * indicates the end of the data stream.
4380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         */
4390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException {
4420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        MockResponse response = new MockResponse();
4430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16);
4440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(truncateViolently(response, 16));
4450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("Request #2"));
4460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
4470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        BufferedReader reader = new BufferedReader(new InputStreamReader(
4490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                server.getUrl("/").openConnection().getInputStream()));
4500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABCDE", reader.readLine());
4510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        try {
4520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            reader.readLine();
4530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            fail("This implementation silently ignored a truncated HTTP body.");
4540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        } catch (IOException expected) {
4550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
4560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getAbortCount());
4580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(0, cache.getSuccessCount());
4590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
4600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("Request #2", readAscii(connection));
4610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getAbortCount());
4620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getSuccessCount());
4630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException {
4660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testClientPrematureDisconnect(TransferKind.FIXED_LENGTH);
4670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException {
4700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testClientPrematureDisconnect(TransferKind.CHUNKED);
4710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException {
4740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testClientPrematureDisconnect(TransferKind.END_OF_STREAM);
4750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException {
4780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        MockResponse response = new MockResponse();
4790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024);
4800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response);
4810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("Request #2"));
4820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
4830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
4850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        InputStream in = connection.getInputStream();
4860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABCDE", readAscii(connection, 5));
4870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        in.close();
4880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        try {
4890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            in.read();
4900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            fail("Expected an IOException because the stream is closed.");
4910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        } catch (IOException expected) {
4920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
4930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getAbortCount());
4950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(0, cache.getSuccessCount());
4960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection = server.getUrl("/").openConnection();
49784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("Request #2", readAscii(connection));
4980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getAbortCount());
4990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getSuccessCount());
5000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
502953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    public void testDefaultExpirationDateFullyCachedForLessThan24Hours() throws Exception {
50321dddca4064527116af7a1553de502c6d11138daJesse Wilson        //      last modified: 105 seconds ago
50421dddca4064527116af7a1553de502c6d11138daJesse Wilson        //             served:   5 seconds ago
50521dddca4064527116af7a1553de502c6d11138daJesse Wilson        //   default lifetime: (105 - 5) / 10 = 10 seconds
50621dddca4064527116af7a1553de502c6d11138daJesse Wilson        //            expires:  10 seconds from served date = 5 seconds from now
507953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.enqueue(new MockResponse()
50821dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS))
509953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS))
510953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .setBody("A"));
511953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.play();
512953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
513953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URL url = server.getUrl("/");
514953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
515953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection = url.openConnection();
516953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("A", readAscii(connection));
517953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertNull(connection.getHeaderField("Warning"));
51821dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
51921dddca4064527116af7a1553de502c6d11138daJesse Wilson
52021dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testDefaultExpirationDateConditionallyCached() throws Exception {
52121dddca4064527116af7a1553de502c6d11138daJesse Wilson        //      last modified: 115 seconds ago
52221dddca4064527116af7a1553de502c6d11138daJesse Wilson        //             served:  15 seconds ago
52321dddca4064527116af7a1553de502c6d11138daJesse Wilson        //   default lifetime: (115 - 15) / 10 = 10 seconds
52421dddca4064527116af7a1553de502c6d11138daJesse Wilson        //            expires:  10 seconds from served date = 5 seconds ago
52521dddca4064527116af7a1553de502c6d11138daJesse Wilson        String lastModifiedDate = formatDate(-115, TimeUnit.SECONDS);
5260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
52721dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
52821dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Date: " + formatDate(-15, TimeUnit.SECONDS)));
5290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = conditionalRequest.getHeaders();
5300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
5310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
533953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    public void testDefaultExpirationDateFullyCachedForMoreThan24Hours() throws Exception {
534953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        //      last modified: 105 days ago
535953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        //             served:   5 days ago
536953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        //   default lifetime: (105 - 5) / 10 = 10 days
537953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        //            expires:  10 days from served date = 5 days from now
538953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.enqueue(new MockResponse()
539953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.DAYS))
540953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Date: " + formatDate(-5, TimeUnit.DAYS))
541953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .setBody("A"));
542953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.play();
543953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
544953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
545953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
546953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("A", readAscii(connection));
547953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("113 HttpURLConnection \"Heuristic expiration\"",
548953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                connection.getHeaderField("Warning"));
549953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    }
550953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
55121dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testNoDefaultExpirationForUrlsWithQueryString() throws Exception {
55221dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.enqueue(new MockResponse()
55321dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS))
55421dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS))
55521dddca4064527116af7a1553de502c6d11138daJesse Wilson                .setBody("A"));
55621dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.enqueue(new MockResponse().setBody("B"));
55721dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.play();
55821dddca4064527116af7a1553de502c6d11138daJesse Wilson
55921dddca4064527116af7a1553de502c6d11138daJesse Wilson        URL url = server.getUrl("/?foo=bar");
56021dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertEquals("A", readAscii(url.openConnection()));
56121dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertEquals("B", readAscii(url.openConnection()));
56221dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
56321dddca4064527116af7a1553de502c6d11138daJesse Wilson
5640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testExpirationDateInThePastWithLastModifiedHeader() throws Exception {
5650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
5660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
5670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
5680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
5690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = conditionalRequest.getHeaders();
5700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
5710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
5730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testExpirationDateInThePastWithNoLastModifiedHeader() throws Exception {
5740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNotCached(new MockResponse()
5750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
5760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
5780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testExpirationDateInTheFuture() throws Exception {
5790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertFullyCached(new MockResponse()
5800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
5810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
5830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testMaxAgePreferredWithMaxAgeAndExpires() throws Exception {
5840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertFullyCached(new MockResponse()
5850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
5860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))
5870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
5880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
5900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testMaxAgeInThePastWithDateAndLastModifiedHeaders() throws Exception {
5910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
5920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
5930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS))
5940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
5950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
5960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = conditionalRequest.getHeaders();
5970c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
5980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testMaxAgeInThePastWithDateHeaderButNoLastModifiedHeader() throws Exception {
60184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        /*
60284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson         * Chrome interprets max-age relative to the local clock. Both our cache
60384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson         * and Firefox both use the earlier of the local and server's clock.
60484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson         */
6050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNotCached(new MockResponse()
6060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS))
6070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
6080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testMaxAgeInTheFutureWithDateHeader() throws Exception {
6110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertFullyCached(new MockResponse()
6120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
6130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
6140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testMaxAgeInTheFutureWithNoDateHeader() throws Exception {
6170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertFullyCached(new MockResponse()
6180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
6190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
621adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    public void testMaxAgeWithLastModifiedButNoServedDate() throws Exception {
622adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        assertFullyCached(new MockResponse()
623adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
624adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
625adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    }
626adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
627adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    public void testMaxAgeInTheFutureWithDateAndLastModifiedHeaders() throws Exception {
628adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        assertFullyCached(new MockResponse()
629adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
630adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
631adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
632adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    }
633adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
63421dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testMaxAgePreferredOverLowerSharedMaxAge() throws Exception {
63521dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertFullyCached(new MockResponse()
63621dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
63721dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: s-maxage=60")
63821dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: max-age=180"));
63921dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
64021dddca4064527116af7a1553de502c6d11138daJesse Wilson
64121dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testMaxAgePreferredOverHigherMaxAge() throws Exception {
64221dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertNotCached(new MockResponse()
64321dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
64421dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: s-maxage=180")
64521dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: max-age=60"));
64621dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
64721dddca4064527116af7a1553de502c6d11138daJesse Wilson
6480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodOptionsIsNotCached() throws Exception {
6490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("OPTIONS", false);
6500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodGetIsCached() throws Exception {
6530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("GET", true);
6540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodHeadIsNotCached() throws Exception {
6570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // We could support this but choose not to for implementation simplicity
6580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("HEAD", false);
6590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodPostIsNotCached() throws Exception {
6620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // We could support this but choose not to for implementation simplicity
6630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("POST", false);
6640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodPutIsNotCached() throws Exception {
6670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("PUT", false);
6680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodDeleteIsNotCached() throws Exception {
6710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("DELETE", false);
6720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodTraceIsNotCached() throws Exception {
6750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("TRACE", false);
6760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void testRequestMethod(String requestMethod, boolean expectCached) throws Exception {
6790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        /*
6800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 1. seed the cache (potentially)
6810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 2. expect a cache hit or miss
6820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         */
6830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
6840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
6850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("X-Response-ID: 1"));
6860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
6870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("X-Response-ID: 2"));
6880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
6890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
6910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        HttpURLConnection request1 = (HttpURLConnection) url.openConnection();
6930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        request1.setRequestMethod(requestMethod);
6940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        addRequestBodyIfNecessary(requestMethod, request1);
6950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("1", request1.getHeaderField("X-Response-ID"));
6960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
697953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection request2 = url.openConnection();
6980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        if (expectCached) {
6990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            assertEquals("1", request1.getHeaderField("X-Response-ID"));
7000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        } else {
7010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            assertEquals("2", request2.getHeaderField("X-Response-ID"));
7020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
7030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testPostInvalidatesCache() throws Exception {
7060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testMethodInvalidates("POST");
7070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testPutInvalidatesCache() throws Exception {
7100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testMethodInvalidates("PUT");
7110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testDeleteMethodInvalidatesCache() throws Exception {
7140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testMethodInvalidates("DELETE");
7150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void testMethodInvalidates(String requestMethod) throws Exception {
7180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        /*
7190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 1. seed the cache
7200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 2. invalidate it
7210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 3. expect a cache miss
7220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         */
7230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("A")
7240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
7250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
7260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("C"));
7270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
7280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
7300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
7320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        HttpURLConnection invalidate = (HttpURLConnection) url.openConnection();
7340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        invalidate.setRequestMethod(requestMethod);
7350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        addRequestBodyIfNecessary(requestMethod, invalidate);
7360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("B", readAscii(invalidate));
7370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("C", readAscii(url.openConnection()));
7390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testEtag() throws Exception {
7420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
7430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("ETag: v1"));
7440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(conditionalRequest.getHeaders().contains("If-None-Match: v1"));
7450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testEtagAndExpirationDateInThePast() throws Exception {
7480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
7490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
7500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("ETag: v1")
7510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
7520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
7530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = conditionalRequest.getHeaders();
7540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-None-Match: v1"));
7550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
7560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testEtagAndExpirationDateInTheFuture() throws Exception {
7590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertFullyCached(new MockResponse()
7600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("ETag: v1")
7610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
7620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
7630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testCacheControlNoCache() throws Exception {
7660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNotCached(new MockResponse().addHeader("Cache-Control: no-cache"));
7670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testCacheControlNoCacheAndExpirationDateInTheFuture() throws Exception {
7700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
7710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
7720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
7730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
7740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: no-cache"));
7750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = conditionalRequest.getHeaders();
7760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
7770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testPragmaNoCache() throws Exception {
7800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNotCached(new MockResponse().addHeader("Pragma: no-cache"));
7810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testPragmaNoCacheAndExpirationDateInTheFuture() throws Exception {
7840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
7850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
7860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
7870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
7880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Pragma: no-cache"));
7890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = conditionalRequest.getHeaders();
7900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
7910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testCacheControlNoStore() throws Exception {
7940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNotCached(new MockResponse().addHeader("Cache-Control: no-store"));
7950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7970c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testCacheControlNoStoreAndExpirationDateInTheFuture() throws Exception {
7980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNotCached(new MockResponse()
7990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
8000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
8010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: no-store"));
8020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
8030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testPartialRangeResponsesDoNotCorruptCache() throws Exception {
8050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        /*
8060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 1. request a range
8070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 2. request a full document, expecting a cache miss
8080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         */
8090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("AA")
8100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setResponseCode(HttpURLConnection.HTTP_PARTIAL)
8110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
8120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Content-Range: bytes 1000-1001/2000"));
8130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("BB"));
8140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
8150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
8170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URLConnection range = url.openConnection();
8190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        range.addRequestProperty("Range", "bytes=1000-1001");
8200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("AA", readAscii(range));
8210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("BB", readAscii(url.openConnection()));
8230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
8240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testServerReturnsDocumentOlderThanCache() throws Exception {
8260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("A")
8270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
8280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
8290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("B")
8300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-4, TimeUnit.HOURS)));
8310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
8320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
8340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
8360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
8370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
8380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testNonIdentityEncodingAndConditionalCache() throws Exception {
8400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNonIdentityEncodingCached(new MockResponse()
8410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
8420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
8430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
8440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testNonIdentityEncodingAndFullCache() throws Exception {
8460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNonIdentityEncodingCached(new MockResponse()
8470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
8480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
8490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
8500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void assertNonIdentityEncodingCached(MockResponse response) throws Exception {
8520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response
8530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
8540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Content-Encoding: gzip"));
8550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
8560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
8580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABCABCABC", readAscii(server.getUrl("/").openConnection()));
8590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABCABCABC", readAscii(server.getUrl("/").openConnection()));
8600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
8610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testExpiresDateBeforeModifiedDate() throws Exception {
8630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertConditionallyCached(new MockResponse()
8640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
8650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-2, TimeUnit.HOURS)));
8660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
8670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
868adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    public void testRequestMaxAge() throws IOException {
869adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        server.enqueue(new MockResponse().setBody("A")
870adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
871adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES))
872adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
873adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
874adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
875adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        server.play();
876adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
877adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
878adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
879adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        connection.addRequestProperty("Cache-Control", "max-age=30");
880adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        assertEquals("B", readAscii(connection));
881adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    }
882adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
883adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    public void testRequestMinFresh() throws IOException {
884adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        server.enqueue(new MockResponse().setBody("A")
885adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Cache-Control: max-age=60")
886adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
887adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
888adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
889adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        server.play();
890adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
891adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
892adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
893adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        connection.addRequestProperty("Cache-Control", "min-fresh=120");
894adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        assertEquals("B", readAscii(connection));
895adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    }
896adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
897c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    public void testRequestMaxStale() throws IOException {
898c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.enqueue(new MockResponse().setBody("A")
899c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson                .addHeader("Cache-Control: max-age=120")
900c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson                .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES)));
901c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.enqueue(new MockResponse().setBody("B"));
902c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
903c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.play();
904c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
905c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
906c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
907c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        connection.addRequestProperty("Cache-Control", "max-stale=180");
908c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertEquals("A", readAscii(connection));
909953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("110 HttpURLConnection \"Response is stale\"",
910953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                connection.getHeaderField("Warning"));
911c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    }
912c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
913c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    public void testRequestOnlyIfCachedWithNoResponseCached() throws IOException {
914c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        // (no responses enqueued)
915c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.play();
916c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
917953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
918c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        connection.addRequestProperty("Cache-Control", "only-if-cached");
919c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertBadGateway(connection);
920c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    }
921c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
922c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    public void testRequestOnlyIfCachedWithFullResponseCached() throws IOException {
923c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.enqueue(new MockResponse().setBody("A")
924c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson                .addHeader("Cache-Control: max-age=30")
925c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
926c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.play();
927c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
928c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
929c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
930c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        connection.addRequestProperty("Cache-Control", "only-if-cached");
931c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
932c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    }
933c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
934c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    public void testRequestOnlyIfCachedWithConditionalResponseCached() throws IOException {
935c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.enqueue(new MockResponse().setBody("A")
936c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson                .addHeader("Cache-Control: max-age=30")
937c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson                .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES)));
938c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.play();
939c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
940c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
941953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
942c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        connection.addRequestProperty("Cache-Control", "only-if-cached");
943c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertBadGateway(connection);
944c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    }
945c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
946c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    public void testRequestOnlyIfCachedWithUnhelpfulResponseCached() throws IOException {
947c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.enqueue(new MockResponse().setBody("A"));
948c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.play();
949c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
950c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
951953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
952c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        connection.addRequestProperty("Cache-Control", "only-if-cached");
953c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertBadGateway(connection);
954c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    }
955c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
95684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    public void testRequestCacheControlNoCache() throws Exception {
95784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(new MockResponse()
95884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
95984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
96084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Cache-Control: max-age=60")
96184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .setBody("A"));
96284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
96384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.play();
96484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
96584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        URL url = server.getUrl("/");
96684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
96784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        URLConnection connection = url.openConnection();
96884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        connection.setRequestProperty("Cache-Control", "no-cache");
96984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("B", readAscii(connection));
97084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    }
97184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
97284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    public void testRequestPragmaNoCache() throws Exception {
97384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(new MockResponse()
97484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
97584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
97684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Cache-Control: max-age=60")
97784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .setBody("A"));
97884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
97984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.play();
98084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
98184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        URL url = server.getUrl("/");
98284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
98384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        URLConnection connection = url.openConnection();
98484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        connection.setRequestProperty("Pragma", "no-cache");
98584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("B", readAscii(connection));
98684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    }
98784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
98884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    public void testClientSuppliedIfModifiedSinceWithCachedResult() throws Exception {
98984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        MockResponse response = new MockResponse()
99084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("ETag: v3")
99184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Cache-Control: max-age=0");
99284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        String ifModifiedSinceDate = formatDate(-24, TimeUnit.HOURS);
99384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        RecordedRequest request = assertClientSuppliedCondition(
99484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                response, "If-Modified-Since", ifModifiedSinceDate);
99584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        List<String> headers = request.getHeaders();
99684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + ifModifiedSinceDate));
99784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertFalse(headers.contains("If-None-Match: v3"));
99884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    }
99984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
100084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    public void testClientSuppliedIfNoneMatchSinceWithCachedResult() throws Exception {
100184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        String lastModifiedDate = formatDate(-3, TimeUnit.MINUTES);
100284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        MockResponse response = new MockResponse()
100384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
100484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
100584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Cache-Control: max-age=0");
100684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        RecordedRequest request = assertClientSuppliedCondition(
100784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                response, "If-None-Match", "v1");
100884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        List<String> headers = request.getHeaders();
100984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertTrue(headers.contains("If-None-Match: v1"));
101084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertFalse(headers.contains("If-Modified-Since: " + lastModifiedDate));
101184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    }
101284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
101384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    private RecordedRequest assertClientSuppliedCondition(MockResponse seed, String conditionName,
101484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson            String conditionValue) throws Exception {
101584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(seed.setBody("A"));
101684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
101784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.play();
101884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
101984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        URL url = server.getUrl("/");
102084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
102184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
102284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
102384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        connection.addRequestProperty(conditionName, conditionValue);
102484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode());
102584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("", readAscii(connection));
102684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
102784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.takeRequest(); // seed
102884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        return server.takeRequest();
102984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    }
103084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
103184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    public void testClientSuppliedConditionWithoutCachedResult() throws Exception {
103284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(new MockResponse()
103384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
103484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.play();
103584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
103684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
103784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        String clientIfModifiedSince = formatDate(-24, TimeUnit.HOURS);
103884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        connection.addRequestProperty("If-Modified-Since", clientIfModifiedSince);
103984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode());
104084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("", readAscii(connection));
104184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    }
1042c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
104321dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testAuthorizationRequestHeaderPreventsCaching() throws Exception {
104421dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.enqueue(new MockResponse()
104521dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.MINUTES))
104621dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: max-age=60")
104721dddca4064527116af7a1553de502c6d11138daJesse Wilson                .setBody("A"));
104821dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.enqueue(new MockResponse().setBody("B"));
104921dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.play();
105021dddca4064527116af7a1553de502c6d11138daJesse Wilson
105121dddca4064527116af7a1553de502c6d11138daJesse Wilson        URL url = server.getUrl("/");
1052953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection = url.openConnection();
105321dddca4064527116af7a1553de502c6d11138daJesse Wilson        connection.addRequestProperty("Authorization", "password");
105421dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertEquals("A", readAscii(connection));
105521dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertEquals("B", readAscii(url.openConnection()));
105621dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
105721dddca4064527116af7a1553de502c6d11138daJesse Wilson
105821dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testAuthorizationResponseCachedWithSMaxAge() throws Exception {
105921dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertAuthorizationRequestFullyCached(new MockResponse()
106021dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: s-maxage=60"));
106121dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
106221dddca4064527116af7a1553de502c6d11138daJesse Wilson
106321dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testAuthorizationResponseCachedWithPublic() throws Exception {
106421dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertAuthorizationRequestFullyCached(new MockResponse()
106521dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: public"));
106621dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
106721dddca4064527116af7a1553de502c6d11138daJesse Wilson
106821dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testAuthorizationResponseCachedWithMustRevalidate() throws Exception {
106921dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertAuthorizationRequestFullyCached(new MockResponse()
107021dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: must-revalidate"));
107121dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
107221dddca4064527116af7a1553de502c6d11138daJesse Wilson
107321dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void assertAuthorizationRequestFullyCached(MockResponse response) throws Exception {
107421dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.enqueue(response
107521dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: max-age=60")
107621dddca4064527116af7a1553de502c6d11138daJesse Wilson                .setBody("A"));
107721dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.enqueue(new MockResponse().setBody("B"));
107821dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.play();
107921dddca4064527116af7a1553de502c6d11138daJesse Wilson
108021dddca4064527116af7a1553de502c6d11138daJesse Wilson        URL url = server.getUrl("/");
1081953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection = url.openConnection();
108221dddca4064527116af7a1553de502c6d11138daJesse Wilson        connection.addRequestProperty("Authorization", "password");
108321dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertEquals("A", readAscii(connection));
108421dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertEquals("A", readAscii(url.openConnection()));
108521dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
108621dddca4064527116af7a1553de502c6d11138daJesse Wilson
108721dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testCacheControlMustRevalidate() throws Exception {
108821dddca4064527116af7a1553de502c6d11138daJesse Wilson        fail("Cache-Control: must-revalidate"); // TODO
108921dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
109021dddca4064527116af7a1553de502c6d11138daJesse Wilson
1091953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    public void testVaryResponsesAreNotSupported() throws Exception {
1092953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.enqueue(new MockResponse()
1093953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Cache-Control: max-age=60")
1094953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Vary: Accept-Language")
1095953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .setBody("A"));
1096953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
1097953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.play();
1098953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
1099953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URL url = server.getUrl("/");
1100953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection1 = url.openConnection();
1101953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        connection1.addRequestProperty("Accept-Language", "fr-CA");
1102953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("A", readAscii(connection1));
1103953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
1104953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection2 = url.openConnection();
1105953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        connection2.addRequestProperty("Accept-Language", "fr-CA");
1106953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("B", readAscii(connection2));
1107953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    }
1108953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
1109953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    public void testContentLocationDoesNotPopulateCache() throws Exception {
1110953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.enqueue(new MockResponse()
1111953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Cache-Control: max-age=60")
1112953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Content-Location: /bar")
1113953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .setBody("A"));
1114953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
1115953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.play();
1116953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
1117953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("A", readAscii(server.getUrl("/foo").openConnection()));
1118953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("B", readAscii(server.getUrl("/bar").openConnection()));
1119953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    }
1120953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
11210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
11220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * @param delta the offset from the current date to use. Negative
11230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *     values yield dates in the past; positive values yield dates in the
11240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *     future.
11250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
1126953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    private String formatDate(long delta, TimeUnit timeUnit) {
1127953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        Date date = new Date(System.currentTimeMillis() + timeUnit.toMillis(delta));
1128953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
1129953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
1130953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        return rfc1123.format(date);
11310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void addRequestBodyIfNecessary(String requestMethod, HttpURLConnection invalidate)
11340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            throws IOException {
11350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        if (requestMethod.equals("POST") || requestMethod.equals("PUT")) {
11360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            invalidate.setDoOutput(true);
11370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            OutputStream requestBody = invalidate.getOutputStream();
11380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            requestBody.write('x');
11390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            requestBody.close();
11400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
11410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void assertNotCached(MockResponse response) throws Exception {
11440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response.setBody("A"));
11450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
11460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
11470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
11490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
11500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("B", readAscii(url.openConnection()));
11510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
11540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * @return the request with the conditional get headers.
11550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
11560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private RecordedRequest assertConditionallyCached(MockResponse response) throws Exception {
11570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // scenario 1: condition succeeds
11580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response.setBody("A"));
11590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
11600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // scenario 2: condition fails
11620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response.setBody("B"));
11630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("C"));
11640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
11660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL valid = server.getUrl("/valid");
11680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(valid.openConnection()));
11690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(valid.openConnection()));
11700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL invalid = server.getUrl("/invalid");
11720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("B", readAscii(invalid.openConnection()));
11730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("C", readAscii(invalid.openConnection()));
11740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.takeRequest(); // regular get
11760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        return server.takeRequest(); // conditional get
11770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void assertFullyCached(MockResponse response) throws Exception {
11800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response.setBody("A"));
11810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response.setBody("B"));
11820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
11830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
11850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
11860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
11870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
11900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Shortens the body of {@code response} but not the corresponding headers.
11910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Only useful to test how clients respond to the premature conclusion of
11920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * the HTTP body.
11930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
11940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) {
11950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        response.setSocketPolicy(DISCONNECT_AT_END);
11960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = new ArrayList<String>(response.getHeaders());
11970c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        response.setBody(Arrays.copyOfRange(response.getBody(), 0, numBytesToKeep));
11980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        response.getHeaders().clear();
11990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        response.getHeaders().addAll(headers);
12000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        return response;
12010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
12020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
12040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Reads {@code count} characters from the stream. If the stream is
12050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * exhausted before {@code count} characters can be read, the remaining
12060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * characters are returned and the stream is closed.
12070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
12080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private String readAscii(URLConnection connection, int count) throws IOException {
120984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        HttpURLConnection httpConnection = (HttpURLConnection) connection;
121084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        InputStream in = httpConnection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST
121184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                ? connection.getInputStream()
121284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                : httpConnection.getErrorStream();
12130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        StringBuilder result = new StringBuilder();
12140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        for (int i = 0; i < count; i++) {
12150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            int value = in.read();
12160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            if (value == -1) {
12170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                in.close();
12180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                break;
12190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
12200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            result.append((char) value);
12210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
12220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        return result.toString();
12230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
12240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private String readAscii(URLConnection connection) throws IOException {
12260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        return readAscii(connection, Integer.MAX_VALUE);
12270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
12280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void reliableSkip(InputStream in, int length) throws IOException {
12300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        while (length > 0) {
12310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            length -= in.skip(length);
12320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
12330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
12340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1235953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    private void assertBadGateway(HttpURLConnection connection) throws IOException {
1236c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        try {
1237c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson            connection.getInputStream();
1238c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson            fail();
1239c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        } catch (FileNotFoundException expected) {
1240c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        }
1241953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals(HttpURLConnection.HTTP_BAD_GATEWAY, connection.getResponseCode());
1242953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals(-1, connection.getErrorStream().read());
1243c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    }
1244c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
12450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    enum TransferKind {
12460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        CHUNKED() {
12470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            @Override void setBody(MockResponse response, byte[] content, int chunkSize)
12480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    throws IOException {
12490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                response.setChunkedBody(content, chunkSize);
12500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
12510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        },
12520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        FIXED_LENGTH() {
12530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
12540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                response.setBody(content);
12550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
12560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        },
12570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        END_OF_STREAM() {
12580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
12590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                response.setBody(content);
12600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                response.setSocketPolicy(DISCONNECT_AT_END);
12610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) {
12620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    if (h.next().startsWith("Content-Length:")) {
12630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                        h.remove();
12640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                        break;
12650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    }
12660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                }
12670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
12680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        };
12690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        abstract void setBody(MockResponse response, byte[] content, int chunkSize)
12710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                throws IOException;
12720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        void setBody(MockResponse response, String content, int chunkSize) throws IOException {
12740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            setBody(response, content.getBytes("UTF-8"), chunkSize);
12750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
12760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
12770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private <T> List<T> toListOrNull(T[] arrayOrNull) {
12790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        return arrayOrNull != null ? Arrays.asList(arrayOrNull) : null;
12800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
12810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
12830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Returns a gzipped copy of {@code bytes}.
12840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
12850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public byte[] gzip(byte[] bytes) throws IOException {
12860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
12870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        OutputStream gzippedOut = new GZIPOutputStream(bytesOut);
12880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        gzippedOut.write(bytes);
12890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        gzippedOut.close();
12900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        return bytesOut.toByteArray();
12910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
12920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private static class InsecureResponseCache extends ResponseCache {
12940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        private final HttpResponseCache delegate = new HttpResponseCache();
12950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
12970c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            return delegate.put(uri, connection);
12980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
12990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
13000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        @Override public CacheResponse get(URI uri, String requestMethod,
13010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                Map<String, List<String>> requestHeaders) throws IOException {
13020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            final CacheResponse response = delegate.get(uri, requestMethod, requestHeaders);
13030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            if (response instanceof SecureCacheResponse) {
13040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                return new CacheResponse() {
13050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    @Override public InputStream getBody() throws IOException {
13060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                        return response.getBody();
13070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    }
13080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    @Override public Map<String, List<String>> getHeaders() throws IOException {
13090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                        return response.getHeaders();
13100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    }
13110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                };
13120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
13130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            return response;
13140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
13150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
13160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson}
1317