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