package okhttp3;

import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import okhttp3.internal.http.HttpHeaders;

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.

See RFC 7234, 5.2.

/** * 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="https://tools.ietf.org/html/rfc7234#section-5.2">RFC 7234, 5.2</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.
/** * 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 504 Unsatisfiable Request.
/** * 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; private final boolean immutable; @Nullable String headerValue; // Lazily computed, null 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, boolean immutable, @Nullable 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.immutable = immutable; this.headerValue = headerValue; } 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; this.immutable = builder.immutable; }
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.

In a request, it means do not use a cache to satisfy the request.

/** * 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.
/** 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.
/** * 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.
/** * 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.
/** * 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; } public boolean immutable() { return immutable; }
Returns the cache directives of headers. This honors both Cache-Control and Pragma headers if they are present.
/** * 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 immutable = 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 = HttpHeaders.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 = HttpHeaders.skipWhitespace(value, pos); // quoted string if (pos < value.length() && value.charAt(pos) == '\"') { pos++; // consume '"' open quote int parameterStart = pos; pos = HttpHeaders.skipUntil(value, pos, "\""); parameter = value.substring(parameterStart, pos); pos++; // consume '"' close quote (if necessary) // unquoted string } else { int parameterStart = pos; pos = HttpHeaders.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 = HttpHeaders.parseSeconds(parameter, -1); } else if ("s-maxage".equalsIgnoreCase(directive)) { sMaxAgeSeconds = HttpHeaders.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 = HttpHeaders.parseSeconds(parameter, Integer.MAX_VALUE); } else if ("min-fresh".equalsIgnoreCase(directive)) { minFreshSeconds = HttpHeaders.parseSeconds(parameter, -1); } else if ("only-if-cached".equalsIgnoreCase(directive)) { onlyIfCached = true; } else if ("no-transform".equalsIgnoreCase(directive)) { noTransform = true; } else if ("immutable".equalsIgnoreCase(directive)) { immutable = true; } } } if (!canUseHeaderValue) { headerValue = null; } return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPrivate, isPublic, mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform, immutable, 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 (immutable) result.append("immutable, "); if (result.length() == 0) return ""; result.delete(result.length() - 2, result.length()); return result.toString(); }
Builds a Cache-Control request header.
/** 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; boolean immutable;
Don't accept an unvalidated cached response.
/** 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.
/** 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 maxAge, it will not be used and a network request will be made.
Params:
  • maxAge – a non-negative integer. This is stored and transmitted with TimeUnit.SECONDS precision; finer precision will be lost.
/** * 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 maxStale. If unspecified, stale cache responses will not be used.
Params:
  • maxStale – a non-negative integer. This is stored and transmitted with TimeUnit.SECONDS precision; finer precision will be lost.
/** * 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 minFresh have elapsed, the cached response will not be used and a network request will be made.
Params:
  • minFresh – a non-negative integer. This is stored and transmitted with TimeUnit.SECONDS precision; finer precision will be lost.
/** * 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 504 Unsatisfiable Request response will be returned.
/** * 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.
/** Don't accept a transformed response. */
public Builder noTransform() { this.noTransform = true; return this; } public Builder immutable() { this.immutable = true; return this; } public CacheControl build() { return new CacheControl(this); } } }