1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-12 10:23:16 +03:00

Merge pull request #1082 from square/jwilson_1010_cache_control

More convenient APIs for using Cache-Control in requests.
This commit is contained in:
Jesse Wilson
2014-10-11 09:14:20 -04:00
4 changed files with 310 additions and 3 deletions

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2014 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.okhttp;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public final class CacheControlTest {
@Test public void emptyBuilderIsEmpty() throws Exception {
CacheControl cacheControl = new CacheControl.Builder().build();
assertEquals("", cacheControl.toString());
assertFalse(cacheControl.noCache());
assertFalse(cacheControl.noStore());
assertEquals(-1, cacheControl.maxAgeSeconds());
assertEquals(-1, cacheControl.sMaxAgeSeconds());
assertFalse(cacheControl.isPublic());
assertFalse(cacheControl.mustRevalidate());
assertEquals(-1, cacheControl.maxStaleSeconds());
assertEquals(-1, cacheControl.minFreshSeconds());
assertFalse(cacheControl.onlyIfCached());
assertFalse(cacheControl.mustRevalidate());
}
@Test public void completeBuilder() throws Exception {
CacheControl cacheControl = new CacheControl.Builder()
.noCache()
.noStore()
.maxAge(1, TimeUnit.SECONDS)
.maxStale(2, TimeUnit.SECONDS)
.minFresh(3, TimeUnit.SECONDS)
.onlyIfCached()
.noTransform()
.build();
assertEquals("no-cache, no-store, max-age=1, max-stale=2, min-fresh=3, only-if-cached, "
+ "no-transform", cacheControl.toString());
assertTrue(cacheControl.noCache());
assertTrue(cacheControl.noStore());
assertEquals(1, cacheControl.maxAgeSeconds());
assertEquals(2, cacheControl.maxStaleSeconds());
assertEquals(3, cacheControl.minFreshSeconds());
assertTrue(cacheControl.onlyIfCached());
// These members are accessible to response headers only.
assertEquals(-1, cacheControl.sMaxAgeSeconds());
assertFalse(cacheControl.isPublic());
assertFalse(cacheControl.mustRevalidate());
}
@Test public void parseEmpty() throws Exception {
CacheControl cacheControl = CacheControl.parse(
new Headers.Builder().set("Cache-Control", "").build());
assertEquals("", cacheControl.toString());
assertFalse(cacheControl.noCache());
assertFalse(cacheControl.noStore());
assertEquals(-1, cacheControl.maxAgeSeconds());
assertEquals(-1, cacheControl.sMaxAgeSeconds());
assertFalse(cacheControl.isPublic());
assertFalse(cacheControl.mustRevalidate());
assertEquals(-1, cacheControl.maxStaleSeconds());
assertEquals(-1, cacheControl.minFreshSeconds());
assertFalse(cacheControl.onlyIfCached());
assertFalse(cacheControl.mustRevalidate());
}
@Test public void parse() throws Exception {
String header = "no-cache, no-store, max-age=1, s-maxage=2, public, must-revalidate, "
+ "max-stale=3, min-fresh=4, only-if-cached, no-transform";
CacheControl cacheControl = CacheControl.parse(new Headers.Builder()
.set("Cache-Control", header)
.build());
assertTrue(cacheControl.noCache());
assertTrue(cacheControl.noStore());
assertEquals(1, cacheControl.maxAgeSeconds());
assertEquals(2, cacheControl.sMaxAgeSeconds());
assertTrue(cacheControl.isPublic());
assertTrue(cacheControl.mustRevalidate());
assertEquals(3, cacheControl.maxStaleSeconds());
assertEquals(4, cacheControl.minFreshSeconds());
assertTrue(cacheControl.onlyIfCached());
assertTrue(cacheControl.noTransform());
assertEquals(header, cacheControl.toString());
}
@Test public void timeDurationTruncatedToMaxValue() throws Exception {
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(365 * 100, TimeUnit.DAYS) // Longer than Integer.MAX_VALUE seconds.
.build();
assertEquals(Integer.MAX_VALUE, cacheControl.maxAgeSeconds());
}
@Test public void secondsMustBeNonNegative() throws Exception {
CacheControl.Builder builder = new CacheControl.Builder();
try {
builder.maxAge(-1, TimeUnit.SECONDS);
fail();
} catch (IllegalArgumentException expected) {
}
}
@Test public void timePrecisionIsTruncatedToSeconds() throws Exception {
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(4999, TimeUnit.MILLISECONDS)
.build();
assertEquals(4, cacheControl.maxAgeSeconds());
}
}

View File

@@ -21,7 +21,8 @@ import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import okio.Buffer;
import org.junit.Test;
@@ -113,6 +114,23 @@ public final class RequestTest {
assertEquals(new URL("http://localhost/api"), request.url());
}
@Test public void cacheControl() throws Exception {
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder().noCache().build())
.url("https://square.com")
.build();
assertEquals(Arrays.asList("no-cache"), request.headers("Cache-Control"));
}
@Test public void emptyCacheControlClearsAllCacheControlHeaders() throws Exception {
Request request = new Request.Builder()
.header("Cache-Control", "foo")
.cacheControl(new CacheControl.Builder().build())
.url("https://square.com")
.build();
assertEquals(Collections.<String>emptyList(), request.headers("Cache-Control"));
}
private String bodyToHex(RequestBody body) throws IOException {
Buffer buffer = new Buffer();
body.writeTo(buffer);

View File

@@ -1,6 +1,7 @@
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
@@ -11,6 +12,24 @@ import com.squareup.okhttp.internal.http.HeaderParser;
* 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;
@@ -20,10 +39,11 @@ public final class CacheControl {
private final int maxStaleSeconds;
private final int minFreshSeconds;
private final boolean onlyIfCached;
private final boolean noTransform;
private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds,
boolean isPublic, boolean mustRevalidate, int maxStaleSeconds, int minFreshSeconds,
boolean onlyIfCached) {
boolean onlyIfCached, boolean noTransform) {
this.noCache = noCache;
this.noStore = noStore;
this.maxAgeSeconds = maxAgeSeconds;
@@ -33,6 +53,20 @@ public final class CacheControl {
this.maxStaleSeconds = maxStaleSeconds;
this.minFreshSeconds = minFreshSeconds;
this.onlyIfCached = onlyIfCached;
this.noTransform = noTransform;
}
private CacheControl(Builder builder) {
this.noCache = builder.noCache;
this.noStore = builder.noStore;
this.maxAgeSeconds = builder.maxAgeSeconds;
this.sMaxAgeSeconds = -1;
this.isPublic = false;
this.mustRevalidate = false;
this.maxStaleSeconds = builder.maxStaleSeconds;
this.minFreshSeconds = builder.minFreshSeconds;
this.onlyIfCached = builder.onlyIfCached;
this.noTransform = builder.noTransform;
}
/**
@@ -96,6 +130,10 @@ public final class CacheControl {
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.
@@ -110,6 +148,7 @@ public final class CacheControl {
int maxStaleSeconds = -1;
int minFreshSeconds = -1;
boolean onlyIfCached = false;
boolean noTransform = false;
for (int i = 0; i < headers.size(); i++) {
if (!headers.name(i).equalsIgnoreCase("Cache-Control")
@@ -166,11 +205,126 @@ public final class CacheControl {
minFreshSeconds = HeaderParser.parseSeconds(parameter);
} else if ("only-if-cached".equalsIgnoreCase(directive)) {
onlyIfCached = true;
} else if ("no-transform".equalsIgnoreCase(directive)) {
noTransform = true;
}
}
}
return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPublic,
mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached);
mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform);
}
@Override public String toString() {
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 (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);
}
}
}

View File

@@ -185,6 +185,17 @@ public final class Request {
return this;
}
/**
* Sets this request's {@code Cache-Control} header, replacing any cache
* control headers already present. If {@code cacheControl} doesn't define
* any directives, this clears this request's cache-control headers.
*/
public Builder cacheControl(CacheControl cacheControl) {
String value = cacheControl.toString();
if (value.isEmpty()) return removeHeader("Cache-Control");
return header("Cache-Control", value);
}
public Builder get() {
return method("GET", null);
}