Home | History | Annotate | Download | only in okhttp
      1 package com.squareup.okhttp;
      2 
      3 import com.squareup.okhttp.internal.http.HeaderParser;
      4 import java.util.concurrent.TimeUnit;
      5 
      6 /**
      7  * A Cache-Control header with cache directives from a server or client. These
      8  * directives set policy on what responses can be stored, and which requests can
      9  * be satisfied by those stored responses.
     10  *
     11  * <p>See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9">RFC
     12  * 2616, 14.9</a>.
     13  */
     14 public final class CacheControl {
     15   /**
     16    * Cache control request directives that require network validation of
     17    * responses. Note that such requests may be assisted by the cache via
     18    * conditional GET requests.
     19    */
     20   public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();
     21 
     22   /**
     23    * Cache control request directives that uses the cache only, even if the
     24    * cached response is stale. If the response isn't available in the cache or
     25    * requires server validation, the call will fail with a {@code 504
     26    * Unsatisfiable Request}.
     27    */
     28   public static final CacheControl FORCE_CACHE = new Builder()
     29       .onlyIfCached()
     30       .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
     31       .build();
     32 
     33   private final boolean noCache;
     34   private final boolean noStore;
     35   private final int maxAgeSeconds;
     36   private final int sMaxAgeSeconds;
     37   private final boolean isPrivate;
     38   private final boolean isPublic;
     39   private final boolean mustRevalidate;
     40   private final int maxStaleSeconds;
     41   private final int minFreshSeconds;
     42   private final boolean onlyIfCached;
     43   private final boolean noTransform;
     44 
     45   String headerValue; // Lazily computed, if absent.
     46 
     47   private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds,
     48       boolean isPrivate, boolean isPublic, boolean mustRevalidate, int maxStaleSeconds,
     49       int minFreshSeconds, boolean onlyIfCached, boolean noTransform, String headerValue) {
     50     this.noCache = noCache;
     51     this.noStore = noStore;
     52     this.maxAgeSeconds = maxAgeSeconds;
     53     this.sMaxAgeSeconds = sMaxAgeSeconds;
     54     this.isPrivate = isPrivate;
     55     this.isPublic = isPublic;
     56     this.mustRevalidate = mustRevalidate;
     57     this.maxStaleSeconds = maxStaleSeconds;
     58     this.minFreshSeconds = minFreshSeconds;
     59     this.onlyIfCached = onlyIfCached;
     60     this.noTransform = noTransform;
     61     this.headerValue = headerValue;
     62   }
     63 
     64   private CacheControl(Builder builder) {
     65     this.noCache = builder.noCache;
     66     this.noStore = builder.noStore;
     67     this.maxAgeSeconds = builder.maxAgeSeconds;
     68     this.sMaxAgeSeconds = -1;
     69     this.isPrivate = false;
     70     this.isPublic = false;
     71     this.mustRevalidate = false;
     72     this.maxStaleSeconds = builder.maxStaleSeconds;
     73     this.minFreshSeconds = builder.minFreshSeconds;
     74     this.onlyIfCached = builder.onlyIfCached;
     75     this.noTransform = builder.noTransform;
     76   }
     77 
     78   /**
     79    * In a response, this field's name "no-cache" is misleading. It doesn't
     80    * prevent us from caching the response; it only means we have to validate the
     81    * response with the origin server before returning it. We can do this with a
     82    * conditional GET.
     83    *
     84    * <p>In a request, it means do not use a cache to satisfy the request.
     85    */
     86   public boolean noCache() {
     87     return noCache;
     88   }
     89 
     90   /** If true, this response should not be cached. */
     91   public boolean noStore() {
     92     return noStore;
     93   }
     94 
     95   /**
     96    * The duration past the response's served date that it can be served without
     97    * validation.
     98    */
     99   public int maxAgeSeconds() {
    100     return maxAgeSeconds;
    101   }
    102 
    103   /**
    104    * The "s-maxage" directive is the max age for shared caches. Not to be
    105    * confused with "max-age" for non-shared caches, As in Firefox and Chrome,
    106    * this directive is not honored by this cache.
    107    */
    108   public int sMaxAgeSeconds() {
    109     return sMaxAgeSeconds;
    110   }
    111 
    112   public boolean isPrivate() {
    113     return isPrivate;
    114   }
    115 
    116   public boolean isPublic() {
    117     return isPublic;
    118   }
    119 
    120   public boolean mustRevalidate() {
    121     return mustRevalidate;
    122   }
    123 
    124   public int maxStaleSeconds() {
    125     return maxStaleSeconds;
    126   }
    127 
    128   public int minFreshSeconds() {
    129     return minFreshSeconds;
    130   }
    131 
    132   /**
    133    * This field's name "only-if-cached" is misleading. It actually means "do
    134    * not use the network". It is set by a client who only wants to make a
    135    * request if it can be fully satisfied by the cache. Cached responses that
    136    * would require validation (ie. conditional gets) are not permitted if this
    137    * header is set.
    138    */
    139   public boolean onlyIfCached() {
    140     return onlyIfCached;
    141   }
    142 
    143   public boolean noTransform() {
    144     return noTransform;
    145   }
    146 
    147   /**
    148    * Returns the cache directives of {@code headers}. This honors both
    149    * Cache-Control and Pragma headers if they are present.
    150    */
    151   public static CacheControl parse(Headers headers) {
    152     boolean noCache = false;
    153     boolean noStore = false;
    154     int maxAgeSeconds = -1;
    155     int sMaxAgeSeconds = -1;
    156     boolean isPrivate = false;
    157     boolean isPublic = false;
    158     boolean mustRevalidate = false;
    159     int maxStaleSeconds = -1;
    160     int minFreshSeconds = -1;
    161     boolean onlyIfCached = false;
    162     boolean noTransform = false;
    163 
    164     boolean canUseHeaderValue = true;
    165     String headerValue = null;
    166 
    167     for (int i = 0, size = headers.size(); i < size; i++) {
    168       String name = headers.name(i);
    169       String value = headers.value(i);
    170 
    171       if (name.equalsIgnoreCase("Cache-Control")) {
    172         if (headerValue != null) {
    173           // Multiple cache-control headers means we can't use the raw value.
    174           canUseHeaderValue = false;
    175         } else {
    176           headerValue = value;
    177         }
    178       } else if (name.equalsIgnoreCase("Pragma")) {
    179         // Might specify additional cache-control params. We invalidate just in case.
    180         canUseHeaderValue = false;
    181       } else {
    182         continue;
    183       }
    184 
    185       int pos = 0;
    186       while (pos < value.length()) {
    187         int tokenStart = pos;
    188         pos = HeaderParser.skipUntil(value, pos, "=,;");
    189         String directive = value.substring(tokenStart, pos).trim();
    190         String parameter;
    191 
    192         if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') {
    193           pos++; // consume ',' or ';' (if necessary)
    194           parameter = null;
    195         } else {
    196           pos++; // consume '='
    197           pos = HeaderParser.skipWhitespace(value, pos);
    198 
    199           // quoted string
    200           if (pos < value.length() && value.charAt(pos) == '\"') {
    201             pos++; // consume '"' open quote
    202             int parameterStart = pos;
    203             pos = HeaderParser.skipUntil(value, pos, "\"");
    204             parameter = value.substring(parameterStart, pos);
    205             pos++; // consume '"' close quote (if necessary)
    206 
    207             // unquoted string
    208           } else {
    209             int parameterStart = pos;
    210             pos = HeaderParser.skipUntil(value, pos, ",;");
    211             parameter = value.substring(parameterStart, pos).trim();
    212           }
    213         }
    214 
    215         if ("no-cache".equalsIgnoreCase(directive)) {
    216           noCache = true;
    217         } else if ("no-store".equalsIgnoreCase(directive)) {
    218           noStore = true;
    219         } else if ("max-age".equalsIgnoreCase(directive)) {
    220           maxAgeSeconds = HeaderParser.parseSeconds(parameter, -1);
    221         } else if ("s-maxage".equalsIgnoreCase(directive)) {
    222           sMaxAgeSeconds = HeaderParser.parseSeconds(parameter, -1);
    223         } else if ("private".equalsIgnoreCase(directive)) {
    224           isPrivate = true;
    225         } else if ("public".equalsIgnoreCase(directive)) {
    226           isPublic = true;
    227         } else if ("must-revalidate".equalsIgnoreCase(directive)) {
    228           mustRevalidate = true;
    229         } else if ("max-stale".equalsIgnoreCase(directive)) {
    230           maxStaleSeconds = HeaderParser.parseSeconds(parameter, Integer.MAX_VALUE);
    231         } else if ("min-fresh".equalsIgnoreCase(directive)) {
    232           minFreshSeconds = HeaderParser.parseSeconds(parameter, -1);
    233         } else if ("only-if-cached".equalsIgnoreCase(directive)) {
    234           onlyIfCached = true;
    235         } else if ("no-transform".equalsIgnoreCase(directive)) {
    236           noTransform = true;
    237         }
    238       }
    239     }
    240 
    241     if (!canUseHeaderValue) {
    242       headerValue = null;
    243     }
    244     return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPrivate, isPublic,
    245         mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform, headerValue);
    246   }
    247 
    248   @Override public String toString() {
    249     String result = headerValue;
    250     return result != null ? result : (headerValue = headerValue());
    251   }
    252 
    253   private String headerValue() {
    254     StringBuilder result = new StringBuilder();
    255     if (noCache) result.append("no-cache, ");
    256     if (noStore) result.append("no-store, ");
    257     if (maxAgeSeconds != -1) result.append("max-age=").append(maxAgeSeconds).append(", ");
    258     if (sMaxAgeSeconds != -1) result.append("s-maxage=").append(sMaxAgeSeconds).append(", ");
    259     if (isPrivate) result.append("private, ");
    260     if (isPublic) result.append("public, ");
    261     if (mustRevalidate) result.append("must-revalidate, ");
    262     if (maxStaleSeconds != -1) result.append("max-stale=").append(maxStaleSeconds).append(", ");
    263     if (minFreshSeconds != -1) result.append("min-fresh=").append(minFreshSeconds).append(", ");
    264     if (onlyIfCached) result.append("only-if-cached, ");
    265     if (noTransform) result.append("no-transform, ");
    266     if (result.length() == 0) return "";
    267     result.delete(result.length() - 2, result.length());
    268     return result.toString();
    269   }
    270 
    271   /** Builds a {@code Cache-Control} request header. */
    272   public static final class Builder {
    273     boolean noCache;
    274     boolean noStore;
    275     int maxAgeSeconds = -1;
    276     int maxStaleSeconds = -1;
    277     int minFreshSeconds = -1;
    278     boolean onlyIfCached;
    279     boolean noTransform;
    280 
    281     /** Don't accept an unvalidated cached response. */
    282     public Builder noCache() {
    283       this.noCache = true;
    284       return this;
    285     }
    286 
    287     /** Don't store the server's response in any cache. */
    288     public Builder noStore() {
    289       this.noStore = true;
    290       return this;
    291     }
    292 
    293     /**
    294      * Sets the maximum age of a cached response. If the cache response's age
    295      * exceeds {@code maxAge}, it will not be used and a network request will
    296      * be made.
    297      *
    298      * @param maxAge a non-negative integer. This is stored and transmitted with
    299      *     {@link TimeUnit#SECONDS} precision; finer precision will be lost.
    300      */
    301     public Builder maxAge(int maxAge, TimeUnit timeUnit) {
    302       if (maxAge < 0) throw new IllegalArgumentException("maxAge < 0: " + maxAge);
    303       long maxAgeSecondsLong = timeUnit.toSeconds(maxAge);
    304       this.maxAgeSeconds = maxAgeSecondsLong > Integer.MAX_VALUE
    305           ? Integer.MAX_VALUE
    306           : (int) maxAgeSecondsLong;
    307       return this;
    308     }
    309 
    310     /**
    311      * Accept cached responses that have exceeded their freshness lifetime by
    312      * up to {@code maxStale}. If unspecified, stale cache responses will not be
    313      * used.
    314      *
    315      * @param maxStale a non-negative integer. This is stored and transmitted
    316      *     with {@link TimeUnit#SECONDS} precision; finer precision will be
    317      *     lost.
    318      */
    319     public Builder maxStale(int maxStale, TimeUnit timeUnit) {
    320       if (maxStale < 0) throw new IllegalArgumentException("maxStale < 0: " + maxStale);
    321       long maxStaleSecondsLong = timeUnit.toSeconds(maxStale);
    322       this.maxStaleSeconds = maxStaleSecondsLong > Integer.MAX_VALUE
    323           ? Integer.MAX_VALUE
    324           : (int) maxStaleSecondsLong;
    325       return this;
    326     }
    327 
    328     /**
    329      * Sets the minimum number of seconds that a response will continue to be
    330      * fresh for. If the response will be stale when {@code minFresh} have
    331      * elapsed, the cached response will not be used and a network request will
    332      * be made.
    333      *
    334      * @param minFresh a non-negative integer. This is stored and transmitted
    335      *     with {@link TimeUnit#SECONDS} precision; finer precision will be
    336      *     lost.
    337      */
    338     public Builder minFresh(int minFresh, TimeUnit timeUnit) {
    339       if (minFresh < 0) throw new IllegalArgumentException("minFresh < 0: " + minFresh);
    340       long minFreshSecondsLong = timeUnit.toSeconds(minFresh);
    341       this.minFreshSeconds = minFreshSecondsLong > Integer.MAX_VALUE
    342           ? Integer.MAX_VALUE
    343           : (int) minFreshSecondsLong;
    344       return this;
    345     }
    346 
    347     /**
    348      * Only accept the response if it is in the cache. If the response isn't
    349      * cached, a {@code 504 Unsatisfiable Request} response will be returned.
    350      */
    351     public Builder onlyIfCached() {
    352       this.onlyIfCached = true;
    353       return this;
    354     }
    355 
    356     /** Don't accept a transformed response. */
    357     public Builder noTransform() {
    358       this.noTransform = true;
    359       return this;
    360     }
    361 
    362     public CacheControl build() {
    363       return new CacheControl(this);
    364     }
    365   }
    366 }
    367