You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2023/05/15 20:44:28 UTC
[httpcomponents-client] branch cache_control_improvements created (now 0091d59ac)
This is an automated email from the ASF dual-hosted git repository.
olegk pushed a change to branch cache_control_improvements
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git
at 0091d59ac Support for request Cache-Control
This branch includes the following new commits:
new 0091d59ac Support for request Cache-Control
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
[httpcomponents-client] 01/01: Support for request Cache-Control
Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
olegk pushed a commit to branch cache_control_improvements
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git
commit 0091d59aca43d83ac312eda7b7dd637560151e58
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Mon May 15 22:18:17 2023 +0200
Support for request Cache-Control
---
.../hc/client5/http/cache/HeaderConstants.java | 21 +++
.../hc/client5/http/impl/cache/CacheControl.java | 179 ++++++++++++++----
.../http/impl/cache/CacheControlHeaderParser.java | 155 +++++++++-------
.../http/impl/cache/RequestCacheControl.java | 206 +++++++++++++++++++++
.../http/impl/cache/CacheControlParserTest.java | 23 ++-
5 files changed, 476 insertions(+), 108 deletions(-)
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HeaderConstants.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HeaderConstants.java
index e45c07206..f3a86017e 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HeaderConstants.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HeaderConstants.java
@@ -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";
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControl.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControl.java
index dadd80152..dc7a78e91 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControl.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControl.java
@@ -27,13 +27,13 @@
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;
-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
* the response can be cached by the client and intermediary proxies.
@@ -45,14 +45,7 @@ import java.util.Set;
* 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
@@ -96,26 +89,18 @@ final class CacheControl {
* 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.
+ * The number of seconds that a cached stale response MAY be used to satisfy the request,
+ * regardless of other freshness information..
*/
- private final Set<String> noCacheFields;
-
-
+ private final long staleIfError;
/**
- * 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.
+ * A set of field names specified in the "no-cache" directive of the Cache-Control header.
*/
- public CacheControl() {
- this(-1, -1, false, false, false, false, false, false,-1, Collections.emptySet());
- }
+ private final Set<String> noCacheFields;
/**
- * 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.
+ * 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.
@@ -125,11 +110,13 @@ final class CacheControl {
* @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.
+ * @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.
*/
- 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,
+ 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 long staleIfError,
final Set<String> noCacheFields) {
this.maxAge = maxAge;
this.sharedMaxAge = sharedMaxAge;
@@ -140,10 +127,10 @@ final class CacheControl {
this.proxyRevalidate = proxyRevalidate;
this.cachePublic = cachePublic;
this.staleWhileRevalidate = staleWhileRevalidate;
- this.noCacheFields = Collections.unmodifiableSet(noCacheFields);
+ this.staleIfError = staleIfError;
+ this.noCacheFields = noCacheFields != null ? Collections.unmodifiableSet(noCacheFields) : Collections.emptySet();
}
-
/**
* Returns the max-age value from the Cache-Control header.
*
@@ -226,6 +213,15 @@ final class CacheControl {
return staleWhileRevalidate;
}
+ /**
+ * Returns the stale-if-error value from the Cache-Control header.
+ *
+ * @return The stale-if-error value.
+ */
+ public long getStaleIfError() {
+ return staleIfError;
+ }
+
/**
* Returns an unmodifiable set of field names specified in the "no-cache" directive of the Cache-Control header.
*
@@ -235,25 +231,130 @@ final class CacheControl {
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 +
+ ", staleWhileRevalidate=" + staleWhileRevalidate +
+ ", staleIfError=" + staleIfError +
+ ", noCacheFields=" + noCacheFields +
'}';
}
+
+ 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;
+
+ public long getMaxAge() {
+ return maxAge;
+ }
+
+ public void setMaxAge(final long maxAge) {
+ this.maxAge = maxAge;
+ }
+
+ public long getSharedMaxAge() {
+ return sharedMaxAge;
+ }
+
+ public void setSharedMaxAge(final long sharedMaxAge) {
+ this.sharedMaxAge = sharedMaxAge;
+ }
+
+ public boolean isNoCache() {
+ return noCache;
+ }
+
+ public void setNoCache(final boolean noCache) {
+ this.noCache = noCache;
+ }
+
+ public boolean isNoStore() {
+ return noStore;
+ }
+
+ public void setNoStore(final boolean noStore) {
+ this.noStore = noStore;
+ }
+
+ public boolean isCachePrivate() {
+ return cachePrivate;
+ }
+
+ public void setCachePrivate(final boolean cachePrivate) {
+ this.cachePrivate = cachePrivate;
+ }
+
+ public boolean isMustRevalidate() {
+ return mustRevalidate;
+ }
+
+ public void setMustRevalidate(final boolean mustRevalidate) {
+ this.mustRevalidate = mustRevalidate;
+ }
+
+ public boolean isProxyRevalidate() {
+ return proxyRevalidate;
+ }
+
+ public void setProxyRevalidate(final boolean proxyRevalidate) {
+ this.proxyRevalidate = proxyRevalidate;
+ }
+
+ public boolean isCachePublic() {
+ return cachePublic;
+ }
+
+ public void setCachePublic(final boolean cachePublic) {
+ this.cachePublic = cachePublic;
+ }
+
+ public long getStaleWhileRevalidate() {
+ return staleWhileRevalidate;
+ }
+
+ public void setStaleWhileRevalidate(final long staleWhileRevalidate) {
+ this.staleWhileRevalidate = staleWhileRevalidate;
+ }
+
+ public long getStaleIfError() {
+ return staleIfError;
+ }
+
+ public void setStaleIfError(final long staleIfError) {
+ this.staleIfError = staleIfError;
+ }
+
+ public Set<String> getNoCacheFields() {
+ return noCacheFields;
+ }
+
+ public void setNoCacheFields(final Set<String> noCacheFields) {
+ this.noCacheFields = noCacheFields;
+ }
+
+ public CacheControl build() {
+ return new CacheControl(maxAge, sharedMaxAge, mustRevalidate, noCache, noStore, cachePrivate, proxyRevalidate,
+ cachePublic, staleWhileRevalidate, staleIfError, noCacheFields);
+ }
+
+ }
+
}
\ No newline at end of file
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java
index 03aca745f..3adad6845 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java
@@ -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;
@@ -102,39 +103,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
+ void parse(final Iterator<Header> headerIterator, final BiConsumer<String, String> consumer) {
while (headerIterator.hasNext()) {
final Header header = headerIterator.next();
final CharArrayBuffer buffer;
@@ -145,7 +114,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,42 +135,96 @@ 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);
- }
+ consumer.accept(name, value);
+ }
+ }
+ }
+
+ /**
+ * Parses the specified response header and returns a new {@link CacheControl} instance containing
+ * the relevant caching 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
+ * directives are malformed, the returned {@link CacheControl} 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 CacheControl} instance containing the relevant caching directives parsed
+ * from the response header
+ * @throws IllegalArgumentException if the input header is {@code null}
+ */
+ public final CacheControl parse(final Iterator<Header> headerIterator) {
+ Args.notNull(headerIterator, "headerIterator");
+ final CacheControl.Builder builder = new CacheControl.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);
}
}
- } 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);
+ 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 a single CacheControl object with the combined directives
- return new CacheControl(maxAge, sharedMaxAge, mustRevalidate, noCache, noStore, cachePrivate, proxyRevalidate, cachePublic, staleWhileRevalidate, noCacheFields);
+ });
+ return builder.build();
+ }
+
+ /**
+ * 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 CacheControl} instance containing the relevant caching directives parsed
+ * from the response 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);
+ }
+ });
+ return builder.build();
}
private static long parseSeconds(final String name, final String value) {
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/RequestCacheControl.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/RequestCacheControl.java
new file mode 100644
index 000000000..956c98f3e
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/RequestCacheControl.java
@@ -0,0 +1,206 @@
+/*
+ * ====================================================================
+ * 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 {
+
+ private final long maxAge;
+ private final long maxStale;
+ private final long minFresh;
+ private final boolean noCache;
+ private final boolean noStore;
+ private final boolean onlyIfCached;
+
+ /**
+ * Creates a new instance of {@code RequestCacheControl} with default values.
+ */
+ public RequestCacheControl() {
+ this(-1, -1, -1, false, false, false);
+ }
+
+ /**
+ * Creates a new instance of {@code RequestCacheControl} with the specified values.
+ *
+ * @param maxAge The max-age value from the Cache-Control header.
+ * @param maxStale The max-stale value from the Cache-Control header.
+ * @param minFresh The min-fresh 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 onlyIfCached The only-if-cached value from the Cache-Control header.
+ */
+ public RequestCacheControl(final long maxAge, final long maxStale, final long minFresh, final boolean noCache,
+ final boolean noStore, final boolean onlyIfCached) {
+ this.maxAge = maxAge;
+ this.maxStale = maxStale;
+ this.minFresh = minFresh;
+ this.noCache = noCache;
+ this.noStore = noStore;
+ this.onlyIfCached = onlyIfCached;
+ }
+
+ /**
+ * Returns the max-age value from the Cache-Control header.
+ *
+ * @return The max-age value.
+ */
+ 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.
+ */
+ public boolean isNoCache() {
+ return noCache;
+ }
+
+ /**
+ * Returns the no-store flag from the Cache-Control header.
+ *
+ * @return The no-store flag.
+ */
+ 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;
+ }
+
+ @Override
+ public String toString() {
+ return "RequestCacheControl{" +
+ "maxAge=" + maxAge +
+ ", maxStale=" + maxStale +
+ ", minFresh=" + minFresh +
+ ", noCache=" + noCache +
+ ", noStore=" + noStore +
+ ", onlyIfCached=" + onlyIfCached +
+ '}';
+ }
+
+ static class Builder {
+
+ private long maxAge;
+ private long maxStale;
+ private long minFresh;
+ private boolean noCache;
+ private boolean noStore;
+ private boolean onlyIfCached;
+
+ public long getMaxAge() {
+ return maxAge;
+ }
+
+ public void setMaxAge(final long maxAge) {
+ this.maxAge = maxAge;
+ }
+
+ public long getMaxStale() {
+ return maxStale;
+ }
+
+ public void setMaxStale(final long maxStale) {
+ this.maxStale = maxStale;
+ }
+
+ public long getMinFresh() {
+ return minFresh;
+ }
+
+ public void setMinFresh(final long minFresh) {
+ this.minFresh = minFresh;
+ }
+
+ public boolean isNoCache() {
+ return noCache;
+ }
+
+ public void setNoCache(final boolean noCache) {
+ this.noCache = noCache;
+ }
+
+ public boolean isNoStore() {
+ return noStore;
+ }
+
+ public void setNoStore(final boolean noStore) {
+ this.noStore = noStore;
+ }
+
+ public boolean isOnlyIfCached() {
+ return onlyIfCached;
+ }
+
+ public void setOnlyIfCached(final boolean onlyIfCached) {
+ this.onlyIfCached = onlyIfCached;
+ }
+
+ public RequestCacheControl build() {
+ return new RequestCacheControl(maxAge, maxStale, minFresh, noCache, noStore, onlyIfCached);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/CacheControlParserTest.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/CacheControlParserTest.java
index 1280d4ea5..9c0b60a8a 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/CacheControlParserTest.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/CacheControlParserTest.java
@@ -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;
@@ -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())
+ );
+ }
+
}