Request / response Cache-Control APIs

This commit is contained in:
Oleg Kalnichevski 2023-05-15 22:18:17 +02:00
parent f0d76de66d
commit 3f81f21cab
9 changed files with 807 additions and 306 deletions

View File

@ -56,10 +56,20 @@ public class HeaderConstants {
public static final String VARY = "Vary";
public static final String ALLOW = "Allow";
public static final String VIA = "Via";
/**
* @deprecated Use {@link #CACHE_CONTROL_PUBLIC}
*/
@Deprecated
public static final String PUBLIC = "public";
/**
* @deprecated Use {@link #CACHE_CONTROL_PRIVATE}
*/
@Deprecated
public static final String PRIVATE = "private";
public static final String CACHE_CONTROL = "Cache-Control";
public static final String CACHE_CONTROL_PUBLIC = "public";
public static final String CACHE_CONTROL_PRIVATE = "private";
public static final String CACHE_CONTROL_NO_STORE = "no-store";
public static final String CACHE_CONTROL_NO_CACHE = "no-cache";
public static final String CACHE_CONTROL_MAX_AGE = "max-age";
@ -68,7 +78,18 @@ public class HeaderConstants {
public static final String CACHE_CONTROL_MIN_FRESH = "min-fresh";
public static final String CACHE_CONTROL_MUST_REVALIDATE = "must-revalidate";
public static final String CACHE_CONTROL_PROXY_REVALIDATE = "proxy-revalidate";
public static final String CACHE_CONTROL_STALE_IF_ERROR = "stale-if-error";
public static final String CACHE_CONTROL_STALE_WHILE_REVALIDATE = "stale-while-revalidate";
public static final String CACHE_CONTROL_ONLY_IF_CACHED = "only-if-cached";
/**
* @deprecated Use {@link #CACHE_CONTROL_STALE_IF_ERROR}
*/
@Deprecated
public static final String STALE_IF_ERROR = "stale-if-error";
/**
* @deprecated Use {@link #CACHE_CONTROL_STALE_WHILE_REVALIDATE}
*/
@Deprecated
public static final String STALE_WHILE_REVALIDATE = "stale-while-revalidate";
public static final String WARNING = "Warning";

View File

@ -31,229 +31,42 @@ import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import java.util.Collections;
import java.util.Set;
/**
* Represents the values of the Cache-Control header in an HTTP response, which indicate whether and for how long
* Represents the values of the Cache-Control header in an HTTP message, which indicate whether and for how long
* the response can be cached by the client and intermediary proxies.
* <p>
* The class provides methods to retrieve the maximum age of the response and the maximum age that applies to shared
* caches. The values are expressed in seconds, with -1 indicating that the value was not specified in the header.
* <p>
* Instances of this class are immutable, meaning that their values cannot be changed once they are set. To create an
* instance, use one of the constructors that take the desired values as arguments. Alternatively, use the default
* constructor to create an instance with both values set to -1, indicating that the header was not present in the
* response.
* <p>
* Example usage:
* <pre>
* HttpResponse response = httpClient.execute(httpGet);
* CacheControlHeader cacheControlHeader = CacheControlHeaderParser.INSTANCE.parse(response.getHeaders("Cache-Control"));
* long maxAge = cacheControlHeader.getMaxAge();
* long sharedMaxAge = cacheControlHeader.getSharedMaxAge();
* </pre>
*
* @since 5.3
*/
@Internal
@Contract(threading = ThreadingBehavior.IMMUTABLE)
final class CacheControl {
/**
* The max-age directive value.
*/
private final long maxAge;
/**
* The shared-max-age directive value.
*/
private final long sharedMaxAge;
/**
* The isNoCache flag indicates whether the Cache-Control header includes the no-cache directive.
*/
private final boolean noCache;
/**
* The isNoStore flag indicates whether the Cache-Control header includes the no-store directive.
*/
private final boolean noStore;
/**
* The isPrivate flag indicates whether the Cache-Control header includes the private directive.
*/
private final boolean cachePrivate;
/**
* Indicates whether the Cache-Control header includes the "must-revalidate" directive.
*/
private final boolean mustRevalidate;
/**
* Indicates whether the Cache-Control header includes the "proxy-revalidate" directive.
*/
private final boolean proxyRevalidate;
/**
* Indicates whether the Cache-Control header includes the "public" directive.
*/
private final boolean cachePublic;
/**
* The number of seconds that a stale response is considered fresh for the purpose
* of serving a response while a revalidation request is made to the origin server.
*/
private final long staleWhileRevalidate;
/**
* A set of field names specified in the "no-cache" directive of the Cache-Control header.
*/
private final Set<String> noCacheFields;
/**
* Creates a new instance of {@code CacheControl} with default values.
* The default values are: max-age=-1, shared-max-age=-1, must-revalidate=false, no-cache=false,
* no-store=false, private=false, proxy-revalidate=false, public=false, stale_while_revalidate=-1,
* and noCacheFields as an empty set.
*/
public CacheControl() {
this(-1, -1, false, false, false, false, false, false,-1, Collections.emptySet());
}
/**
* Creates a new instance of {@code CacheControl} with the specified max-age, shared-max-age, no-cache, no-store,
* private, must-revalidate, proxy-revalidate, and public values.
*
* @param maxAge The max-age value from the Cache-Control header.
* @param sharedMaxAge The shared-max-age value from the Cache-Control header.
* @param mustRevalidate The must-revalidate value from the Cache-Control header.
* @param noCache The no-cache value from the Cache-Control header.
* @param noStore The no-store value from the Cache-Control header.
* @param cachePrivate The private value from the Cache-Control header.
* @param proxyRevalidate The proxy-revalidate value from the Cache-Control header.
* @param cachePublic The public value from the Cache-Control header.
* @param noCacheFields The set of field names specified in the "no-cache" directive of the Cache-Control header.
*/
public CacheControl(final long maxAge, final long sharedMaxAge, final boolean mustRevalidate, final boolean noCache, final boolean noStore,
final boolean cachePrivate, final boolean proxyRevalidate, final boolean cachePublic,
final long staleWhileRevalidate,
final Set<String> noCacheFields) {
this.maxAge = maxAge;
this.sharedMaxAge = sharedMaxAge;
this.noCache = noCache;
this.noStore = noStore;
this.cachePrivate = cachePrivate;
this.mustRevalidate = mustRevalidate;
this.proxyRevalidate = proxyRevalidate;
this.cachePublic = cachePublic;
this.staleWhileRevalidate = staleWhileRevalidate;
this.noCacheFields = Collections.unmodifiableSet(noCacheFields);
}
interface CacheControl {
/**
* Returns the max-age value from the Cache-Control header.
*
* @return The max-age value.
*/
public long getMaxAge() {
return maxAge;
}
/**
* Returns the shared-max-age value from the Cache-Control header.
*
* @return The shared-max-age value.
*/
public long getSharedMaxAge() {
return sharedMaxAge;
}
long getMaxAge();
/**
* Returns the no-cache flag from the Cache-Control header.
*
* @return The no-cache flag.
*/
public boolean isNoCache() {
return noCache;
}
boolean isNoCache();
/**
* Returns the no-store flag from the Cache-Control header.
*
* @return The no-store flag.
*/
public boolean isNoStore() {
return noStore;
}
boolean isNoStore();
/**
* Returns the private flag from the Cache-Control header.
* Returns the stale-if-error value from the Cache-Control header.
*
* @return The private flag.
* @return The stale-if-error value.
*/
public boolean isCachePrivate() {
return cachePrivate;
}
long getStaleIfError();
/**
* Returns whether the must-revalidate directive is present in the Cache-Control header.
*
* @return {@code true} if the must-revalidate directive is present, otherwise {@code false}
*/
public boolean isMustRevalidate() {
return mustRevalidate;
}
/**
* Returns whether the proxy-revalidate value is set in the Cache-Control header.
*
* @return {@code true} if proxy-revalidate is set, {@code false} otherwise.
*/
public boolean isProxyRevalidate() {
return proxyRevalidate;
}
/**
* Returns whether the public value is set in the Cache-Control header.
*
* @return {@code true} if public is set, {@code false} otherwise.
*/
public boolean isPublic() {
return cachePublic;
}
/**
* Returns the stale-while-revalidate value from the Cache-Control header.
*
* @return The stale-while-revalidate value.
*/
public long getStaleWhileRevalidate() {
return staleWhileRevalidate;
}
/**
* Returns an unmodifiable set of field names specified in the "no-cache" directive of the Cache-Control header.
*
* @return The set of field names specified in the "no-cache" directive.
*/
public Set<String> getNoCacheFields() {
return noCacheFields;
}
/**
* Returns a string representation of the {@code CacheControl} object, including the max-age, shared-max-age, no-cache,
* no-store, private, must-revalidate, proxy-revalidate, public values, and noCacheFields.
*
* @return A string representation of the object.
*/
@Override
public String toString() {
return "CacheControl{" +
"maxAge=" + maxAge +
", sharedMaxAge=" + sharedMaxAge +
", noCache=" + noCache +
", noCacheFields=" + noCacheFields +
", noStore=" + noStore +
", cachePrivate=" + cachePrivate +
", mustRevalidate=" + mustRevalidate +
", proxyRevalidate=" + proxyRevalidate +
", cachePublic=" + cachePublic +
", stale_while_revalidate=" + staleWhileRevalidate +
'}';
}
}

View File

@ -30,6 +30,7 @@ import java.util.BitSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.function.BiConsumer;
import org.apache.hc.client5.http.cache.HeaderConstants;
import org.apache.hc.core5.annotation.Contract;
@ -37,6 +38,9 @@ import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.FormattedHeader;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.message.ParserCursor;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.CharArrayBuffer;
@ -51,14 +55,14 @@ import org.slf4j.LoggerFactory;
* This class is thread-safe and has a singleton instance ({@link #INSTANCE}).
* </p>
* <p>
* The {@link #parse(Iterator)} method takes an HTTP header and returns a {@link CacheControl} object containing
* The {@link #parseResponse(Iterator)} method takes an HTTP header and returns a {@link ResponseCacheControl} object containing
* the relevant caching directives. The header can be either a {@link FormattedHeader} object, which contains a
* pre-parsed {@link CharArrayBuffer}, or a plain {@link Header} object, in which case the value will be parsed and
* stored in a new {@link CharArrayBuffer}.
* </p>
* <p>
* This parser only supports two directives: "max-age" and "s-maxage". If either of these directives are present in the
* header, their values will be parsed and stored in the {@link CacheControl} object. If both directives are
* header, their values will be parsed and stored in the {@link ResponseCacheControl} object. If both directives are
* present, the value of "s-maxage" takes precedence.
* </p>
*/
@ -102,39 +106,7 @@ class CacheControlHeaderParser {
this.tokenParser = Tokenizer.INSTANCE;
}
/**
* Parses the specified header and returns a new {@link CacheControl} instance containing the relevant caching
* <p>
* directives.
*
* <p>The returned {@link CacheControl} instance will contain the values for "max-age" and "s-maxage" caching
* directives parsed from the input header. If the input header does not contain any caching directives or if the
* <p>
* directives are malformed, the returned {@link CacheControl} instance will have default values for "max-age" and
* <p>
* "s-maxage" (-1).</p>
*
* @param headerIterator the header to parse, cannot be {@code null}
* @return a new {@link CacheControl} instance containing the relevant caching directives parsed from the header
* @throws IllegalArgumentException if the input header is {@code null}
*/
public final CacheControl parse(final Iterator<Header> headerIterator) {
Args.notNull(headerIterator, "headerIterator");
// Initialize variables to hold the Cache-Control directives
long maxAge = -1;
long sharedMaxAge = -1;
boolean noCache = false;
boolean noStore = false;
boolean cachePrivate = false;
boolean mustRevalidate = false;
boolean proxyRevalidate = false;
boolean cachePublic = false;
long staleWhileRevalidate = -1;
// Declare a new Set variable at the beginning of the method to store the field-names
final Set<String> noCacheFields = new HashSet<>();
// Iterate over each header
public void parse(final Iterator<Header> headerIterator, final BiConsumer<String, String> consumer) {
while (headerIterator.hasNext()) {
final Header header = headerIterator.next();
final CharArrayBuffer buffer;
@ -145,7 +117,7 @@ class CacheControlHeaderParser {
} else {
final String s = header.getValue();
if (s == null) {
return new CacheControl();
return;
}
buffer = new CharArrayBuffer(s.length());
buffer.append(s);
@ -166,53 +138,121 @@ class CacheControlHeaderParser {
}
}
}
// Update the Cache-Control directives based on the current header
if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_S_MAX_AGE)) {
sharedMaxAge = parseSeconds(name, value);
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MAX_AGE)) {
maxAge = parseSeconds(name, value);
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE)) {
mustRevalidate = true;
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_CACHE)) {
noCache = true;
if (value != null) {
final Tokenizer.Cursor valCursor = new ParserCursor(0, value.length());
while (!valCursor.atEnd()) {
final String token = tokenParser.parseToken(value, valCursor, VALUE_DELIMS);
if (!TextUtils.isBlank(token)) {
noCacheFields.add(token);
}
if (!valCursor.atEnd()) {
valCursor.updatePos(valCursor.getPos() + 1);
}
}
}
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_STORE)) {
noStore = true;
} else if (name.equalsIgnoreCase(HeaderConstants.PRIVATE)) {
cachePrivate = true;
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE)) {
proxyRevalidate = true;
} else if (name.equalsIgnoreCase(HeaderConstants.PUBLIC)) {
cachePublic = true;
} else if (name.equalsIgnoreCase(HeaderConstants.STALE_WHILE_REVALIDATE)) {
staleWhileRevalidate = parseSeconds(name, value);
}
consumer.accept(name, value);
}
}
// Return a single CacheControl object with the combined directives
return new CacheControl(maxAge, sharedMaxAge, mustRevalidate, noCache, noStore, cachePrivate, proxyRevalidate, cachePublic, staleWhileRevalidate, noCacheFields);
}
/**
* Parses the specified response header and returns a new {@link ResponseCacheControl} instance containing
* the relevant caching directives.
*
* <p>The returned {@link ResponseCacheControl} instance will contain the values for "max-age" and "s-maxage"
* caching directives parsed from the input header. If the input header does not contain any caching directives
* or if the directives are malformed, the returned {@link ResponseCacheControl} instance will have default values
* for "max-age" and "s-maxage" (-1).</p>
*
* @param headerIterator the header to parse, cannot be {@code null}
* @return a new {@link ResponseCacheControl} instance containing the relevant caching directives parsed
* from the response header
* @throws IllegalArgumentException if the input header is {@code null}
*/
public final ResponseCacheControl parseResponse(final Iterator<Header> headerIterator) {
Args.notNull(headerIterator, "headerIterator");
final ResponseCacheControl.Builder builder = new ResponseCacheControl.Builder();
parse(headerIterator, (name, value) -> {
if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_S_MAX_AGE)) {
builder.setSharedMaxAge(parseSeconds(name, value));
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MAX_AGE)) {
builder.setMaxAge(parseSeconds(name, value));
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE)) {
builder.setMustRevalidate(true);
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_CACHE)) {
builder.setNoCache(true);
if (value != null) {
final Tokenizer.Cursor valCursor = new ParserCursor(0, value.length());
final Set<String> noCacheFields = new HashSet<>();
while (!valCursor.atEnd()) {
final String token = tokenParser.parseToken(value, valCursor, VALUE_DELIMS);
if (!TextUtils.isBlank(token)) {
noCacheFields.add(token);
}
if (!valCursor.atEnd()) {
valCursor.updatePos(valCursor.getPos() + 1);
}
}
builder.setNoCacheFields(noCacheFields);
}
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_STORE)) {
builder.setNoStore(true);
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_PRIVATE)) {
builder.setCachePrivate(true);
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE)) {
builder.setProxyRevalidate(true);
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_PUBLIC)) {
builder.setCachePublic(true);
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_STALE_WHILE_REVALIDATE)) {
builder.setStaleWhileRevalidate(parseSeconds(name, value));
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_STALE_IF_ERROR)) {
builder.setStaleIfError(parseSeconds(name, value));
}
});
return builder.build();
}
public final ResponseCacheControl parse(final HttpResponse response) {
return parseResponse(response.headerIterator(HttpHeaders.CACHE_CONTROL));
}
/**
* Parses the specified request header and returns a new {@link RequestCacheControl} instance containing
* the relevant caching directives.
*
* @param headerIterator the header to parse, cannot be {@code null}
* @return a new {@link RequestCacheControl} instance containing the relevant caching directives parsed
* from the request header
* @throws IllegalArgumentException if the input header is {@code null}
*/
public final RequestCacheControl parseRequest(final Iterator<Header> headerIterator) {
Args.notNull(headerIterator, "headerIterator");
final RequestCacheControl.Builder builder = new RequestCacheControl.Builder();
parse(headerIterator, (name, value) -> {
if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MAX_AGE)) {
builder.setMaxAge(parseSeconds(name, value));
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MAX_STALE)) {
builder.setMaxStale(parseSeconds(name, value));
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MIN_FRESH)) {
builder.setMinFresh(parseSeconds(name, value));
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_STORE)) {
builder.setNoStore(true);
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_CACHE)) {
builder.setNoCache(true);
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_ONLY_IF_CACHED)) {
builder.setOnlyIfCached(true);
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_STALE_IF_ERROR)) {
builder.setStaleIfError(parseSeconds(name, value));
}
});
return builder.build();
}
public final RequestCacheControl parse(final HttpRequest request) {
return parseRequest(request.headerIterator(HttpHeaders.CACHE_CONTROL));
}
private static long parseSeconds(final String name, final String value) {
if (TextUtils.isEmpty(value)) {
return -1;
}
try {
return value != null ? Long.parseLong(value) : -1;
final long n = Long.parseLong(value);
return n < 0 ? -1 : n;
} catch (final NumberFormatException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Directive {} was malformed: {}", name, value);
}
return -1;
}
return 0;
}
}

View File

@ -407,7 +407,7 @@ public class CachingExecBase {
if (it == null || !it.hasNext()) {
return false;
} else {
final CacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(it);
final ResponseCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parseResponse(it);
return cacheControl.isNoCache();
}
}

View File

@ -0,0 +1,228 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior;
/**
* Represents the values of the Cache-Control header in an HTTP request, which indicate whether and for how long
* the client is willing to cache the response.
*
* @since 5.3
*/
@Internal
@Contract(threading = ThreadingBehavior.IMMUTABLE)
final class RequestCacheControl implements CacheControl {
private final long maxAge;
private final long maxStale;
private final long minFresh;
private final boolean noCache;
private final boolean noStore;
private final boolean onlyIfCached;
private final long staleIfError;
RequestCacheControl(final long maxAge, final long maxStale, final long minFresh, final boolean noCache,
final boolean noStore, final boolean onlyIfCached, final long staleIfError) {
this.maxAge = maxAge;
this.maxStale = maxStale;
this.minFresh = minFresh;
this.noCache = noCache;
this.noStore = noStore;
this.onlyIfCached = onlyIfCached;
this.staleIfError = staleIfError;
}
/**
* Returns the max-age value from the Cache-Control header.
*
* @return The max-age value.
*/
@Override
public long getMaxAge() {
return maxAge;
}
/**
* Returns the max-stale value from the Cache-Control header.
*
* @return The max-stale value.
*/
public long getMaxStale() {
return maxStale;
}
/**
* Returns the min-fresh value from the Cache-Control header.
*
* @return The min-fresh value.
*/
public long getMinFresh() {
return minFresh;
}
/**
* Returns the no-cache flag from the Cache-Control header.
*
* @return The no-cache flag.
*/
@Override
public boolean isNoCache() {
return noCache;
}
/**
* Returns the no-store flag from the Cache-Control header.
*
* @return The no-store flag.
*/
@Override
public boolean isNoStore() {
return noStore;
}
/**
* Returns the only-if-cached flag from the Cache-Control header.
*
* @return The only-if-cached flag.
*/
public boolean isOnlyIfCached() {
return onlyIfCached;
}
/**
* Returns the stale-if-error value from the Cache-Control header.
*
* @return The stale-if-error value.
*/
@Override
public long getStaleIfError() {
return staleIfError;
}
@Override
public String toString() {
return "RequestCacheControl{" +
"maxAge=" + maxAge +
", maxStale=" + maxStale +
", minFresh=" + minFresh +
", noCache=" + noCache +
", noStore=" + noStore +
", onlyIfCached=" + onlyIfCached +
", staleIfError=" + staleIfError +
'}';
}
static Builder builder() {
return new Builder();
}
static class Builder {
private long maxAge = -1;
private long maxStale = -1;
private long minFresh = -1;
private boolean noCache;
private boolean noStore;
private boolean onlyIfCached;
private long staleIfError = -1;
Builder() {
}
public long getMaxAge() {
return maxAge;
}
public Builder setMaxAge(final long maxAge) {
this.maxAge = maxAge;
return this;
}
public long getMaxStale() {
return maxStale;
}
public Builder setMaxStale(final long maxStale) {
this.maxStale = maxStale;
return this;
}
public long getMinFresh() {
return minFresh;
}
public Builder setMinFresh(final long minFresh) {
this.minFresh = minFresh;
return this;
}
public boolean isNoCache() {
return noCache;
}
public Builder setNoCache(final boolean noCache) {
this.noCache = noCache;
return this;
}
public boolean isNoStore() {
return noStore;
}
public Builder setNoStore(final boolean noStore) {
this.noStore = noStore;
return this;
}
public boolean isOnlyIfCached() {
return onlyIfCached;
}
public Builder setOnlyIfCached(final boolean onlyIfCached) {
this.onlyIfCached = onlyIfCached;
return this;
}
public long getStaleIfError() {
return staleIfError;
}
public Builder setStaleIfError(final long staleIfError) {
this.staleIfError = staleIfError;
return this;
}
public RequestCacheControl build() {
return new RequestCacheControl(maxAge, maxStale, minFresh, noCache, noStore, onlyIfCached, staleIfError);
}
}
}

View File

@ -0,0 +1,382 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import java.util.Collections;
import java.util.Set;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior;
/**
* Represents the values of the Cache-Control header in an HTTP response, which indicate whether and for how long
* the response can be cached by the client and intermediary proxies.
* <p>
* The class provides methods to retrieve the maximum age of the response and the maximum age that applies to shared
* caches. The values are expressed in seconds, with -1 indicating that the value was not specified in the header.
* <p>
* Instances of this class are immutable, meaning that their values cannot be changed once they are set. To create an
* instance, use one of the constructors that take the desired values as arguments. Alternatively, use the default
* constructor to create an instance with both values set to -1, indicating that the header was not present in the
* response.
*
* @since 5.3
*/
@Internal
@Contract(threading = ThreadingBehavior.IMMUTABLE)
final class ResponseCacheControl implements CacheControl {
/**
* The max-age directive value.
*/
private final long maxAge;
/**
* The shared-max-age directive value.
*/
private final long sharedMaxAge;
/**
* The isNoCache flag indicates whether the Cache-Control header includes the no-cache directive.
*/
private final boolean noCache;
/**
* The isNoStore flag indicates whether the Cache-Control header includes the no-store directive.
*/
private final boolean noStore;
/**
* The isPrivate flag indicates whether the Cache-Control header includes the private directive.
*/
private final boolean cachePrivate;
/**
* Indicates whether the Cache-Control header includes the "must-revalidate" directive.
*/
private final boolean mustRevalidate;
/**
* Indicates whether the Cache-Control header includes the "proxy-revalidate" directive.
*/
private final boolean proxyRevalidate;
/**
* Indicates whether the Cache-Control header includes the "public" directive.
*/
private final boolean cachePublic;
/**
* The number of seconds that a stale response is considered fresh for the purpose
* of serving a response while a revalidation request is made to the origin server.
*/
private final long staleWhileRevalidate;
/**
* The number of seconds that a cached stale response MAY be used to satisfy the request,
* regardless of other freshness information..
*/
private final long staleIfError;
/**
* A set of field names specified in the "no-cache" directive of the Cache-Control header.
*/
private final Set<String> noCacheFields;
/**
* Creates a new instance of {@code CacheControl} with the specified values.
*
* @param maxAge The max-age value from the Cache-Control header.
* @param sharedMaxAge The shared-max-age value from the Cache-Control header.
* @param mustRevalidate The must-revalidate value from the Cache-Control header.
* @param noCache The no-cache value from the Cache-Control header.
* @param noStore The no-store value from the Cache-Control header.
* @param cachePrivate The private value from the Cache-Control header.
* @param proxyRevalidate The proxy-revalidate value from the Cache-Control header.
* @param cachePublic The public value from the Cache-Control header.
* @param staleWhileRevalidate The stale-while-revalidate value from the Cache-Control header.
* @param staleIfError The stale-if-error value from the Cache-Control header.
* @param noCacheFields The set of field names specified in the "no-cache" directive of the Cache-Control header.
*/
ResponseCacheControl(final long maxAge, final long sharedMaxAge, final boolean mustRevalidate, final boolean noCache,
final boolean noStore, final boolean cachePrivate, final boolean proxyRevalidate,
final boolean cachePublic, final long staleWhileRevalidate, final long staleIfError,
final Set<String> noCacheFields) {
this.maxAge = maxAge;
this.sharedMaxAge = sharedMaxAge;
this.noCache = noCache;
this.noStore = noStore;
this.cachePrivate = cachePrivate;
this.mustRevalidate = mustRevalidate;
this.proxyRevalidate = proxyRevalidate;
this.cachePublic = cachePublic;
this.staleWhileRevalidate = staleWhileRevalidate;
this.staleIfError = staleIfError;
this.noCacheFields = noCacheFields != null ? Collections.unmodifiableSet(noCacheFields) : Collections.emptySet();
}
/**
* Returns the max-age value from the Cache-Control header.
*
* @return The max-age value.
*/
@Override
public long getMaxAge() {
return maxAge;
}
/**
* Returns the shared-max-age value from the Cache-Control header.
*
* @return The shared-max-age value.
*/
public long getSharedMaxAge() {
return sharedMaxAge;
}
/**
* Returns the no-cache flag from the Cache-Control header.
*
* @return The no-cache flag.
*/
@Override
public boolean isNoCache() {
return noCache;
}
/**
* Returns the no-store flag from the Cache-Control header.
*
* @return The no-store flag.
*/
@Override
public boolean isNoStore() {
return noStore;
}
/**
* Returns the private flag from the Cache-Control header.
*
* @return The private flag.
*/
public boolean isCachePrivate() {
return cachePrivate;
}
/**
* Returns whether the must-revalidate directive is present in the Cache-Control header.
*
* @return {@code true} if the must-revalidate directive is present, otherwise {@code false}
*/
public boolean isMustRevalidate() {
return mustRevalidate;
}
/**
* Returns whether the proxy-revalidate value is set in the Cache-Control header.
*
* @return {@code true} if proxy-revalidate is set, {@code false} otherwise.
*/
public boolean isProxyRevalidate() {
return proxyRevalidate;
}
/**
* Returns whether the public value is set in the Cache-Control header.
*
* @return {@code true} if public is set, {@code false} otherwise.
*/
public boolean isPublic() {
return cachePublic;
}
/**
* Returns the stale-while-revalidate value from the Cache-Control header.
*
* @return The stale-while-revalidate value.
*/
public long getStaleWhileRevalidate() {
return staleWhileRevalidate;
}
/**
* Returns the stale-if-error value from the Cache-Control header.
*
* @return The stale-if-error value.
*/
@Override
public long getStaleIfError() {
return staleIfError;
}
/**
* Returns an unmodifiable set of field names specified in the "no-cache" directive of the Cache-Control header.
*
* @return The set of field names specified in the "no-cache" directive.
*/
public Set<String> getNoCacheFields() {
return noCacheFields;
}
@Override
public String toString() {
return "CacheControl{" +
"maxAge=" + maxAge +
", sharedMaxAge=" + sharedMaxAge +
", noCache=" + noCache +
", noStore=" + noStore +
", cachePrivate=" + cachePrivate +
", mustRevalidate=" + mustRevalidate +
", proxyRevalidate=" + proxyRevalidate +
", cachePublic=" + cachePublic +
", staleWhileRevalidate=" + staleWhileRevalidate +
", staleIfError=" + staleIfError +
", noCacheFields=" + noCacheFields +
'}';
}
static Builder create() {
return new Builder();
}
static class Builder {
private long maxAge = -1;
private long sharedMaxAge = -1;
private boolean noCache;
private boolean noStore;
private boolean cachePrivate;
private boolean mustRevalidate;
private boolean proxyRevalidate;
private boolean cachePublic;
private long staleWhileRevalidate = -1;
private long staleIfError = -1;
private Set<String> noCacheFields;
Builder() {
}
public long getMaxAge() {
return maxAge;
}
public Builder setMaxAge(final long maxAge) {
this.maxAge = maxAge;
return this;
}
public long getSharedMaxAge() {
return sharedMaxAge;
}
public Builder setSharedMaxAge(final long sharedMaxAge) {
this.sharedMaxAge = sharedMaxAge;
return this;
}
public boolean isNoCache() {
return noCache;
}
public Builder setNoCache(final boolean noCache) {
this.noCache = noCache;
return this;
}
public boolean isNoStore() {
return noStore;
}
public Builder setNoStore(final boolean noStore) {
this.noStore = noStore;
return this;
}
public boolean isCachePrivate() {
return cachePrivate;
}
public Builder setCachePrivate(final boolean cachePrivate) {
this.cachePrivate = cachePrivate;
return this;
}
public boolean isMustRevalidate() {
return mustRevalidate;
}
public Builder setMustRevalidate(final boolean mustRevalidate) {
this.mustRevalidate = mustRevalidate;
return this;
}
public boolean isProxyRevalidate() {
return proxyRevalidate;
}
public Builder setProxyRevalidate(final boolean proxyRevalidate) {
this.proxyRevalidate = proxyRevalidate;
return this;
}
public boolean isCachePublic() {
return cachePublic;
}
public Builder setCachePublic(final boolean cachePublic) {
this.cachePublic = cachePublic;
return this;
}
public long getStaleWhileRevalidate() {
return staleWhileRevalidate;
}
public Builder setStaleWhileRevalidate(final long staleWhileRevalidate) {
this.staleWhileRevalidate = staleWhileRevalidate;
return this;
}
public long getStaleIfError() {
return staleIfError;
}
public Builder setStaleIfError(final long staleIfError) {
this.staleIfError = staleIfError;
return this;
}
public Set<String> getNoCacheFields() {
return noCacheFields;
}
public Builder setNoCacheFields(final Set<String> noCacheFields) {
this.noCacheFields = noCacheFields;
return this;
}
public ResponseCacheControl build() {
return new ResponseCacheControl(maxAge, sharedMaxAge, mustRevalidate, noCache, noStore, cachePrivate, proxyRevalidate,
cachePublic, staleWhileRevalidate, staleIfError, noCacheFields);
}
}
}

View File

@ -166,7 +166,7 @@ class ResponseCachingPolicy {
* @param response The origin response
* @return {@code true} if response is cacheable
*/
public boolean isResponseCacheable(final String httpMethod, final HttpResponse response, final CacheControl cacheControl) {
public boolean isResponseCacheable(final String httpMethod, final HttpResponse response, final ResponseCacheControl cacheControl) {
boolean cacheable = false;
if (!HeaderConstants.GET_METHOD.equals(httpMethod) && !HeaderConstants.HEAD_METHOD.equals(httpMethod)
@ -285,7 +285,7 @@ class ResponseCachingPolicy {
* <p>
* When cacheControl is null, returns false, implying the response is cacheable.
*/
protected boolean isExplicitlyNonCacheable(final CacheControl cacheControl) {
protected boolean isExplicitlyNonCacheable(final ResponseCacheControl cacheControl) {
if (cacheControl == null) {
return false;
} else {
@ -313,7 +313,7 @@ class ResponseCachingPolicy {
return false;
}
protected boolean isExplicitlyCacheable(final HttpResponse response, final CacheControl cacheControl ) {
protected boolean isExplicitlyCacheable(final HttpResponse response, final ResponseCacheControl cacheControl ) {
if (response.getFirstHeader(HeaderConstants.EXPIRES) != null) {
return true;
}
@ -363,7 +363,7 @@ class ResponseCachingPolicy {
* @param response the {@link HttpResponse} from the origin
* @return {@code true} if response is cacheable
*/
public boolean isResponseCacheable(final HttpRequest request, final HttpResponse response, final CacheControl cacheControl) {
public boolean isResponseCacheable(final HttpRequest request, final HttpResponse response, final ResponseCacheControl cacheControl) {
final ProtocolVersion version = request.getVersion() != null ? request.getVersion() : HttpVersion.DEFAULT;
if (version.compareToVersion(HttpVersion.HTTP_1_1) > 0) {
if (LOG.isDebugEnabled()) {
@ -403,7 +403,7 @@ class ResponseCachingPolicy {
return isResponseCacheable(method, response, cacheControl);
}
private boolean expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(final HttpResponse response, final CacheControl cacheControl) {
private boolean expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(final HttpResponse response, final ResponseCacheControl cacheControl) {
if (cacheControl != null) {
return false;
}
@ -464,7 +464,7 @@ class ResponseCachingPolicy {
* @param response the HTTP response for which to calculate the freshness lifetime
* @return the freshness lifetime of the response, in seconds
*/
private Duration calculateFreshnessLifetime(final HttpResponse response, final CacheControl cacheControl) {
private Duration calculateFreshnessLifetime(final HttpResponse response, final ResponseCacheControl cacheControl) {
if (cacheControl == null) {
// If no cache-control header is present, assume no caching directives and return a default value
@ -516,7 +516,7 @@ class ResponseCachingPolicy {
final int statusCode = entry.getStatus();
if (statusCode >= HttpStatus.SC_INTERNAL_SERVER_ERROR && statusCode <= HttpStatus.SC_GATEWAY_TIMEOUT) {
// Check if the cached response has a stale-while-revalidate directive
final CacheControl cacheControl = parseCacheControlHeader(entry);
final ResponseCacheControl cacheControl = parseCacheControlHeader(entry);
if (cacheControl == null) {
return false;
} else {
@ -534,12 +534,12 @@ class ResponseCachingPolicy {
* @param messageHeaders the HTTP message to parse the header from
* @return a CacheControl instance with the parsed directives or default values if the header is not present
*/
private CacheControl parseCacheControlHeader(final MessageHeaders messageHeaders) {
private ResponseCacheControl parseCacheControlHeader(final MessageHeaders messageHeaders) {
final Iterator<Header> it = messageHeaders.headerIterator(HttpHeaders.CACHE_CONTROL);
if (it == null || !it.hasNext()) {
return null;
} else {
return CacheControlHeaderParser.INSTANCE.parse(it);
return CacheControlHeaderParser.INSTANCE.parseResponse(it);
}
}
@ -558,7 +558,7 @@ class ResponseCachingPolicy {
* @return true if revalidation is required based on the {@code no-cache} directive, {@code false} otherwise.
*/
boolean responseContainsNoCacheDirective(final HttpCacheEntry entry) {
final CacheControl responseCacheControl = parseCacheControlHeader(entry);
final ResponseCacheControl responseCacheControl = parseCacheControlHeader(entry);
if (responseCacheControl != null) {
final Set<String> noCacheFields = responseCacheControl.getNoCacheFields();

View File

@ -31,13 +31,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import java.util.Collections;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.message.BasicHeader;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.Collections;
public class CacheControlParserTest {
private final CacheControlHeaderParser parser = CacheControlHeaderParser.INSTANCE;
@ -45,69 +45,69 @@ public class CacheControlParserTest {
@Test
public void testParseMaxAgeZero() {
final Header header = new BasicHeader("Cache-Control", "max-age=0 , this = stuff;");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertEquals(0L, cacheControl.getMaxAge());
}
@Test
public void testParseSMaxAge() {
final Header header = new BasicHeader("Cache-Control", "s-maxage=3600");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertEquals(3600L, cacheControl.getSharedMaxAge());
}
@Test
public void testParseInvalidCacheValue() {
final Header header = new BasicHeader("Cache-Control", "max-age=invalid");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
assertEquals(-1L, cacheControl.getMaxAge());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertEquals(0L, cacheControl.getMaxAge());
}
@Test
public void testParseInvalidHeader() {
final Header header = new BasicHeader("Cache-Control", "max-age");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertEquals(-1L, cacheControl.getMaxAge());
}
@Test
public void testParseNullHeader() {
final Header header = null;
assertThrows(NullPointerException.class, () -> parser.parse(Collections.singletonList(header).iterator()));
assertThrows(NullPointerException.class, () -> parser.parseResponse(Collections.singletonList(header).iterator()));
}
@Test
public void testParseEmptyHeader() {
final Header header = new BasicHeader("Cache-Control", "");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertEquals(-1L, cacheControl.getMaxAge());
}
@Test
public void testParseCookieEmptyValue() {
final Header header = new BasicHeader("Cache-Control", "max-age=;");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final Header header = new BasicHeader("Cache-Control", "max-age=,");
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertEquals(-1L, cacheControl.getMaxAge());
}
@Test
public void testParseNoCache() {
final Header header = new BasicHeader(" Cache-Control", "no-cache");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertEquals(-1L, cacheControl.getMaxAge());
}
@Test
public void testParseNoDirective() {
final Header header = new BasicHeader(" Cache-Control", "");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertEquals(-1L, cacheControl.getMaxAge());
}
@Test
public void testGarbage() {
final Header header = new BasicHeader("Cache-Control", ",,= blah,");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertEquals(-1L, cacheControl.getMaxAge());
}
@ -115,7 +115,7 @@ public class CacheControlParserTest {
@Test
public void testParseMultipleDirectives() {
final Header header = new BasicHeader("Cache-Control", "max-age=604800, stale-while-revalidate=86400, s-maxage=3600, must-revalidate, private");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertAll("Must all pass",
() -> assertEquals(604800L, cacheControl.getMaxAge()),
@ -128,7 +128,7 @@ public class CacheControlParserTest {
@Test
public void testParseMultipleDirectives2() {
final Header header = new BasicHeader("Cache-Control", "max-age=604800, stale-while-revalidate=86400, must-revalidate, private, s-maxage=3600");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertAll("Must all pass",
() -> assertEquals(604800L, cacheControl.getMaxAge()),
@ -141,7 +141,7 @@ public class CacheControlParserTest {
@Test
public void testParsePublic() {
final Header header = new BasicHeader("Cache-Control", "public");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertTrue(cacheControl.isPublic());
}
@ -149,7 +149,7 @@ public class CacheControlParserTest {
@Test
public void testParsePrivate() {
final Header header = new BasicHeader("Cache-Control", "private");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertTrue(cacheControl.isCachePrivate());
}
@ -157,7 +157,7 @@ public class CacheControlParserTest {
@Test
public void testParseNoStore() {
final Header header = new BasicHeader("Cache-Control", "no-store");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertTrue(cacheControl.isNoStore());
}
@ -165,7 +165,7 @@ public class CacheControlParserTest {
@Test
public void testParseStaleWhileRevalidate() {
final Header header = new BasicHeader("Cache-Control", "max-age=3600, stale-while-revalidate=120");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertEquals(120, cacheControl.getStaleWhileRevalidate());
}
@ -173,7 +173,7 @@ public class CacheControlParserTest {
@Test
public void testParseNoCacheFields() {
final Header header = new BasicHeader("Cache-Control", "no-cache=\"Set-Cookie, Content-Language\", stale-while-revalidate=120");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertTrue(cacheControl.isNoCache());
assertEquals(2, cacheControl.getNoCacheFields().size());
@ -185,7 +185,7 @@ public class CacheControlParserTest {
@Test
public void testParseNoCacheFieldsNoQuote() {
final Header header = new BasicHeader("Cache-Control", "no-cache=Set-Cookie, Content-Language, stale-while-revalidate=120");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertTrue(cacheControl.isNoCache());
assertEquals(1, cacheControl.getNoCacheFields().size());
@ -196,7 +196,7 @@ public class CacheControlParserTest {
@Test
public void testParseNoCacheFieldsMessy() {
final Header header = new BasicHeader("Cache-Control", "no-cache=\" , , ,,, Set-Cookie , , Content-Language , \", stale-while-revalidate=120");
final CacheControl cacheControl = parser.parse(Collections.singletonList(header).iterator());
final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator());
assertTrue(cacheControl.isNoCache());
assertEquals(2, cacheControl.getNoCacheFields().size());
@ -217,8 +217,8 @@ public class CacheControlParserTest {
final Header header6 = new BasicHeader("Cache-Control", "must-revalidate");
// Parse headers
final CacheControl cacheControl1 = parser.parse(Arrays.asList(header1, header2).iterator());
final CacheControl cacheControl2 = parser.parse(Arrays.asList(header3, header4, header5, header6).iterator());
final ResponseCacheControl cacheControl1 = parser.parseResponse(Arrays.asList(header1, header2).iterator());
final ResponseCacheControl cacheControl2 = parser.parseResponse(Arrays.asList(header3, header4, header5, header6).iterator());
// Validate Cache-Control directives
assertEquals(cacheControl1.getMaxAge(), cacheControl2.getMaxAge());
@ -226,4 +226,21 @@ public class CacheControlParserTest {
assertEquals(cacheControl1.isCachePrivate(), cacheControl2.isCachePrivate());
assertEquals(cacheControl1.isMustRevalidate(), cacheControl2.isMustRevalidate());
}
@Test
public void testParseRequestMultipleDirectives() {
final Header header = new BasicHeader("Cache-Control", "blah, max-age=1111, max-stale=2222, " +
"min-fresh=3333, no-cache, no-store, no-cache, no-stuff, only-if-cached, only-if-cached-or-maybe-not");
final RequestCacheControl cacheControl = parser.parseRequest(Collections.singletonList(header).iterator());
assertAll("Must all pass",
() -> assertEquals(1111L, cacheControl.getMaxAge()),
() -> assertEquals(2222L, cacheControl.getMaxStale()),
() -> assertEquals(3333L, cacheControl.getMinFresh()),
() -> assertTrue(cacheControl.isNoCache()),
() -> assertTrue(cacheControl.isNoCache()),
() -> assertTrue(cacheControl.isOnlyIfCached())
);
}
}

View File

@ -227,7 +227,7 @@ public class TestResponseCachingPolicy {
final int status = getRandomStatus();
response.setCode(status);
response.setHeader("Cache-Control", "max-age=boom");
Assertions.assertTrue(policy.isResponseCacheable("GET", response));
Assertions.assertFalse(policy.isResponseCacheable("GET", response));
}