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.ExtendedResponseCache;
     26 import java.net.HttpURLConnection;
     27 import java.net.ResponseCache;
     28 import java.net.ResponseSource;
     29 import java.net.URI;
     30 import java.net.URLConnection;
     31 import java.util.List;
     32 import java.util.Map;
     33 import javax.net.ssl.HttpsURLConnection;
     34 import libcore.io.DiskLruCache;
     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
     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&#8212;even if available&#8212;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.
     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>
    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>
    153  */
    154 public final class HttpResponseCache extends ResponseCache
    155         implements Closeable, ExtendedResponseCache {
    156 
    157     private final libcore.net.http.HttpResponseCache delegate;
    158 
    159     private HttpResponseCache(File directory, long maxSize) throws IOException {
    160         this.delegate = new libcore.net.http.HttpResponseCache(directory, maxSize);
    161     }
    162 
    163     /**
    164      * Returns the currently-installed {@code HttpResponseCache}, or null if
    165      * there is no cache installed or it is not a {@code HttpResponseCache}.
    166      */
    167     public static HttpResponseCache getInstalled() {
    168         ResponseCache installed = ResponseCache.getDefault();
    169         return installed instanceof HttpResponseCache ? (HttpResponseCache) installed : 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         HttpResponseCache installed = getInstalled();
    185         if (installed != null) {
    186             // don't close and reopen if an equivalent cache is already installed
    187             DiskLruCache installedCache = installed.delegate.getCache();
    188             if (installedCache.getDirectory().equals(directory)
    189                     && installedCache.maxSize() == maxSize
    190                     && !installedCache.isClosed()) {
    191                 return installed;
    192             } else {
    193                 IoUtils.closeQuietly(installed);
    194             }
    195         }
    196 
    197         HttpResponseCache result = new HttpResponseCache(directory, maxSize);
    198         ResponseCache.setDefault(result);
    199         return result;
    200     }
    201 
    202     @Override public CacheResponse get(URI uri, String requestMethod,
    203             Map<String, List<String>> requestHeaders) throws IOException {
    204         return delegate.get(uri, requestMethod, requestHeaders);
    205     }
    206 
    207     @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
    208         return delegate.put(uri, urlConnection);
    209     }
    210 
    211     /**
    212      * Returns the number of bytes currently being used to store the values in
    213      * this cache. This may be greater than the {@link #maxSize} if a background
    214      * deletion is pending.
    215      */
    216     public long size() {
    217         return delegate.getCache().size();
    218     }
    219 
    220     /**
    221      * Returns the maximum number of bytes that this cache should use to store
    222      * its data.
    223      */
    224     public long maxSize() {
    225         return delegate.getCache().maxSize();
    226     }
    227 
    228     /**
    229      * Force buffered operations to the filesystem. This ensures that responses
    230      * written to the cache will be available the next time the cache is opened,
    231      * even if this process is killed.
    232      */
    233     public void flush() {
    234         try {
    235             delegate.getCache().flush();
    236         } catch (IOException ignored) {
    237         }
    238     }
    239 
    240     /**
    241      * Returns the number of HTTP requests that required the network to either
    242      * supply a response or validate a locally cached response.
    243      */
    244     public int getNetworkCount() {
    245         return delegate.getNetworkCount();
    246     }
    247 
    248     /**
    249      * Returns the number of HTTP requests whose response was provided by the
    250      * cache. This may include conditional {@code GET} requests that were
    251      * validated over the network.
    252      */
    253     public int getHitCount() {
    254         return delegate.getHitCount();
    255     }
    256 
    257     /**
    258      * Returns the total number of HTTP requests that were made. This includes
    259      * both client requests and requests that were made on the client's behalf
    260      * to handle a redirects and retries.
    261      */
    262     public int getRequestCount() {
    263         return delegate.getRequestCount();
    264     }
    265 
    266     /** @hide */
    267     @Override public void trackResponse(ResponseSource source) {
    268         delegate.trackResponse(source);
    269     }
    270 
    271     /** @hide */
    272     @Override public void trackConditionalCacheHit() {
    273         delegate.trackConditionalCacheHit();
    274     }
    275 
    276     /** @hide */
    277     @Override public void update(CacheResponse conditionalCacheHit, HttpURLConnection connection) {
    278         delegate.update(conditionalCacheHit, connection);
    279     }
    280 
    281     /**
    282      * Uninstalls the cache and releases any active resources. Stored contents
    283      * will remain on the filesystem.
    284      */
    285     @Override public void close() throws IOException {
    286         if (ResponseCache.getDefault() == this) {
    287             ResponseCache.setDefault(null);
    288         }
    289         delegate.getCache().close();
    290     }
    291 
    292     /**
    293      * Uninstalls the cache and deletes all of its stored contents.
    294      */
    295     public void delete() throws IOException {
    296         if (ResponseCache.getDefault() == this) {
    297             ResponseCache.setDefault(null);
    298         }
    299         delegate.getCache().delete();
    300     }
    301 }
    302