Java tutorial
package com.squareup.okhttp; import com.squareup.okhttp.internal.http.HeaderParser; import java.util.concurrent.TimeUnit; /** * A Cache-Control header with cache directives from a server or client. These * directives set policy on what responses can be stored, and which requests can * be satisfied by those stored responses. * * <p>See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9">RFC * 2616, 14.9</a>. */ public final class CacheControl { /** * Cache control request directives that require network validation of * responses. Note that such requests may be assisted by the cache via * conditional GET requests. */ public static final CacheControl FORCE_NETWORK = new Builder().noCache().build(); /** * Cache control request directives that uses the cache only, even if the * cached response is stale. If the response isn't available in the cache or * requires server validation, the call will fail with a {@code 504 * Unsatisfiable Request}. */ public static final CacheControl FORCE_CACHE = new Builder().onlyIfCached() .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS).build(); private final boolean noCache; private final boolean noStore; private final int maxAgeSeconds; private final int sMaxAgeSeconds; private final boolean isPrivate; private final boolean isPublic; private final boolean mustRevalidate; private final int maxStaleSeconds; private final int minFreshSeconds; private final boolean onlyIfCached; private final boolean noTransform; String headerValue; // Lazily computed, if absent. private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds, boolean isPrivate, boolean isPublic, boolean mustRevalidate, int maxStaleSeconds, int minFreshSeconds, boolean onlyIfCached, boolean noTransform, String headerValue) { this.noCache = noCache; this.noStore = noStore; this.maxAgeSeconds = maxAgeSeconds; this.sMaxAgeSeconds = sMaxAgeSeconds; this.isPrivate = isPrivate; this.isPublic = isPublic; this.mustRevalidate = mustRevalidate; this.maxStaleSeconds = maxStaleSeconds; this.minFreshSeconds = minFreshSeconds; this.onlyIfCached = onlyIfCached; this.noTransform = noTransform; this.headerValue = headerValue; } private CacheControl(Builder builder) { this.noCache = builder.noCache; this.noStore = builder.noStore; this.maxAgeSeconds = builder.maxAgeSeconds; this.sMaxAgeSeconds = -1; this.isPrivate = false; this.isPublic = false; this.mustRevalidate = false; this.maxStaleSeconds = builder.maxStaleSeconds; this.minFreshSeconds = builder.minFreshSeconds; this.onlyIfCached = builder.onlyIfCached; this.noTransform = builder.noTransform; } /** * In a response, this field's name "no-cache" is misleading. It doesn't * prevent us from caching the response; it only means we have to validate the * response with the origin server before returning it. We can do this with a * conditional GET. * * <p>In a request, it means do not use a cache to satisfy the request. */ public boolean noCache() { return noCache; } /** If true, this response should not be cached. */ public boolean noStore() { return noStore; } /** * The duration past the response's served date that it can be served without * validation. */ public int maxAgeSeconds() { return maxAgeSeconds; } /** * The "s-maxage" directive is the max age for shared caches. Not to be * confused with "max-age" for non-shared caches, As in Firefox and Chrome, * this directive is not honored by this cache. */ public int sMaxAgeSeconds() { return sMaxAgeSeconds; } public boolean isPrivate() { return isPrivate; } public boolean isPublic() { return isPublic; } public boolean mustRevalidate() { return mustRevalidate; } public int maxStaleSeconds() { return maxStaleSeconds; } public int minFreshSeconds() { return minFreshSeconds; } /** * This field's name "only-if-cached" is misleading. It actually means "do * not use the network". It is set by a client who only wants to make a * request if it can be fully satisfied by the cache. Cached responses that * would require validation (ie. conditional gets) are not permitted if this * header is set. */ public boolean onlyIfCached() { return onlyIfCached; } public boolean noTransform() { return noTransform; } /** * Returns the cache directives of {@code headers}. This honors both * Cache-Control and Pragma headers if they are present. */ public static CacheControl parse(Headers headers) { boolean noCache = false; boolean noStore = false; int maxAgeSeconds = -1; int sMaxAgeSeconds = -1; boolean isPrivate = false; boolean isPublic = false; boolean mustRevalidate = false; int maxStaleSeconds = -1; int minFreshSeconds = -1; boolean onlyIfCached = false; boolean noTransform = false; boolean canUseHeaderValue = true; String headerValue = null; for (int i = 0, size = headers.size(); i < size; i++) { String name = headers.name(i); String value = headers.value(i); if (name.equalsIgnoreCase("Cache-Control")) { if (headerValue != null) { // Multiple cache-control headers means we can't use the raw value. canUseHeaderValue = false; } else { headerValue = value; } } else if (name.equalsIgnoreCase("Pragma")) { // Might specify additional cache-control params. We invalidate just in case. canUseHeaderValue = false; } else { continue; } int pos = 0; while (pos < value.length()) { int tokenStart = pos; pos = HeaderParser.skipUntil(value, pos, "=,;"); String directive = value.substring(tokenStart, pos).trim(); String parameter; if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') { pos++; // consume ',' or ';' (if necessary) parameter = null; } else { pos++; // consume '=' pos = HeaderParser.skipWhitespace(value, pos); // quoted string if (pos < value.length() && value.charAt(pos) == '\"') { pos++; // consume '"' open quote int parameterStart = pos; pos = HeaderParser.skipUntil(value, pos, "\""); parameter = value.substring(parameterStart, pos); pos++; // consume '"' close quote (if necessary) // unquoted string } else { int parameterStart = pos; pos = HeaderParser.skipUntil(value, pos, ",;"); parameter = value.substring(parameterStart, pos).trim(); } } if ("no-cache".equalsIgnoreCase(directive)) { noCache = true; } else if ("no-store".equalsIgnoreCase(directive)) { noStore = true; } else if ("max-age".equalsIgnoreCase(directive)) { maxAgeSeconds = HeaderParser.parseSeconds(parameter, -1); } else if ("s-maxage".equalsIgnoreCase(directive)) { sMaxAgeSeconds = HeaderParser.parseSeconds(parameter, -1); } else if ("private".equalsIgnoreCase(directive)) { isPrivate = true; } else if ("public".equalsIgnoreCase(directive)) { isPublic = true; } else if ("must-revalidate".equalsIgnoreCase(directive)) { mustRevalidate = true; } else if ("max-stale".equalsIgnoreCase(directive)) { maxStaleSeconds = HeaderParser.parseSeconds(parameter, Integer.MAX_VALUE); } else if ("min-fresh".equalsIgnoreCase(directive)) { minFreshSeconds = HeaderParser.parseSeconds(parameter, -1); } else if ("only-if-cached".equalsIgnoreCase(directive)) { onlyIfCached = true; } else if ("no-transform".equalsIgnoreCase(directive)) { noTransform = true; } } } if (!canUseHeaderValue) { headerValue = null; } return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPrivate, isPublic, mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform, headerValue); } @Override public String toString() { String result = headerValue; return result != null ? result : (headerValue = headerValue()); } private String headerValue() { StringBuilder result = new StringBuilder(); if (noCache) result.append("no-cache, "); if (noStore) result.append("no-store, "); if (maxAgeSeconds != -1) result.append("max-age=").append(maxAgeSeconds).append(", "); if (sMaxAgeSeconds != -1) result.append("s-maxage=").append(sMaxAgeSeconds).append(", "); if (isPrivate) result.append("private, "); if (isPublic) result.append("public, "); if (mustRevalidate) result.append("must-revalidate, "); if (maxStaleSeconds != -1) result.append("max-stale=").append(maxStaleSeconds).append(", "); if (minFreshSeconds != -1) result.append("min-fresh=").append(minFreshSeconds).append(", "); if (onlyIfCached) result.append("only-if-cached, "); if (noTransform) result.append("no-transform, "); if (result.length() == 0) return ""; result.delete(result.length() - 2, result.length()); return result.toString(); } /** Builds a {@code Cache-Control} request header. */ public static final class Builder { boolean noCache; boolean noStore; int maxAgeSeconds = -1; int maxStaleSeconds = -1; int minFreshSeconds = -1; boolean onlyIfCached; boolean noTransform; /** Don't accept an unvalidated cached response. */ public Builder noCache() { this.noCache = true; return this; } /** Don't store the server's response in any cache. */ public Builder noStore() { this.noStore = true; return this; } /** * Sets the maximum age of a cached response. If the cache response's age * exceeds {@code maxAge}, it will not be used and a network request will * be made. * * @param maxAge a non-negative integer. This is stored and transmitted with * {@link TimeUnit#SECONDS} precision; finer precision will be lost. */ public Builder maxAge(int maxAge, TimeUnit timeUnit) { if (maxAge < 0) throw new IllegalArgumentException("maxAge < 0: " + maxAge); long maxAgeSecondsLong = timeUnit.toSeconds(maxAge); this.maxAgeSeconds = maxAgeSecondsLong > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) maxAgeSecondsLong; return this; } /** * Accept cached responses that have exceeded their freshness lifetime by * up to {@code maxStale}. If unspecified, stale cache responses will not be * used. * * @param maxStale a non-negative integer. This is stored and transmitted * with {@link TimeUnit#SECONDS} precision; finer precision will be * lost. */ public Builder maxStale(int maxStale, TimeUnit timeUnit) { if (maxStale < 0) throw new IllegalArgumentException("maxStale < 0: " + maxStale); long maxStaleSecondsLong = timeUnit.toSeconds(maxStale); this.maxStaleSeconds = maxStaleSecondsLong > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) maxStaleSecondsLong; return this; } /** * Sets the minimum number of seconds that a response will continue to be * fresh for. If the response will be stale when {@code minFresh} have * elapsed, the cached response will not be used and a network request will * be made. * * @param minFresh a non-negative integer. This is stored and transmitted * with {@link TimeUnit#SECONDS} precision; finer precision will be * lost. */ public Builder minFresh(int minFresh, TimeUnit timeUnit) { if (minFresh < 0) throw new IllegalArgumentException("minFresh < 0: " + minFresh); long minFreshSecondsLong = timeUnit.toSeconds(minFresh); this.minFreshSeconds = minFreshSecondsLong > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) minFreshSecondsLong; return this; } /** * Only accept the response if it is in the cache. If the response isn't * cached, a {@code 504 Unsatisfiable Request} response will be returned. */ public Builder onlyIfCached() { this.onlyIfCached = true; return this; } /** Don't accept a transformed response. */ public Builder noTransform() { this.noTransform = true; return this; } public CacheControl build() { return new CacheControl(this); } } }