diff --git a/okhttp/src/main/java/com/squareup/okhttp/CacheControl.java b/okhttp/src/main/java/com/squareup/okhttp/CacheControl.java new file mode 100644 index 000000000..dc944e4f6 --- /dev/null +++ b/okhttp/src/main/java/com/squareup/okhttp/CacheControl.java @@ -0,0 +1,176 @@ +package com.squareup.okhttp; + +import com.squareup.okhttp.internal.http.HeaderParser; + +/** + * 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 + * 2616, 14.9. + */ +public final class CacheControl { + private final boolean noCache; + private final boolean noStore; + private final int maxAgeSeconds; + private final int sMaxAgeSeconds; + private final boolean isPublic; + private final boolean mustRevalidate; + private final int maxStaleSeconds; + private final int minFreshSeconds; + private final boolean onlyIfCached; + + private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds, + boolean isPublic, boolean mustRevalidate, int maxStaleSeconds, int minFreshSeconds, + boolean onlyIfCached) { + this.noCache = noCache; + this.noStore = noStore; + this.maxAgeSeconds = maxAgeSeconds; + this.sMaxAgeSeconds = sMaxAgeSeconds; + this.isPublic = isPublic; + this.mustRevalidate = mustRevalidate; + this.maxStaleSeconds = maxStaleSeconds; + this.minFreshSeconds = minFreshSeconds; + this.onlyIfCached = onlyIfCached; + } + + /** + * 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.
+ */
+ 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 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;
+ }
+
+ /**
+ * 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 isPublic = false;
+ boolean mustRevalidate = false;
+ int maxStaleSeconds = -1;
+ int minFreshSeconds = -1;
+ boolean onlyIfCached = false;
+
+ for (int i = 0; i < headers.size(); i++) {
+ if (!headers.name(i).equalsIgnoreCase("Cache-Control")
+ && !headers.name(i).equalsIgnoreCase("Pragma")) {
+ continue;
+ }
+
+ String string = headers.value(i);
+ int pos = 0;
+ while (pos < string.length()) {
+ int tokenStart = pos;
+ pos = HeaderParser.skipUntil(string, pos, "=,;");
+ String directive = string.substring(tokenStart, pos).trim();
+ String parameter;
+
+ if (pos == string.length() || string.charAt(pos) == ',' || string.charAt(pos) == ';') {
+ pos++; // consume ',' or ';' (if necessary)
+ parameter = null;
+ } else {
+ pos++; // consume '='
+ pos = HeaderParser.skipWhitespace(string, pos);
+
+ // quoted string
+ if (pos < string.length() && string.charAt(pos) == '\"') {
+ pos++; // consume '"' open quote
+ int parameterStart = pos;
+ pos = HeaderParser.skipUntil(string, pos, "\"");
+ parameter = string.substring(parameterStart, pos);
+ pos++; // consume '"' close quote (if necessary)
+
+ // unquoted string
+ } else {
+ int parameterStart = pos;
+ pos = HeaderParser.skipUntil(string, pos, ",;");
+ parameter = string.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);
+ } else if ("s-maxage".equalsIgnoreCase(directive)) {
+ sMaxAgeSeconds = HeaderParser.parseSeconds(parameter);
+ } 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);
+ } else if ("min-fresh".equalsIgnoreCase(directive)) {
+ minFreshSeconds = HeaderParser.parseSeconds(parameter);
+ } else if ("only-if-cached".equalsIgnoreCase(directive)) {
+ onlyIfCached = true;
+ }
+ }
+ }
+
+ return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPublic,
+ mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached);
+ }
+}
diff --git a/okhttp/src/main/java/com/squareup/okhttp/Request.java b/okhttp/src/main/java/com/squareup/okhttp/Request.java
index 83c6fbb2d..af90db152 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/Request.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/Request.java
@@ -17,7 +17,6 @@ package com.squareup.okhttp;
import com.squareup.okhttp.internal.Platform;
import com.squareup.okhttp.internal.Util;
-import com.squareup.okhttp.internal.http.HeaderParser;
import com.squareup.okhttp.internal.http.HttpDate;
import java.io.File;
import java.io.FileInputStream;
@@ -45,6 +44,7 @@ public final class Request {
private volatile ParsedHeaders parsedHeaders; // Lazily initialized.
private volatile URI uri; // Lazily initialized.
+ private volatile CacheControl cacheControl; // Lazily initialized.
private Request(Builder builder) {
this.url = builder.url;
@@ -103,26 +103,6 @@ public final class Request {
return headers;
}
- public boolean getNoCache() {
- return parsedHeaders().noCache;
- }
-
- public int getMaxAgeSeconds() {
- return parsedHeaders().maxAgeSeconds;
- }
-
- public int getMaxStaleSeconds() {
- return parsedHeaders().maxStaleSeconds;
- }
-
- public int getMinFreshSeconds() {
- return parsedHeaders().minFreshSeconds;
- }
-
- public boolean getOnlyIfCached() {
- return parsedHeaders().onlyIfCached;
- }
-
public String getUserAgent() {
return parsedHeaders().userAgent;
}
@@ -136,57 +116,29 @@ public final class Request {
return result != null ? result : (parsedHeaders = new ParsedHeaders(headers));
}
+ /**
+ * Returns the cache control directives for this response. This is never null,
+ * even if this response contains no {@code Cache-Control} header.
+ */
+ public CacheControl cacheControl() {
+ CacheControl result = cacheControl;
+ return result != null ? result : (cacheControl = CacheControl.parse(headers));
+ }
+
public boolean isHttps() {
return url().getProtocol().equals("https");
}
/** Parsed request headers, computed on-demand and cached. */
private static class ParsedHeaders {
- /** Don't use a cache to satisfy this request. */
- private boolean noCache;
- private int maxAgeSeconds = -1;
- private int maxStaleSeconds = -1;
- private int minFreshSeconds = -1;
-
- /**
- * 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.
- */
- private boolean onlyIfCached;
-
private String userAgent;
private String proxyAuthorization;
public ParsedHeaders(Headers headers) {
- HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() {
- @Override public void handle(String directive, String parameter) {
- if ("no-cache".equalsIgnoreCase(directive)) {
- noCache = true;
- } else if ("max-age".equalsIgnoreCase(directive)) {
- maxAgeSeconds = HeaderParser.parseSeconds(parameter);
- } else if ("max-stale".equalsIgnoreCase(directive)) {
- maxStaleSeconds = HeaderParser.parseSeconds(parameter);
- } else if ("min-fresh".equalsIgnoreCase(directive)) {
- minFreshSeconds = HeaderParser.parseSeconds(parameter);
- } else if ("only-if-cached".equalsIgnoreCase(directive)) {
- onlyIfCached = true;
- }
- }
- };
-
for (int i = 0; i < headers.size(); i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
- if ("Cache-Control".equalsIgnoreCase(fieldName)) {
- HeaderParser.parseCacheControl(value, handler);
- } else if ("Pragma".equalsIgnoreCase(fieldName)) {
- if ("no-cache".equalsIgnoreCase(value)) {
- noCache = true;
- }
- } else if ("User-Agent".equalsIgnoreCase(fieldName)) {
+ if ("User-Agent".equalsIgnoreCase(fieldName)) {
userAgent = value;
} else if ("Proxy-Authorization".equalsIgnoreCase(fieldName)) {
proxyAuthorization = value;
diff --git a/okhttp/src/main/java/com/squareup/okhttp/Response.java b/okhttp/src/main/java/com/squareup/okhttp/Response.java
index cb3ecb87d..0110f3e19 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/Response.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/Response.java
@@ -51,6 +51,7 @@ public final class Response {
private final Response redirectedBy;
private volatile ParsedHeaders parsedHeaders; // Lazily initialized.
+ private volatile CacheControl cacheControl; // Lazily initialized.
private Response(Builder builder) {
this.request = builder.request;
@@ -148,30 +149,6 @@ public final class Response {
return parsedHeaders().expires;
}
- public boolean isNoCache() {
- return parsedHeaders().noCache;
- }
-
- public boolean isNoStore() {
- return parsedHeaders().noStore;
- }
-
- public int getMaxAgeSeconds() {
- return parsedHeaders().maxAgeSeconds;
- }
-
- public int getSMaxAgeSeconds() {
- return parsedHeaders().sMaxAgeSeconds;
- }
-
- public boolean isPublic() {
- return parsedHeaders().isPublic;
- }
-
- public boolean isMustRevalidate() {
- return parsedHeaders().mustRevalidate;
- }
-
public String getEtag() {
return parsedHeaders().etag;
}
@@ -319,6 +296,15 @@ public final class Response {
return result != null ? result : (parsedHeaders = new ParsedHeaders(headers));
}
+ /**
+ * Returns the cache control directives for this response. This is never null,
+ * even if this response contains no {@code Cache-Control} header.
+ */
+ public CacheControl cacheControl() {
+ CacheControl result = cacheControl;
+ return result != null ? result : (cacheControl = CacheControl.parse(headers));
+ }
+
/** Parsed response headers, computed on-demand and cached. */
private static class ParsedHeaders {
/** The server's time when this response was served, if known. */
@@ -345,73 +331,17 @@ public final class Response {
*/
long receivedResponseMillis;
- /**
- * In the 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.
- */
- boolean noCache;
-
- /** If true, this response should not be cached. */
- boolean noStore;
-
- /**
- * The duration past the response's served date that it can be served
- * without validation.
- */
- int maxAgeSeconds = -1;
-
- /**
- * 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.
- */
- int sMaxAgeSeconds = -1;
-
- /**
- * This request header 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.
- */
- boolean isPublic;
- boolean mustRevalidate;
String etag;
int ageSeconds = -1;
/** Case-insensitive set of field names. */
private Set