Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2011 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package android.net.http; |
| 18 | |
| 19 | import android.content.Context; |
Narayan Kamath | c0bb166 | 2013-07-18 13:42:50 +0100 | [diff] [blame] | 20 | import com.android.okhttp.OkResponseCache; |
jwilson | dea64ad | 2013-03-30 09:06:48 -0400 | [diff] [blame] | 21 | import com.android.okhttp.ResponseSource; |
| 22 | import com.android.okhttp.internal.DiskLruCache; |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 23 | import java.io.Closeable; |
| 24 | import java.io.File; |
| 25 | import java.io.IOException; |
| 26 | import java.net.CacheRequest; |
| 27 | import java.net.CacheResponse; |
| 28 | import java.net.HttpURLConnection; |
| 29 | import java.net.ResponseCache; |
| 30 | import java.net.URI; |
| 31 | import java.net.URLConnection; |
| 32 | import java.util.List; |
| 33 | import java.util.Map; |
| 34 | import javax.net.ssl.HttpsURLConnection; |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 35 | import libcore.io.IoUtils; |
| 36 | import org.apache.http.impl.client.DefaultHttpClient; |
| 37 | |
| 38 | /** |
| 39 | * Caches HTTP and HTTPS responses to the filesystem so they may be reused, |
| 40 | * saving time and bandwidth. This class supports {@link HttpURLConnection} and |
| 41 | * {@link HttpsURLConnection}; there is no platform-provided cache for {@link |
| 42 | * DefaultHttpClient} or {@link AndroidHttpClient}. |
| 43 | * |
| 44 | * <h3>Installing an HTTP response cache</h3> |
| 45 | * Enable caching of all of your application's HTTP requests by installing the |
| 46 | * cache at application startup. For example, this code installs a 10 MiB cache |
| 47 | * in the {@link Context#getCacheDir() application-specific cache directory} of |
| 48 | * the filesystem}: <pre> {@code |
| 49 | * protected void onCreate(Bundle savedInstanceState) { |
| 50 | * ... |
| 51 | * |
| 52 | * try { |
| 53 | * File httpCacheDir = new File(context.getCacheDir(), "http"); |
| 54 | * long httpCacheSize = 10 * 1024 * 1024; // 10 MiB |
| 55 | * HttpResponseCache.install(httpCacheDir, httpCacheSize); |
| 56 | * } catch (IOException e) { |
| 57 | * Log.i(TAG, "HTTP response cache installation failed:" + e); |
| 58 | * } |
| 59 | * } |
| 60 | * |
| 61 | * protected void onStop() { |
| 62 | * ... |
| 63 | * |
| 64 | * HttpResponseCache cache = HttpResponseCache.getInstalled(); |
| 65 | * if (cache != null) { |
| 66 | * cache.flush(); |
| 67 | * } |
| 68 | * }}</pre> |
| 69 | * This cache will evict entries as necessary to keep its size from exceeding |
| 70 | * 10 MiB. The best cache size is application specific and depends on the size |
| 71 | * and frequency of the files being downloaded. Increasing the limit may improve |
| 72 | * the hit rate, but it may also just waste filesystem space! |
| 73 | * |
| 74 | * <p>For some applications it may be preferable to create the cache in the |
Jesse Wilson | 98e8b19 | 2011-06-23 15:59:32 -0700 | [diff] [blame] | 75 | * external storage directory. <strong>There are no access controls on the |
| 76 | * external storage directory so it should not be used for caches that could |
| 77 | * contain private data.</strong> Although it often has more free space, |
| 78 | * external storage is optional and—even if available—can disappear |
| 79 | * during use. Retrieve the external cache directory using {@link |
| 80 | * Context#getExternalCacheDir()}. If this method returns null, your application |
| 81 | * should fall back to either not caching or caching on non-external storage. If |
| 82 | * the external storage is removed during use, the cache hit rate will drop to |
| 83 | * zero and ongoing cache reads will fail. |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 84 | * |
| 85 | * <p>Flushing the cache forces its data to the filesystem. This ensures that |
| 86 | * all responses written to the cache will be readable the next time the |
| 87 | * activity starts. |
| 88 | * |
| 89 | * <h3>Cache Optimization</h3> |
| 90 | * To measure cache effectiveness, this class tracks three statistics: |
| 91 | * <ul> |
| 92 | * <li><strong>{@link #getRequestCount() Request Count:}</strong> the number |
| 93 | * of HTTP requests issued since this cache was created. |
| 94 | * <li><strong>{@link #getNetworkCount() Network Count:}</strong> the |
| 95 | * number of those requests that required network use. |
| 96 | * <li><strong>{@link #getHitCount() Hit Count:}</strong> the number of |
| 97 | * those requests whose responses were served by the cache. |
| 98 | * </ul> |
| 99 | * Sometimes a request will result in a conditional cache hit. If the cache |
| 100 | * contains a stale copy of the response, the client will issue a conditional |
| 101 | * {@code GET}. The server will then send either the updated response if it has |
| 102 | * changed, or a short 'not modified' response if the client's copy is still |
| 103 | * valid. Such responses increment both the network count and hit count. |
| 104 | * |
| 105 | * <p>The best way to improve the cache hit rate is by configuring the web |
| 106 | * server to return cacheable responses. Although this client honors all <a |
| 107 | * href="http://www.ietf.org/rfc/rfc2616.txt">HTTP/1.1 (RFC 2068)</a> cache |
| 108 | * headers, it doesn't cache partial responses. |
| 109 | * |
| 110 | * <h3>Force a Network Response</h3> |
| 111 | * In some situations, such as after a user clicks a 'refresh' button, it may be |
| 112 | * necessary to skip the cache, and fetch data directly from the server. To force |
| 113 | * a full refresh, add the {@code no-cache} directive: <pre> {@code |
| 114 | * connection.addRequestProperty("Cache-Control", "no-cache"); |
| 115 | * }</pre> |
| 116 | * If it is only necessary to force a cached response to be validated by the |
| 117 | * server, use the more efficient {@code max-age=0} instead: <pre> {@code |
| 118 | * connection.addRequestProperty("Cache-Control", "max-age=0"); |
| 119 | * }</pre> |
| 120 | * |
| 121 | * <h3>Force a Cache Response</h3> |
| 122 | * Sometimes you'll want to show resources if they are available immediately, |
| 123 | * but not otherwise. This can be used so your application can show |
| 124 | * <i>something</i> while waiting for the latest data to be downloaded. To |
| 125 | * restrict a request to locally-cached resources, add the {@code |
| 126 | * only-if-cached} directive: <pre> {@code |
| 127 | * try { |
| 128 | * connection.addRequestProperty("Cache-Control", "only-if-cached"); |
| 129 | * InputStream cached = connection.getInputStream(); |
| 130 | * // the resource was cached! show it |
| 131 | * } catch (FileNotFoundException e) { |
| 132 | * // the resource was not cached |
| 133 | * } |
| 134 | * }</pre> |
| 135 | * This technique works even better in situations where a stale response is |
| 136 | * better than no response. To permit stale cached responses, use the {@code |
| 137 | * max-stale} directive with the maximum staleness in seconds: <pre> {@code |
| 138 | * int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale |
| 139 | * connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale); |
| 140 | * }</pre> |
Jesse Wilson | 8543b54 | 2011-12-15 12:25:55 -0500 | [diff] [blame] | 141 | * |
| 142 | * <h3>Working With Earlier Releases</h3> |
| 143 | * This class was added in Android 4.0 (Ice Cream Sandwich). Use reflection to |
| 144 | * enable the response cache without impacting earlier releases: <pre> {@code |
| 145 | * try { |
| 146 | * File httpCacheDir = new File(context.getCacheDir(), "http"); |
| 147 | * long httpCacheSize = 10 * 1024 * 1024; // 10 MiB |
| 148 | * Class.forName("android.net.http.HttpResponseCache") |
| 149 | * .getMethod("install", File.class, long.class) |
| 150 | * .invoke(null, httpCacheDir, httpCacheSize); |
| 151 | * } catch (Exception httpResponseCacheNotAvailable) { |
| 152 | * }}</pre> |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 153 | */ |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 154 | public final class HttpResponseCache extends ResponseCache implements Closeable { |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 155 | |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 156 | private final com.android.okhttp.HttpResponseCache delegate; |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 157 | |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 158 | private HttpResponseCache(com.android.okhttp.HttpResponseCache delegate) { |
| 159 | this.delegate = delegate; |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 160 | } |
| 161 | |
| 162 | /** |
| 163 | * Returns the currently-installed {@code HttpResponseCache}, or null if |
| 164 | * there is no cache installed or it is not a {@code HttpResponseCache}. |
| 165 | */ |
| 166 | public static HttpResponseCache getInstalled() { |
| 167 | ResponseCache installed = ResponseCache.getDefault(); |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 168 | if (installed instanceof com.android.okhttp.HttpResponseCache) { |
| 169 | return new HttpResponseCache( |
| 170 | (com.android.okhttp.HttpResponseCache) installed); |
| 171 | } |
| 172 | |
| 173 | return null; |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 174 | } |
| 175 | |
| 176 | /** |
| 177 | * Creates a new HTTP response cache and {@link ResponseCache#setDefault |
| 178 | * sets it} as the system default cache. |
| 179 | * |
| 180 | * @param directory the directory to hold cache data. |
| 181 | * @param maxSize the maximum size of the cache in bytes. |
| 182 | * @return the newly-installed cache |
| 183 | * @throws IOException if {@code directory} cannot be used for this cache. |
| 184 | * Most applications should respond to this exception by logging a |
| 185 | * warning. |
| 186 | */ |
| 187 | public static HttpResponseCache install(File directory, long maxSize) throws IOException { |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 188 | ResponseCache installed = ResponseCache.getDefault(); |
| 189 | if (installed instanceof com.android.okhttp.HttpResponseCache) { |
| 190 | com.android.okhttp.HttpResponseCache installedCache = |
| 191 | (com.android.okhttp.HttpResponseCache) installed; |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 192 | // don't close and reopen if an equivalent cache is already installed |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 193 | if (installedCache.getDirectory().equals(directory) |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 194 | && installedCache.getMaxSize() == maxSize |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 195 | && !installedCache.isClosed()) { |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 196 | return new HttpResponseCache(installedCache); |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 197 | } else { |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 198 | // The HttpResponseCache that owns this object is about to be replaced. |
| 199 | installedCache.close(); |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 200 | } |
| 201 | } |
| 202 | |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 203 | com.android.okhttp.HttpResponseCache responseCache = |
| 204 | new com.android.okhttp.HttpResponseCache(directory, maxSize); |
| 205 | ResponseCache.setDefault(responseCache); |
| 206 | return new HttpResponseCache(responseCache); |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 207 | } |
| 208 | |
| 209 | @Override public CacheResponse get(URI uri, String requestMethod, |
| 210 | Map<String, List<String>> requestHeaders) throws IOException { |
| 211 | return delegate.get(uri, requestMethod, requestHeaders); |
| 212 | } |
| 213 | |
| 214 | @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { |
| 215 | return delegate.put(uri, urlConnection); |
| 216 | } |
| 217 | |
| 218 | /** |
| 219 | * Returns the number of bytes currently being used to store the values in |
| 220 | * this cache. This may be greater than the {@link #maxSize} if a background |
| 221 | * deletion is pending. |
| 222 | */ |
| 223 | public long size() { |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 224 | return delegate.getSize(); |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 225 | } |
| 226 | |
| 227 | /** |
| 228 | * Returns the maximum number of bytes that this cache should use to store |
| 229 | * its data. |
| 230 | */ |
| 231 | public long maxSize() { |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 232 | return delegate.getMaxSize(); |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 233 | } |
| 234 | |
| 235 | /** |
| 236 | * Force buffered operations to the filesystem. This ensures that responses |
| 237 | * written to the cache will be available the next time the cache is opened, |
| 238 | * even if this process is killed. |
| 239 | */ |
| 240 | public void flush() { |
| 241 | try { |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 242 | delegate.flush(); |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 243 | } catch (IOException ignored) { |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | /** |
| 248 | * Returns the number of HTTP requests that required the network to either |
| 249 | * supply a response or validate a locally cached response. |
| 250 | */ |
| 251 | public int getNetworkCount() { |
| 252 | return delegate.getNetworkCount(); |
| 253 | } |
| 254 | |
| 255 | /** |
| 256 | * Returns the number of HTTP requests whose response was provided by the |
| 257 | * cache. This may include conditional {@code GET} requests that were |
| 258 | * validated over the network. |
| 259 | */ |
| 260 | public int getHitCount() { |
| 261 | return delegate.getHitCount(); |
| 262 | } |
| 263 | |
| 264 | /** |
| 265 | * Returns the total number of HTTP requests that were made. This includes |
| 266 | * both client requests and requests that were made on the client's behalf |
| 267 | * to handle a redirects and retries. |
| 268 | */ |
| 269 | public int getRequestCount() { |
| 270 | return delegate.getRequestCount(); |
| 271 | } |
| 272 | |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 273 | /** |
| 274 | * Uninstalls the cache and releases any active resources. Stored contents |
| 275 | * will remain on the filesystem. |
| 276 | */ |
| 277 | @Override public void close() throws IOException { |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 278 | if (ResponseCache.getDefault() == this.delegate) { |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 279 | ResponseCache.setDefault(null); |
| 280 | } |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 281 | delegate.close(); |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 282 | } |
| 283 | |
| 284 | /** |
| 285 | * Uninstalls the cache and deletes all of its stored contents. |
| 286 | */ |
| 287 | public void delete() throws IOException { |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 288 | if (ResponseCache.getDefault() == this.delegate) { |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 289 | ResponseCache.setDefault(null); |
| 290 | } |
Narayan Kamath | 68a3cd7 | 2013-06-06 12:16:40 +0100 | [diff] [blame] | 291 | delegate.delete(); |
Brian Carlstrom | a7284f0 | 2011-05-27 00:07:42 -0700 | [diff] [blame] | 292 | } |
| 293 | } |