HttpResponseCacheTest.java revision 953df613522e12a418cb7cb73248594d6c9f53d4
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
3210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testResponseCachingAndRedirects() throws IOException {
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
3440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testSecureResponseCachingAndRedirects() throws IOException {
3450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        TestSSLContext testSSLContext = TestSSLContext.create();
3460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
3470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
3480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
3490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
3500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
3510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Location: /foo"));
3520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
3530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
3540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
3550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setBody("ABC"));
3560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("DEF"));
3570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
3580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
3600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
3610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABC", readAscii(connection));
3620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
3640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
3650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABC", readAscii(connection));
3660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
3680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(2, cache.getHitCount());
3690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
3700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException {
3720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("ABC"));
3730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
3740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        final AtomicReference<Map<String, List<String>>> requestHeadersRef
3760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                = new AtomicReference<Map<String, List<String>>>();
3770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        ResponseCache.setDefault(new ResponseCache() {
3780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            @Override public CacheResponse get(URI uri, String requestMethod,
3790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    Map<String, List<String>> requestHeaders) throws IOException {
3800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                requestHeadersRef.set(requestHeaders);
3810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                return null;
3820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
3830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
3840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                return null;
3850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
3860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        });
3870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
3890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URLConnection urlConnection = url.openConnection();
3900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        urlConnection.addRequestProperty("A", "android");
3910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        readAscii(urlConnection);
3920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A"));
3930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
3940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
3960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException {
3970c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testServerPrematureDisconnect(TransferKind.FIXED_LENGTH);
3980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
3990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException {
4010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testServerPrematureDisconnect(TransferKind.CHUNKED);
4020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException {
4050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        /*
4060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * Intentionally empty. This case doesn't make sense because there's no
4070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * such thing as a premature disconnect when the disconnect itself
4080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * indicates the end of the data stream.
4090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         */
4100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException {
4130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        MockResponse response = new MockResponse();
4140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16);
4150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(truncateViolently(response, 16));
4160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("Request #2"));
4170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
4180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        BufferedReader reader = new BufferedReader(new InputStreamReader(
4200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                server.getUrl("/").openConnection().getInputStream()));
4210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABCDE", reader.readLine());
4220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        try {
4230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            reader.readLine();
4240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            fail("This implementation silently ignored a truncated HTTP body.");
4250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        } catch (IOException expected) {
4260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
4270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getAbortCount());
4290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(0, cache.getSuccessCount());
4300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
4310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("Request #2", readAscii(connection));
4320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getAbortCount());
4330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getSuccessCount());
4340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException {
4370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testClientPrematureDisconnect(TransferKind.FIXED_LENGTH);
4380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException {
4410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testClientPrematureDisconnect(TransferKind.CHUNKED);
4420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException {
4450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testClientPrematureDisconnect(TransferKind.END_OF_STREAM);
4460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException {
4490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        MockResponse response = new MockResponse();
4500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024);
4510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response);
4520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("Request #2"));
4530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
4540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
4560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        InputStream in = connection.getInputStream();
4570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABCDE", readAscii(connection, 5));
4580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        in.close();
4590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        try {
4600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            in.read();
4610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            fail("Expected an IOException because the stream is closed.");
4620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        } catch (IOException expected) {
4630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
4640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
4650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getAbortCount());
4660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(0, cache.getSuccessCount());
4670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        connection = server.getUrl("/").openConnection();
46884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("Request #2", readAscii(connection));
4690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getAbortCount());
4700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals(1, cache.getSuccessCount());
4710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
4720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
473953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    public void testDefaultExpirationDateFullyCachedForLessThan24Hours() throws Exception {
47421dddca4064527116af7a1553de502c6d11138daJesse Wilson        //      last modified: 105 seconds ago
47521dddca4064527116af7a1553de502c6d11138daJesse Wilson        //             served:   5 seconds ago
47621dddca4064527116af7a1553de502c6d11138daJesse Wilson        //   default lifetime: (105 - 5) / 10 = 10 seconds
47721dddca4064527116af7a1553de502c6d11138daJesse Wilson        //            expires:  10 seconds from served date = 5 seconds from now
478953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.enqueue(new MockResponse()
47921dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS))
480953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS))
481953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .setBody("A"));
482953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.play();
483953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
484953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URL url = server.getUrl("/");
485953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
486953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection = url.openConnection();
487953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("A", readAscii(connection));
488953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertNull(connection.getHeaderField("Warning"));
48921dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
49021dddca4064527116af7a1553de502c6d11138daJesse Wilson
49121dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testDefaultExpirationDateConditionallyCached() throws Exception {
49221dddca4064527116af7a1553de502c6d11138daJesse Wilson        //      last modified: 115 seconds ago
49321dddca4064527116af7a1553de502c6d11138daJesse Wilson        //             served:  15 seconds ago
49421dddca4064527116af7a1553de502c6d11138daJesse Wilson        //   default lifetime: (115 - 15) / 10 = 10 seconds
49521dddca4064527116af7a1553de502c6d11138daJesse Wilson        //            expires:  10 seconds from served date = 5 seconds ago
49621dddca4064527116af7a1553de502c6d11138daJesse Wilson        String lastModifiedDate = formatDate(-115, TimeUnit.SECONDS);
4970c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
49821dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
49921dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Date: " + formatDate(-15, TimeUnit.SECONDS)));
5000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = conditionalRequest.getHeaders();
5010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
5020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
504953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    public void testDefaultExpirationDateFullyCachedForMoreThan24Hours() throws Exception {
505953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        //      last modified: 105 days ago
506953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        //             served:   5 days ago
507953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        //   default lifetime: (105 - 5) / 10 = 10 days
508953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        //            expires:  10 days from served date = 5 days from now
509953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.enqueue(new MockResponse()
510953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.DAYS))
511953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Date: " + formatDate(-5, TimeUnit.DAYS))
512953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .setBody("A"));
513953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.play();
514953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
515953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
516953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
517953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("A", readAscii(connection));
518953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("113 HttpURLConnection \"Heuristic expiration\"",
519953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                connection.getHeaderField("Warning"));
520953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    }
521953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
52221dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testNoDefaultExpirationForUrlsWithQueryString() throws Exception {
52321dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.enqueue(new MockResponse()
52421dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS))
52521dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS))
52621dddca4064527116af7a1553de502c6d11138daJesse Wilson                .setBody("A"));
52721dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.enqueue(new MockResponse().setBody("B"));
52821dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.play();
52921dddca4064527116af7a1553de502c6d11138daJesse Wilson
53021dddca4064527116af7a1553de502c6d11138daJesse Wilson        URL url = server.getUrl("/?foo=bar");
53121dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertEquals("A", readAscii(url.openConnection()));
53221dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertEquals("B", readAscii(url.openConnection()));
53321dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
53421dddca4064527116af7a1553de502c6d11138daJesse Wilson
5350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testExpirationDateInThePastWithLastModifiedHeader() throws Exception {
5360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
5370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
5380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
5390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
5400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = conditionalRequest.getHeaders();
5410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
5420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
5440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testExpirationDateInThePastWithNoLastModifiedHeader() throws Exception {
5450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNotCached(new MockResponse()
5460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
5470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
5490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testExpirationDateInTheFuture() throws Exception {
5500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertFullyCached(new MockResponse()
5510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
5520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
5540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testMaxAgePreferredWithMaxAgeAndExpires() throws Exception {
5550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertFullyCached(new MockResponse()
5560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
5570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))
5580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
5590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
5610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testMaxAgeInThePastWithDateAndLastModifiedHeaders() throws Exception {
5620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
5630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
5640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS))
5650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
5660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
5670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = conditionalRequest.getHeaders();
5680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
5690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
5710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testMaxAgeInThePastWithDateHeaderButNoLastModifiedHeader() throws Exception {
57284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        /*
57384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson         * Chrome interprets max-age relative to the local clock. Both our cache
57484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson         * and Firefox both use the earlier of the local and server's clock.
57584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson         */
5760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNotCached(new MockResponse()
5770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS))
5780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
5790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
5810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testMaxAgeInTheFutureWithDateHeader() throws Exception {
5820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertFullyCached(new MockResponse()
5830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
5840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
5850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
5870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testMaxAgeInTheFutureWithNoDateHeader() throws Exception {
5880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertFullyCached(new MockResponse()
5890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
5900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
5910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
592adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    public void testMaxAgeWithLastModifiedButNoServedDate() throws Exception {
593adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        assertFullyCached(new MockResponse()
594adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
595adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
596adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    }
597adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
598adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    public void testMaxAgeInTheFutureWithDateAndLastModifiedHeaders() throws Exception {
599adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        assertFullyCached(new MockResponse()
600adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
601adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
602adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Cache-Control: max-age=60"));
603adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    }
604adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
60521dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testMaxAgePreferredOverLowerSharedMaxAge() throws Exception {
60621dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertFullyCached(new MockResponse()
60721dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
60821dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: s-maxage=60")
60921dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: max-age=180"));
61021dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
61121dddca4064527116af7a1553de502c6d11138daJesse Wilson
61221dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testMaxAgePreferredOverHigherMaxAge() throws Exception {
61321dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertNotCached(new MockResponse()
61421dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
61521dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: s-maxage=180")
61621dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: max-age=60"));
61721dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
61821dddca4064527116af7a1553de502c6d11138daJesse Wilson
6190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodOptionsIsNotCached() throws Exception {
6200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("OPTIONS", false);
6210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodGetIsCached() throws Exception {
6240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("GET", true);
6250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodHeadIsNotCached() throws Exception {
6280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // We could support this but choose not to for implementation simplicity
6290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("HEAD", false);
6300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodPostIsNotCached() throws Exception {
6330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // We could support this but choose not to for implementation simplicity
6340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("POST", false);
6350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodPutIsNotCached() throws Exception {
6380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("PUT", false);
6390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodDeleteIsNotCached() throws Exception {
6420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("DELETE", false);
6430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testRequestMethodTraceIsNotCached() throws Exception {
6460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testRequestMethod("TRACE", false);
6470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void testRequestMethod(String requestMethod, boolean expectCached) throws Exception {
6500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        /*
6510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 1. seed the cache (potentially)
6520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 2. expect a cache hit or miss
6530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         */
6540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
6550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
6560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("X-Response-ID: 1"));
6570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse()
6580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("X-Response-ID: 2"));
6590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
6600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
6620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        HttpURLConnection request1 = (HttpURLConnection) url.openConnection();
6640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        request1.setRequestMethod(requestMethod);
6650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        addRequestBodyIfNecessary(requestMethod, request1);
6660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("1", request1.getHeaderField("X-Response-ID"));
6670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
668953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection request2 = url.openConnection();
6690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        if (expectCached) {
6700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            assertEquals("1", request1.getHeaderField("X-Response-ID"));
6710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        } else {
6720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            assertEquals("2", request2.getHeaderField("X-Response-ID"));
6730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
6740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testPostInvalidatesCache() throws Exception {
6770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testMethodInvalidates("POST");
6780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testPutInvalidatesCache() throws Exception {
6810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testMethodInvalidates("PUT");
6820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testDeleteMethodInvalidatesCache() throws Exception {
6850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        testMethodInvalidates("DELETE");
6860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
6870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
6880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void testMethodInvalidates(String requestMethod) throws Exception {
6890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        /*
6900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 1. seed the cache
6910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 2. invalidate it
6920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 3. expect a cache miss
6930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         */
6940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("A")
6950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
6960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
6970c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("C"));
6980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
6990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
7010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
7030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        HttpURLConnection invalidate = (HttpURLConnection) url.openConnection();
7050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        invalidate.setRequestMethod(requestMethod);
7060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        addRequestBodyIfNecessary(requestMethod, invalidate);
7070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("B", readAscii(invalidate));
7080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("C", readAscii(url.openConnection()));
7100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testEtag() throws Exception {
7130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
7140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("ETag: v1"));
7150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(conditionalRequest.getHeaders().contains("If-None-Match: v1"));
7160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testEtagAndExpirationDateInThePast() throws Exception {
7190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
7200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
7210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("ETag: v1")
7220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
7230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
7240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = conditionalRequest.getHeaders();
7250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-None-Match: v1"));
7260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
7270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testEtagAndExpirationDateInTheFuture() throws Exception {
7300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertFullyCached(new MockResponse()
7310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("ETag: v1")
7320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
7330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
7340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testCacheControlNoCache() throws Exception {
7370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNotCached(new MockResponse().addHeader("Cache-Control: no-cache"));
7380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testCacheControlNoCacheAndExpirationDateInTheFuture() throws Exception {
7410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
7420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
7430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
7440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
7450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: no-cache"));
7460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = conditionalRequest.getHeaders();
7470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
7480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testPragmaNoCache() throws Exception {
7510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNotCached(new MockResponse().addHeader("Pragma: no-cache"));
7520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testPragmaNoCacheAndExpirationDateInTheFuture() throws Exception {
7550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
7560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
7570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
7580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
7590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Pragma: no-cache"));
7600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = conditionalRequest.getHeaders();
7610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
7620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testCacheControlNoStore() throws Exception {
7650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNotCached(new MockResponse().addHeader("Cache-Control: no-store"));
7660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testCacheControlNoStoreAndExpirationDateInTheFuture() throws Exception {
7690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNotCached(new MockResponse()
7700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
7710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
7720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Cache-Control: no-store"));
7730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testPartialRangeResponsesDoNotCorruptCache() throws Exception {
7760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        /*
7770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 1. request a range
7780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         * 2. request a full document, expecting a cache miss
7790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson         */
7800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("AA")
7810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setResponseCode(HttpURLConnection.HTTP_PARTIAL)
7820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
7830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Content-Range: bytes 1000-1001/2000"));
7840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("BB"));
7850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
7860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
7880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URLConnection range = url.openConnection();
7900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        range.addRequestProperty("Range", "bytes=1000-1001");
7910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("AA", readAscii(range));
7920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("BB", readAscii(url.openConnection()));
7940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
7950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
7960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testServerReturnsDocumentOlderThanCache() throws Exception {
7970c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("A")
7980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
7990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
8000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("B")
8010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-4, TimeUnit.HOURS)));
8020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
8030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
8050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
8070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
8080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
8090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testNonIdentityEncodingAndConditionalCache() throws Exception {
8110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNonIdentityEncodingCached(new MockResponse()
8120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
8130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
8140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
8150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testNonIdentityEncodingAndFullCache() throws Exception {
8170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertNonIdentityEncodingCached(new MockResponse()
8180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
8190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
8200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
8210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void assertNonIdentityEncodingCached(MockResponse response) throws Exception {
8230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response
8240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
8250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Content-Encoding: gzip"));
8260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
8270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
8290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABCABCABC", readAscii(server.getUrl("/").openConnection()));
8300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("ABCABCABC", readAscii(server.getUrl("/").openConnection()));
8310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
8320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
8330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void testExpiresDateBeforeModifiedDate() throws Exception {
8340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertConditionallyCached(new MockResponse()
8350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
8360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                .addHeader("Expires: " + formatDate(-2, TimeUnit.HOURS)));
8370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
8380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
839adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    public void testRequestMaxAge() throws IOException {
840adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        server.enqueue(new MockResponse().setBody("A")
841adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
842adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES))
843adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
844adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
845adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
846adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        server.play();
847adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
848adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
849adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
850adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        connection.addRequestProperty("Cache-Control", "max-age=30");
851adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        assertEquals("B", readAscii(connection));
852adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    }
853adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
854adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    public void testRequestMinFresh() throws IOException {
855adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        server.enqueue(new MockResponse().setBody("A")
856adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Cache-Control: max-age=60")
857adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
858adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
859adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
860adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        server.play();
861adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
862adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
863adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
864adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        connection.addRequestProperty("Cache-Control", "min-fresh=120");
865adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson        assertEquals("B", readAscii(connection));
866adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson    }
867adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson
868c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    public void testRequestMaxStale() throws IOException {
869c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.enqueue(new MockResponse().setBody("A")
870c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson                .addHeader("Cache-Control: max-age=120")
871c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson                .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES)));
872c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.enqueue(new MockResponse().setBody("B"));
873c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
874c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.play();
875c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
876c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
877c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
878c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        connection.addRequestProperty("Cache-Control", "max-stale=180");
879c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertEquals("A", readAscii(connection));
880953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("110 HttpURLConnection \"Response is stale\"",
881953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                connection.getHeaderField("Warning"));
882c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    }
883c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
884c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    public void testRequestOnlyIfCachedWithNoResponseCached() throws IOException {
885c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        // (no responses enqueued)
886c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.play();
887c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
888953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
889c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        connection.addRequestProperty("Cache-Control", "only-if-cached");
890c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertBadGateway(connection);
891c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    }
892c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
893c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    public void testRequestOnlyIfCachedWithFullResponseCached() throws IOException {
894c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.enqueue(new MockResponse().setBody("A")
895c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson                .addHeader("Cache-Control: max-age=30")
896c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
897c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.play();
898c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
899c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
900c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        URLConnection connection = server.getUrl("/").openConnection();
901c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        connection.addRequestProperty("Cache-Control", "only-if-cached");
902c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
903c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    }
904c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
905c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    public void testRequestOnlyIfCachedWithConditionalResponseCached() throws IOException {
906c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.enqueue(new MockResponse().setBody("A")
907c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson                .addHeader("Cache-Control: max-age=30")
908c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson                .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES)));
909c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.play();
910c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
911c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
912953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
913c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        connection.addRequestProperty("Cache-Control", "only-if-cached");
914c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertBadGateway(connection);
915c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    }
916c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
917c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    public void testRequestOnlyIfCachedWithUnhelpfulResponseCached() throws IOException {
918c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.enqueue(new MockResponse().setBody("A"));
919c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        server.play();
920c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
921c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
922953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
923c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        connection.addRequestProperty("Cache-Control", "only-if-cached");
924c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        assertBadGateway(connection);
925c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    }
926c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
92784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    public void testRequestCacheControlNoCache() throws Exception {
92884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(new MockResponse()
92984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
93084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
93184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Cache-Control: max-age=60")
93284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .setBody("A"));
93384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
93484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.play();
93584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
93684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        URL url = server.getUrl("/");
93784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
93884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        URLConnection connection = url.openConnection();
93984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        connection.setRequestProperty("Cache-Control", "no-cache");
94084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("B", readAscii(connection));
94184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    }
94284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
94384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    public void testRequestPragmaNoCache() throws Exception {
94484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(new MockResponse()
94584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
94684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
94784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Cache-Control: max-age=60")
94884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .setBody("A"));
94984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
95084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.play();
95184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
95284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        URL url = server.getUrl("/");
95384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
95484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        URLConnection connection = url.openConnection();
95584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        connection.setRequestProperty("Pragma", "no-cache");
95684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("B", readAscii(connection));
95784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    }
95884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
95984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    public void testClientSuppliedIfModifiedSinceWithCachedResult() throws Exception {
96084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        MockResponse response = new MockResponse()
96184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("ETag: v3")
96284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Cache-Control: max-age=0");
96384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        String ifModifiedSinceDate = formatDate(-24, TimeUnit.HOURS);
96484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        RecordedRequest request = assertClientSuppliedCondition(
96584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                response, "If-Modified-Since", ifModifiedSinceDate);
96684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        List<String> headers = request.getHeaders();
96784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertTrue(headers.contains("If-Modified-Since: " + ifModifiedSinceDate));
96884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertFalse(headers.contains("If-None-Match: v3"));
96984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    }
97084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
97184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    public void testClientSuppliedIfNoneMatchSinceWithCachedResult() throws Exception {
97284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        String lastModifiedDate = formatDate(-3, TimeUnit.MINUTES);
97384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        MockResponse response = new MockResponse()
97484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Last-Modified: " + lastModifiedDate)
97584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
97684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .addHeader("Cache-Control: max-age=0");
97784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        RecordedRequest request = assertClientSuppliedCondition(
97884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                response, "If-None-Match", "v1");
97984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        List<String> headers = request.getHeaders();
98084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertTrue(headers.contains("If-None-Match: v1"));
98184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertFalse(headers.contains("If-Modified-Since: " + lastModifiedDate));
98284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    }
98384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
98484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    private RecordedRequest assertClientSuppliedCondition(MockResponse seed, String conditionName,
98584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson            String conditionValue) throws Exception {
98684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(seed.setBody("A"));
98784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
98884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.play();
98984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
99084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        URL url = server.getUrl("/");
99184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
99284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
99384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
99484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        connection.addRequestProperty(conditionName, conditionValue);
99584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode());
99684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("", readAscii(connection));
99784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
99884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.takeRequest(); // seed
99984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        return server.takeRequest();
100084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    }
100184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
100284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    public void testClientSuppliedConditionWithoutCachedResult() throws Exception {
100384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.enqueue(new MockResponse()
100484f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
100584f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        server.play();
100684f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson
100784f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
100884f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        String clientIfModifiedSince = formatDate(-24, TimeUnit.HOURS);
100984f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        connection.addRequestProperty("If-Modified-Since", clientIfModifiedSince);
101084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode());
101184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        assertEquals("", readAscii(connection));
101284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson    }
1013c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
101421dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testAuthorizationRequestHeaderPreventsCaching() throws Exception {
101521dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.enqueue(new MockResponse()
101621dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.MINUTES))
101721dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: max-age=60")
101821dddca4064527116af7a1553de502c6d11138daJesse Wilson                .setBody("A"));
101921dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.enqueue(new MockResponse().setBody("B"));
102021dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.play();
102121dddca4064527116af7a1553de502c6d11138daJesse Wilson
102221dddca4064527116af7a1553de502c6d11138daJesse Wilson        URL url = server.getUrl("/");
1023953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection = url.openConnection();
102421dddca4064527116af7a1553de502c6d11138daJesse Wilson        connection.addRequestProperty("Authorization", "password");
102521dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertEquals("A", readAscii(connection));
102621dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertEquals("B", readAscii(url.openConnection()));
102721dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
102821dddca4064527116af7a1553de502c6d11138daJesse Wilson
102921dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testAuthorizationResponseCachedWithSMaxAge() throws Exception {
103021dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertAuthorizationRequestFullyCached(new MockResponse()
103121dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: s-maxage=60"));
103221dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
103321dddca4064527116af7a1553de502c6d11138daJesse Wilson
103421dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testAuthorizationResponseCachedWithPublic() throws Exception {
103521dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertAuthorizationRequestFullyCached(new MockResponse()
103621dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: public"));
103721dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
103821dddca4064527116af7a1553de502c6d11138daJesse Wilson
103921dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testAuthorizationResponseCachedWithMustRevalidate() throws Exception {
104021dddca4064527116af7a1553de502c6d11138daJesse Wilson        assertAuthorizationRequestFullyCached(new MockResponse()
104121dddca4064527116af7a1553de502c6d11138daJesse Wilson                .addHeader("Cache-Control: must-revalidate"));
104221dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
104321dddca4064527116af7a1553de502c6d11138daJesse Wilson
104421dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void assertAuthorizationRequestFullyCached(MockResponse response) throws Exception {
104521dddca4064527116af7a1553de502c6d11138daJesse Wilson        server.enqueue(response
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("A", readAscii(url.openConnection()));
105621dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
105721dddca4064527116af7a1553de502c6d11138daJesse Wilson
105821dddca4064527116af7a1553de502c6d11138daJesse Wilson    public void testCacheControlMustRevalidate() throws Exception {
105921dddca4064527116af7a1553de502c6d11138daJesse Wilson        fail("Cache-Control: must-revalidate"); // TODO
106021dddca4064527116af7a1553de502c6d11138daJesse Wilson    }
106121dddca4064527116af7a1553de502c6d11138daJesse Wilson
1062953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    public void testVaryResponsesAreNotSupported() throws Exception {
1063953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.enqueue(new MockResponse()
1064953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Cache-Control: max-age=60")
1065953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Vary: Accept-Language")
1066953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .setBody("A"));
1067953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
1068953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.play();
1069953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
1070953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URL url = server.getUrl("/");
1071953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection1 = url.openConnection();
1072953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        connection1.addRequestProperty("Accept-Language", "fr-CA");
1073953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("A", readAscii(connection1));
1074953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
1075953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        URLConnection connection2 = url.openConnection();
1076953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        connection2.addRequestProperty("Accept-Language", "fr-CA");
1077953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("B", readAscii(connection2));
1078953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    }
1079953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
1080953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    public void testContentLocationDoesNotPopulateCache() throws Exception {
1081953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.enqueue(new MockResponse()
1082953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Cache-Control: max-age=60")
1083953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .addHeader("Content-Location: /bar")
1084953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson                .setBody("A"));
1085953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
1086953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        server.play();
1087953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
1088953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("A", readAscii(server.getUrl("/foo").openConnection()));
1089953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals("B", readAscii(server.getUrl("/bar").openConnection()));
1090953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    }
1091953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson
10920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
10930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * @param delta the offset from the current date to use. Negative
10940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *     values yield dates in the past; positive values yield dates in the
10950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *     future.
10960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
1097953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    private String formatDate(long delta, TimeUnit timeUnit) {
1098953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        Date date = new Date(System.currentTimeMillis() + timeUnit.toMillis(delta));
1099953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
1100953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
1101953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        return rfc1123.format(date);
11020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void addRequestBodyIfNecessary(String requestMethod, HttpURLConnection invalidate)
11050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            throws IOException {
11060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        if (requestMethod.equals("POST") || requestMethod.equals("PUT")) {
11070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            invalidate.setDoOutput(true);
11080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            OutputStream requestBody = invalidate.getOutputStream();
11090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            requestBody.write('x');
11100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            requestBody.close();
11110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
11120c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11130c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11140c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void assertNotCached(MockResponse response) throws Exception {
11150c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response.setBody("A"));
11160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("B"));
11170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
11180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
11200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
11210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("B", readAscii(url.openConnection()));
11220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
11250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * @return the request with the conditional get headers.
11260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
11270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private RecordedRequest assertConditionallyCached(MockResponse response) throws Exception {
11280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // scenario 1: condition succeeds
11290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response.setBody("A"));
11300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
11310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // scenario 2: condition fails
11330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response.setBody("B"));
11340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(new MockResponse().setBody("C"));
11350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
11370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL valid = server.getUrl("/valid");
11390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(valid.openConnection()));
11400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(valid.openConnection()));
11410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL invalid = server.getUrl("/invalid");
11430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("B", readAscii(invalid.openConnection()));
11440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("C", readAscii(invalid.openConnection()));
11450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.takeRequest(); // regular get
11470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        return server.takeRequest(); // conditional get
11480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void assertFullyCached(MockResponse response) throws Exception {
11510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response.setBody("A"));
11520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.enqueue(response.setBody("B"));
11530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        server.play();
11540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        URL url = server.getUrl("/");
11560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
11570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        assertEquals("A", readAscii(url.openConnection()));
11580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
11610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Shortens the body of {@code response} but not the corresponding headers.
11620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Only useful to test how clients respond to the premature conclusion of
11630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * the HTTP body.
11640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
11650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) {
11660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        response.setSocketPolicy(DISCONNECT_AT_END);
11670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        List<String> headers = new ArrayList<String>(response.getHeaders());
11680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        response.setBody(Arrays.copyOfRange(response.getBody(), 0, numBytesToKeep));
11690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        response.getHeaders().clear();
11700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        response.getHeaders().addAll(headers);
11710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        return response;
11720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
11750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Reads {@code count} characters from the stream. If the stream is
11760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * exhausted before {@code count} characters can be read, the remaining
11770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * characters are returned and the stream is closed.
11780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
11790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private String readAscii(URLConnection connection, int count) throws IOException {
118084f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        HttpURLConnection httpConnection = (HttpURLConnection) connection;
118184f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson        InputStream in = httpConnection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST
118284f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                ? connection.getInputStream()
118384f1fd18b9db6fc6f2bb65694bee99d42f88bb79Jesse Wilson                : httpConnection.getErrorStream();
11840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        StringBuilder result = new StringBuilder();
11850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        for (int i = 0; i < count; i++) {
11860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            int value = in.read();
11870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            if (value == -1) {
11880c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                in.close();
11890c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                break;
11900c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
11910c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            result.append((char) value);
11920c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
11930c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        return result.toString();
11940c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11950c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
11960c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private String readAscii(URLConnection connection) throws IOException {
11970c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        return readAscii(connection, Integer.MAX_VALUE);
11980c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
11990c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12000c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private void reliableSkip(InputStream in, int length) throws IOException {
12010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        while (length > 0) {
12020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            length -= in.skip(length);
12030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
12040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
12050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1206953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson    private void assertBadGateway(HttpURLConnection connection) throws IOException {
1207c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        try {
1208c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson            connection.getInputStream();
1209c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson            fail();
1210c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        } catch (FileNotFoundException expected) {
1211c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson        }
1212953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals(HttpURLConnection.HTTP_BAD_GATEWAY, connection.getResponseCode());
1213953df613522e12a418cb7cb73248594d6c9f53d4Jesse Wilson        assertEquals(-1, connection.getErrorStream().read());
1214c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson    }
1215c9e12f729cb962eb60754e4500312421c46e71ddJesse Wilson
12160c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    enum TransferKind {
12170c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        CHUNKED() {
12180c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            @Override void setBody(MockResponse response, byte[] content, int chunkSize)
12190c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    throws IOException {
12200c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                response.setChunkedBody(content, chunkSize);
12210c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
12220c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        },
12230c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        FIXED_LENGTH() {
12240c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
12250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                response.setBody(content);
12260c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
12270c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        },
12280c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        END_OF_STREAM() {
12290c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
12300c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                response.setBody(content);
12310c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                response.setSocketPolicy(DISCONNECT_AT_END);
12320c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) {
12330c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    if (h.next().startsWith("Content-Length:")) {
12340c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                        h.remove();
12350c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                        break;
12360c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    }
12370c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                }
12380c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
12390c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        };
12400c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        abstract void setBody(MockResponse response, byte[] content, int chunkSize)
12420c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                throws IOException;
12430c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        void setBody(MockResponse response, String content, int chunkSize) throws IOException {
12450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            setBody(response, content.getBytes("UTF-8"), chunkSize);
12460c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
12470c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
12480c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12490c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private <T> List<T> toListOrNull(T[] arrayOrNull) {
12500c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        return arrayOrNull != null ? Arrays.asList(arrayOrNull) : null;
12510c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
12520c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12530c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
12540c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Returns a gzipped copy of {@code bytes}.
12550c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
12560c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public byte[] gzip(byte[] bytes) throws IOException {
12570c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
12580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        OutputStream gzippedOut = new GZIPOutputStream(bytesOut);
12590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        gzippedOut.write(bytes);
12600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        gzippedOut.close();
12610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        return bytesOut.toByteArray();
12620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
12630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    private static class InsecureResponseCache extends ResponseCache {
12650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        private final HttpResponseCache delegate = new HttpResponseCache();
12660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
12680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            return delegate.put(uri, connection);
12690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
12700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
12710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        @Override public CacheResponse get(URI uri, String requestMethod,
12720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                Map<String, List<String>> requestHeaders) throws IOException {
12730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            final CacheResponse response = delegate.get(uri, requestMethod, requestHeaders);
12740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            if (response instanceof SecureCacheResponse) {
12750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                return new CacheResponse() {
12760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    @Override public InputStream getBody() throws IOException {
12770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                        return response.getBody();
12780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    }
12790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    @Override public Map<String, List<String>> getHeaders() throws IOException {
12800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                        return response.getHeaders();
12810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                    }
12820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                };
12830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            }
12840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            return response;
12850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
12860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
12870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson}
1288