diff options
| -rw-r--r-- | api/13.xml | 167 | ||||
| -rw-r--r-- | api/current.txt | 15 | ||||
| -rw-r--r-- | core/java/android/net/http/HttpResponseCache.java | 268 | ||||
| -rw-r--r-- | core/java/android/webkit/DataLoader.java | 2 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/net/http/AbstractProxyTest.java | 8 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java | 103 |
6 files changed, 558 insertions, 5 deletions
diff --git a/api/13.xml b/api/13.xml index b8b11fe91fc8..ff279e10f0c6 100644 --- a/api/13.xml +++ b/api/13.xml @@ -117527,6 +117527,173 @@ > </field> </class> +<class name="HttpResponseCache" + extends="java.net.ResponseCache" + abstract="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<implements name="java.io.Closeable"> +</implements> +<method name="close" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +<method name="delete" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +<method name="flush" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="get" + return="java.net.CacheResponse" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="java.net.URI"> +</parameter> +<parameter name="requestMethod" type="java.lang.String"> +</parameter> +<parameter name="requestHeaders" type="java.util.Map<java.lang.String, java.util.List<java.lang.String>>"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +<method name="getHitCount" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getInstalled" + return="android.net.http.HttpResponseCache" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getNetworkCount" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getRequestCount" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="install" + return="android.net.http.HttpResponseCache" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="directory" type="java.io.File"> +</parameter> +<parameter name="maxSize" type="long"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +<method name="maxSize" + return="long" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="put" + return="java.net.CacheRequest" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="java.net.URI"> +</parameter> +<parameter name="urlConnection" type="java.net.URLConnection"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +<method name="size" + return="long" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +</class> <class name="SslCertificate" extends="java.lang.Object" abstract="false" diff --git a/api/current.txt b/api/current.txt index fdef20fc5ad4..dffb86849dbf 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11164,6 +11164,21 @@ package android.net.http { field public static long DEFAULT_SYNC_MIN_GZIP_BYTES; } + public final class HttpResponseCache extends java.net.ResponseCache implements java.io.Closeable { + method public void close() throws java.io.IOException; + method public void delete() throws java.io.IOException; + method public void flush(); + method public java.net.CacheResponse get(java.net.URI, java.lang.String, java.util.Map<java.lang.String, java.util.List<java.lang.String>>) throws java.io.IOException; + method public int getHitCount(); + method public static android.net.http.HttpResponseCache getInstalled(); + method public int getNetworkCount(); + method public int getRequestCount(); + method public static android.net.http.HttpResponseCache install(java.io.File, long) throws java.io.IOException; + method public long maxSize(); + method public java.net.CacheRequest put(java.net.URI, java.net.URLConnection) throws java.io.IOException; + method public long size(); + } + public class SslCertificate { ctor public deprecated SslCertificate(java.lang.String, java.lang.String, java.lang.String, java.lang.String); ctor public deprecated SslCertificate(java.lang.String, java.lang.String, java.util.Date, java.util.Date); diff --git a/core/java/android/net/http/HttpResponseCache.java b/core/java/android/net/http/HttpResponseCache.java new file mode 100644 index 000000000000..b5d64e4a6008 --- /dev/null +++ b/core/java/android/net/http/HttpResponseCache.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.http; + +import android.content.Context; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.net.CacheRequest; +import java.net.CacheResponse; +import java.net.HttpURLConnection; +import java.net.ResponseCache; +import java.net.URI; +import java.net.URLConnection; +import java.util.List; +import java.util.Map; +import javax.net.ssl.HttpsURLConnection; +import libcore.io.DiskLruCache; +import libcore.io.IoUtils; +import org.apache.http.impl.client.DefaultHttpClient; + +/** + * Caches HTTP and HTTPS responses to the filesystem so they may be reused, + * saving time and bandwidth. This class supports {@link HttpURLConnection} and + * {@link HttpsURLConnection}; there is no platform-provided cache for {@link + * DefaultHttpClient} or {@link AndroidHttpClient}. + * + * <h3>Installing an HTTP response cache</h3> + * Enable caching of all of your application's HTTP requests by installing the + * cache at application startup. For example, this code installs a 10 MiB cache + * in the {@link Context#getCacheDir() application-specific cache directory} of + * the filesystem}: <pre> {@code + * protected void onCreate(Bundle savedInstanceState) { + * ... + * + * try { + * File httpCacheDir = new File(context.getCacheDir(), "http"); + * long httpCacheSize = 10 * 1024 * 1024; // 10 MiB + * HttpResponseCache.install(httpCacheDir, httpCacheSize); + * } catch (IOException e) { + * Log.i(TAG, "HTTP response cache installation failed:" + e); + * } + * } + * + * protected void onStop() { + * ... + * + * HttpResponseCache cache = HttpResponseCache.getInstalled(); + * if (cache != null) { + * cache.flush(); + * } + * }}</pre> + * This cache will evict entries as necessary to keep its size from exceeding + * 10 MiB. The best cache size is application specific and depends on the size + * and frequency of the files being downloaded. Increasing the limit may improve + * the hit rate, but it may also just waste filesystem space! + * + * <p>For some applications it may be preferable to create the cache in the + * external storage directory. Although it often has more free space, external + * storage is optional and—even if available—can disappear during + * use. Retrieve the external cache directory using {@link Context#getExternalCacheDir()}. If this method + * returns null, your application should fall back to either not caching or + * caching on non-external storage. If the external storage is removed during + * use, the cache hit rate will drop to zero and ongoing cache reads will fail. + * + * <p>Flushing the cache forces its data to the filesystem. This ensures that + * all responses written to the cache will be readable the next time the + * activity starts. + * + * <h3>Cache Optimization</h3> + * To measure cache effectiveness, this class tracks three statistics: + * <ul> + * <li><strong>{@link #getRequestCount() Request Count:}</strong> the number + * of HTTP requests issued since this cache was created. + * <li><strong>{@link #getNetworkCount() Network Count:}</strong> the + * number of those requests that required network use. + * <li><strong>{@link #getHitCount() Hit Count:}</strong> the number of + * those requests whose responses were served by the cache. + * </ul> + * Sometimes a request will result in a conditional cache hit. If the cache + * contains a stale copy of the response, the client will issue a conditional + * {@code GET}. The server will then send either the updated response if it has + * changed, or a short 'not modified' response if the client's copy is still + * valid. Such responses increment both the network count and hit count. + * + * <p>The best way to improve the cache hit rate is by configuring the web + * server to return cacheable responses. Although this client honors all <a + * href="http://www.ietf.org/rfc/rfc2616.txt">HTTP/1.1 (RFC 2068)</a> cache + * headers, it doesn't cache partial responses. + * + * <h3>Force a Network Response</h3> + * In some situations, such as after a user clicks a 'refresh' button, it may be + * necessary to skip the cache, and fetch data directly from the server. To force + * a full refresh, add the {@code no-cache} directive: <pre> {@code + * connection.addRequestProperty("Cache-Control", "no-cache"); + * }</pre> + * If it is only necessary to force a cached response to be validated by the + * server, use the more efficient {@code max-age=0} instead: <pre> {@code + * connection.addRequestProperty("Cache-Control", "max-age=0"); + * }</pre> + * + * <h3>Force a Cache Response</h3> + * Sometimes you'll want to show resources if they are available immediately, + * but not otherwise. This can be used so your application can show + * <i>something</i> while waiting for the latest data to be downloaded. To + * restrict a request to locally-cached resources, add the {@code + * only-if-cached} directive: <pre> {@code + * try { + * connection.addRequestProperty("Cache-Control", "only-if-cached"); + * InputStream cached = connection.getInputStream(); + * // the resource was cached! show it + * } catch (FileNotFoundException e) { + * // the resource was not cached + * } + * }</pre> + * This technique works even better in situations where a stale response is + * better than no response. To permit stale cached responses, use the {@code + * max-stale} directive with the maximum staleness in seconds: <pre> {@code + * int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale + * connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale); + * }</pre> + */ +public final class HttpResponseCache extends ResponseCache implements Closeable { + + private final libcore.net.http.HttpResponseCache delegate; + + private HttpResponseCache(File directory, long maxSize) throws IOException { + this.delegate = new libcore.net.http.HttpResponseCache(directory, maxSize); + } + + /** + * Returns the currently-installed {@code HttpResponseCache}, or null if + * there is no cache installed or it is not a {@code HttpResponseCache}. + */ + public static HttpResponseCache getInstalled() { + ResponseCache installed = ResponseCache.getDefault(); + return installed instanceof HttpResponseCache ? (HttpResponseCache) installed : null; + } + + /** + * Creates a new HTTP response cache and {@link ResponseCache#setDefault + * sets it} as the system default cache. + * + * @param directory the directory to hold cache data. + * @param maxSize the maximum size of the cache in bytes. + * @return the newly-installed cache + * @throws IOException if {@code directory} cannot be used for this cache. + * Most applications should respond to this exception by logging a + * warning. + */ + public static HttpResponseCache install(File directory, long maxSize) throws IOException { + HttpResponseCache installed = getInstalled(); + if (installed != null) { + // don't close and reopen if an equivalent cache is already installed + DiskLruCache installedCache = installed.delegate.getCache(); + if (installedCache.getDirectory().equals(directory) + && installedCache.maxSize() == maxSize + && !installedCache.isClosed()) { + return installed; + } else { + IoUtils.closeQuietly(installed); + } + } + + HttpResponseCache result = new HttpResponseCache(directory, maxSize); + ResponseCache.setDefault(result); + return result; + } + + @Override public CacheResponse get(URI uri, String requestMethod, + Map<String, List<String>> requestHeaders) throws IOException { + return delegate.get(uri, requestMethod, requestHeaders); + } + + @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { + return delegate.put(uri, urlConnection); + } + + /** + * Returns the number of bytes currently being used to store the values in + * this cache. This may be greater than the {@link #maxSize} if a background + * deletion is pending. + */ + public long size() { + return delegate.getCache().size(); + } + + /** + * Returns the maximum number of bytes that this cache should use to store + * its data. + */ + public long maxSize() { + return delegate.getCache().maxSize(); + } + + /** + * Force buffered operations to the filesystem. This ensures that responses + * written to the cache will be available the next time the cache is opened, + * even if this process is killed. + */ + public void flush() { + try { + delegate.getCache().flush(); // TODO: fix flush() to not throw? + } catch (IOException ignored) { + } + } + + /** + * Returns the number of HTTP requests that required the network to either + * supply a response or validate a locally cached response. + */ + public int getNetworkCount() { + return delegate.getNetworkCount(); + } + + /** + * Returns the number of HTTP requests whose response was provided by the + * cache. This may include conditional {@code GET} requests that were + * validated over the network. + */ + public int getHitCount() { + return delegate.getHitCount(); + } + + /** + * Returns the total number of HTTP requests that were made. This includes + * both client requests and requests that were made on the client's behalf + * to handle a redirects and retries. + */ + public int getRequestCount() { + return delegate.getRequestCount(); + } + + /** + * Uninstalls the cache and releases any active resources. Stored contents + * will remain on the filesystem. + */ + @Override public void close() throws IOException { + if (ResponseCache.getDefault() == this) { + ResponseCache.setDefault(null); + } + delegate.getCache().close(); + } + + /** + * Uninstalls the cache and deletes all of its stored contents. + */ + public void delete() throws IOException { + if (ResponseCache.getDefault() == this) { + ResponseCache.setDefault(null); + } + delegate.getCache().delete(); + } +} diff --git a/core/java/android/webkit/DataLoader.java b/core/java/android/webkit/DataLoader.java index 235dc5be6289..e8d906934954 100644 --- a/core/java/android/webkit/DataLoader.java +++ b/core/java/android/webkit/DataLoader.java @@ -22,7 +22,7 @@ import com.android.internal.R; import java.io.ByteArrayInputStream; -import org.apache.harmony.luni.util.Base64; +import libcore.io.Base64; /** * This class is a concrete implementation of StreamLoader that uses the diff --git a/core/tests/coretests/src/android/net/http/AbstractProxyTest.java b/core/tests/coretests/src/android/net/http/AbstractProxyTest.java index 2fb833cfb83b..ee8941439d6e 100644 --- a/core/tests/coretests/src/android/net/http/AbstractProxyTest.java +++ b/core/tests/coretests/src/android/net/http/AbstractProxyTest.java @@ -36,6 +36,7 @@ import org.apache.http.conn.ssl.SSLSocketFactory; import tests.http.MockResponse; import tests.http.MockWebServer; import tests.http.RecordedRequest; +import tests.http.SocketPolicy; public abstract class AbstractProxyTest extends TestCase { @@ -148,10 +149,9 @@ public abstract class AbstractProxyTest extends TestCase { TestSSLContext testSSLContext = TestSSLContext.create(); server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); - MockResponse connectResponse = new MockResponse() - .setResponseCode(200); - connectResponse.getHeaders().clear(); - server.enqueue(connectResponse); + server.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) + .clearHeaders()); server.enqueue(new MockResponse() .setResponseCode(200) .setBody("this response comes via a secure proxy")); diff --git a/core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java b/core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java new file mode 100644 index 000000000000..4d65588532ec --- /dev/null +++ b/core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.http; + +import java.io.File; +import java.net.CacheRequest; +import java.net.CacheResponse; +import java.net.ResponseCache; +import java.net.URI; +import java.net.URLConnection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import junit.framework.TestCase; + +public final class HttpResponseCacheTest extends TestCase { + + private File cacheDir; + + @Override public void setUp() throws Exception { + super.setUp(); + String tmp = System.getProperty("java.io.tmpdir"); + cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID()); + } + + @Override protected void tearDown() throws Exception { + ResponseCache.setDefault(null); + super.tearDown(); + } + + public void testInstall() throws Exception { + HttpResponseCache installed = HttpResponseCache.install(cacheDir, 10 * 1024 * 1024); + assertNotNull(installed); + assertSame(installed, ResponseCache.getDefault()); + assertSame(installed, HttpResponseCache.getDefault()); + } + + public void testSecondEquivalentInstallDoesNothing() throws Exception { + HttpResponseCache first = HttpResponseCache.install(cacheDir, 10 * 1024 * 1024); + HttpResponseCache another = HttpResponseCache.install(cacheDir, 10 * 1024 * 1024); + assertSame(first, another); + } + + public void testInstallClosesPreviouslyInstalled() throws Exception { + HttpResponseCache first = HttpResponseCache.install(cacheDir, 10 * 1024 * 1024); + HttpResponseCache another = HttpResponseCache.install(cacheDir, 8 * 1024 * 1024); + assertNotSame(first, another); + try { + first.flush(); + fail(); + } catch (IllegalStateException expected) { + } + } + + public void testGetInstalledWithWrongTypeInstalled() { + ResponseCache.setDefault(new ResponseCache() { + @Override public CacheResponse get(URI uri, String requestMethod, + Map<String, List<String>> requestHeaders) { + return null; + } + @Override public CacheRequest put(URI uri, URLConnection connection) { + return null; + } + }); + assertNull(HttpResponseCache.getInstalled()); + } + + public void testCloseCloses() throws Exception { + HttpResponseCache cache = HttpResponseCache.install(cacheDir, 10 * 1024 * 1024); + cache.close(); + try { + cache.flush(); + fail(); + } catch (IllegalStateException expected) { + } + } + + public void testCloseUninstalls() throws Exception { + HttpResponseCache cache = HttpResponseCache.install(cacheDir, 10 * 1024 * 1024); + cache.close(); + assertNull(ResponseCache.getDefault()); + } + + public void testDeleteUninstalls() throws Exception { + HttpResponseCache cache = HttpResponseCache.install(cacheDir, 10 * 1024 * 1024); + cache.delete(); + assertNull(ResponseCache.getDefault()); + } +} |