Skip to content

Commit

Permalink
Make cache-control remember its header value.
Browse files Browse the repository at this point in the history
  • Loading branch information
JakeWharton committed Jan 5, 2015
1 parent 7000edd commit 7a279cb
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

Expand Down Expand Up @@ -99,6 +101,50 @@ public final class CacheControlTest {
assertEquals(header, cacheControl.toString());
}

@Test public void parseCacheControlAndPragmaAreCombined() {
Headers headers =
Headers.of("Cache-Control", "max-age=12", "Pragma", "must-revalidate", "Pragma", "public");
CacheControl cacheControl = CacheControl.parse(headers);
assertEquals("max-age=12, public, must-revalidate", cacheControl.toString());
}

@SuppressWarnings("RedundantStringConstructorCall") // Testing instance equality.
@Test public void parseCacheControlHeaderValueIsRetained() {
String value = new String("max-age=12");
Headers headers = Headers.of("Cache-Control", value);
CacheControl cacheControl = CacheControl.parse(headers);
assertSame(value, cacheControl.toString());
}

@Test public void parseCacheControlHeaderValueInvalidatedByPragma() {
Headers headers = Headers.of("Cache-Control", "max-age=12", "Pragma", "must-revalidate");
CacheControl cacheControl = CacheControl.parse(headers);
assertNull(cacheControl.headerValue);
}

@Test public void parseCacheControlHeaderValueInvalidatedByTwoValues() {
Headers headers = Headers.of("Cache-Control", "max-age=12", "Cache-Control", "must-revalidate");
CacheControl cacheControl = CacheControl.parse(headers);
assertNull(cacheControl.headerValue);
}

@Test public void parsePragmaHeaderValueIsNotRetained() {
Headers headers = Headers.of("Pragma", "must-revalidate");
CacheControl cacheControl = CacheControl.parse(headers);
assertNull(cacheControl.headerValue);
}

@Test public void computedHeaderValueIsCached() {
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(2, TimeUnit.DAYS)
.build();
assertNull(cacheControl.headerValue);
assertEquals("max-age=172800", cacheControl.toString());
assertEquals("max-age=172800", cacheControl.headerValue);
cacheControl.headerValue = "Hi";
assertEquals("Hi", cacheControl.toString());
}

@Test public void timeDurationTruncatedToMaxValue() throws Exception {
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(365 * 100, TimeUnit.DAYS) // Longer than Integer.MAX_VALUE seconds.
Expand Down
55 changes: 40 additions & 15 deletions okhttp/src/main/java/com/squareup/okhttp/CacheControl.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ public final class CacheControl {
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 isPublic, boolean mustRevalidate, int maxStaleSeconds, int minFreshSeconds,
boolean onlyIfCached, boolean noTransform) {
boolean onlyIfCached, boolean noTransform, String headerValue) {
this.noCache = noCache;
this.noStore = noStore;
this.maxAgeSeconds = maxAgeSeconds;
Expand All @@ -54,6 +56,7 @@ private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sM
this.minFreshSeconds = minFreshSeconds;
this.onlyIfCached = onlyIfCached;
this.noTransform = noTransform;
this.headerValue = headerValue;
}

private CacheControl(Builder builder) {
Expand Down Expand Up @@ -150,40 +153,54 @@ public static CacheControl parse(Headers headers) {
boolean onlyIfCached = false;
boolean noTransform = false;

boolean canUseHeaderValue = true;
String headerValue = null;

for (int i = 0, size = headers.size(); i < size; i++) {
if (!headers.name(i).equalsIgnoreCase("Cache-Control")
&& !headers.name(i).equalsIgnoreCase("Pragma")) {
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;
}

String string = headers.value(i);
int pos = 0;
while (pos < string.length()) {
while (pos < value.length()) {
int tokenStart = pos;
pos = HeaderParser.skipUntil(string, pos, "=,;");
String directive = string.substring(tokenStart, pos).trim();
pos = HeaderParser.skipUntil(value, pos, "=,;");
String directive = value.substring(tokenStart, pos).trim();
String parameter;

if (pos == string.length() || string.charAt(pos) == ',' || string.charAt(pos) == ';') {
if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') {
pos++; // consume ',' or ';' (if necessary)
parameter = null;
} else {
pos++; // consume '='
pos = HeaderParser.skipWhitespace(string, pos);
pos = HeaderParser.skipWhitespace(value, pos);

// quoted string
if (pos < string.length() && string.charAt(pos) == '\"') {
if (pos < value.length() && value.charAt(pos) == '\"') {
pos++; // consume '"' open quote
int parameterStart = pos;
pos = HeaderParser.skipUntil(string, pos, "\"");
parameter = string.substring(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(string, pos, ",;");
parameter = string.substring(parameterStart, pos).trim();
pos = HeaderParser.skipUntil(value, pos, ",;");
parameter = value.substring(parameterStart, pos).trim();
}
}

Expand Down Expand Up @@ -211,11 +228,19 @@ public static CacheControl parse(Headers headers) {
}
}

if (!canUseHeaderValue) {
headerValue = null;
}
return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPublic,
mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform);
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, ");
Expand Down

0 comments on commit 7a279cb

Please sign in to comment.