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:
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user