diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheContextBuilder.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheContextBuilder.java
new file mode 100644
index 000000000..85f1ab952
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheContextBuilder.java
@@ -0,0 +1,117 @@
+/*
+ * ====================================================================
+ * 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
+ * .
+ *
+ */
+package org.apache.hc.client5.http.cache;
+
+import org.apache.hc.client5.http.AbstractClientContextBuilder;
+import org.apache.hc.client5.http.SchemePortResolver;
+import org.apache.hc.client5.http.auth.AuthCache;
+import org.apache.hc.client5.http.auth.AuthScheme;
+import org.apache.hc.client5.http.auth.AuthSchemeFactory;
+import org.apache.hc.client5.http.auth.CredentialsProvider;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.http.cookie.CookieSpecFactory;
+import org.apache.hc.client5.http.cookie.CookieStore;
+import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.config.Lookup;
+
+public class CacheContextBuilder extends AbstractClientContextBuilder {
+
+ public static CacheContextBuilder create(final SchemePortResolver schemePortResolver) {
+ return new CacheContextBuilder(schemePortResolver);
+ }
+
+ public static CacheContextBuilder create() {
+ return new CacheContextBuilder(DefaultSchemePortResolver.INSTANCE);
+ }
+
+ private RequestCacheControl cacheControl;
+
+ protected CacheContextBuilder(final SchemePortResolver schemePortResolver) {
+ super(schemePortResolver);
+ }
+
+ @Override
+ public CacheContextBuilder useCookieSpecRegistry(final Lookup cookieSpecRegistry) {
+ super.useCookieSpecRegistry(cookieSpecRegistry);
+ return this;
+ }
+
+ @Override
+ public CacheContextBuilder useAuthSchemeRegistry(final Lookup authSchemeRegistry) {
+ super.useAuthSchemeRegistry(authSchemeRegistry);
+ return this;
+ }
+
+ @Override
+ public CacheContextBuilder useCookieStore(final CookieStore cookieStore) {
+ super.useCookieStore(cookieStore);
+ return this;
+ }
+
+ @Override
+ public CacheContextBuilder useCredentialsProvider(final CredentialsProvider credentialsProvider) {
+ super.useCredentialsProvider(credentialsProvider);
+ return this;
+ }
+
+ @Override
+ public CacheContextBuilder useAuthCache(final AuthCache authCache) {
+ super.useAuthCache(authCache);
+ return this;
+ }
+
+ @Override
+ public CacheContextBuilder preemptiveAuth(final HttpHost host, final AuthScheme authScheme) {
+ super.preemptiveAuth(host, authScheme);
+ return this;
+ }
+
+ @Override
+ public CacheContextBuilder preemptiveBasicAuth(final HttpHost host, final UsernamePasswordCredentials credentials) {
+ super.preemptiveBasicAuth(host, credentials);
+ return this;
+ }
+
+ public CacheContextBuilder setCacheControl(final RequestCacheControl cacheControl) {
+ this.cacheControl = cacheControl;
+ return this;
+ }
+
+ @Override
+ protected HttpCacheContext createContext() {
+ return HttpCacheContext.create();
+ }
+
+ @Override
+ public HttpCacheContext build() {
+ final HttpCacheContext context = super.build();
+ context.setRequestCacheControl(cacheControl);
+ return context;
+ }
+
+}
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheContext.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheContext.java
index 435bc8618..ba6976977 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheContext.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheContext.java
@@ -45,6 +45,21 @@ public class HttpCacheContext extends HttpClientContext {
*/
public static final String CACHE_RESPONSE_STATUS = "http.cache.response.status";
+ /**
+ * @since 5.4
+ */
+ public static final String CACHE_ENTRY = "http.cache.entry";
+
+ /**
+ * @since 5.4
+ */
+ public static final String CACHE_REQUEST_CONTROL = "http.cache.request.control";
+
+ /**
+ * @since 5.4
+ */
+ public static final String CACHE_RESPONSE_CONTROL = "http.cache.response.control";
+
public static HttpCacheContext adapt(final HttpContext context) {
if (context instanceof HttpCacheContext) {
return (HttpCacheContext) context;
@@ -69,4 +84,55 @@ public class HttpCacheContext extends HttpClientContext {
return getAttribute(CACHE_RESPONSE_STATUS, CacheResponseStatus.class);
}
+ /**
+ * @since 5.4
+ */
+ public void setCacheResponseStatus(final CacheResponseStatus status) {
+ setAttribute(CACHE_RESPONSE_STATUS, status);
+ }
+
+ /**
+ * @since 5.4
+ */
+ public RequestCacheControl getRequestCacheControl() {
+ final RequestCacheControl cacheControl = getAttribute(CACHE_REQUEST_CONTROL, RequestCacheControl.class);
+ return cacheControl != null ? cacheControl : RequestCacheControl.DEFAULT;
+ }
+
+ /**
+ * @since 5.4
+ */
+ public void setRequestCacheControl(final RequestCacheControl requestCacheControl) {
+ setAttribute(CACHE_REQUEST_CONTROL, requestCacheControl);
+ }
+
+ /**
+ * @since 5.4
+ */
+ public ResponseCacheControl getResponseCacheControl() {
+ final ResponseCacheControl cacheControl = getAttribute(CACHE_RESPONSE_CONTROL, ResponseCacheControl.class);
+ return cacheControl != null ? cacheControl : ResponseCacheControl.DEFAULT;
+ }
+
+ /**
+ * @since 5.4
+ */
+ public void setResponseCacheControl(final ResponseCacheControl responseCacheControl) {
+ setAttribute(CACHE_RESPONSE_CONTROL, responseCacheControl);
+ }
+
+ /**
+ * @since 5.4
+ */
+ public HttpCacheEntry getCacheEntry() {
+ return getAttribute(CACHE_ENTRY, HttpCacheEntry.class);
+ }
+
+ /**
+ * @since 5.4
+ */
+ public void setCacheEntry(final HttpCacheEntry entry) {
+ setAttribute(CACHE_ENTRY, entry);
+ }
+
}
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/RequestCacheControl.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/RequestCacheControl.java
index f6ea444ee..54c85b4e4 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/RequestCacheControl.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/RequestCacheControl.java
@@ -47,15 +47,8 @@ public final class RequestCacheControl implements CacheControl {
private final boolean onlyIfCached;
private final long staleIfError;
- /**
- * Flag for the 'no-transform' Cache-Control directive.
- * If this field is true, then the 'no-transform' directive is present in the Cache-Control header.
- * According to RFC 'no-transform' directive indicates that the cache MUST NOT transform the payload.
- */
- private final boolean noTransform;
-
RequestCacheControl(final long maxAge, final long maxStale, final long minFresh, final boolean noCache,
- final boolean noStore, final boolean onlyIfCached, final long staleIfError, final boolean noTransform) {
+ final boolean noStore, final boolean onlyIfCached, final long staleIfError) {
this.maxAge = maxAge;
this.maxStale = maxStale;
this.minFresh = minFresh;
@@ -63,7 +56,6 @@ public final class RequestCacheControl implements CacheControl {
this.noStore = noStore;
this.onlyIfCached = onlyIfCached;
this.staleIfError = staleIfError;
- this.noTransform = noTransform;
}
/**
@@ -144,7 +136,7 @@ public final class RequestCacheControl implements CacheControl {
buf.append("max-stale=").append(maxStale).append(",");
}
if (minFresh >= 0) {
- buf.append("max-fresh=").append(minFresh).append(",");
+ buf.append("min-fresh=").append(minFresh).append(",");
}
if (noCache) {
buf.append("no-cache").append(",");
@@ -158,9 +150,6 @@ public final class RequestCacheControl implements CacheControl {
if (staleIfError >= 0) {
buf.append("stale-if-error").append(staleIfError).append(",");
}
- if (noTransform) {
- buf.append("no-transform").append(",");
- }
if (buf.charAt(buf.length() - 1) == ',') {
buf.setLength(buf.length() - 1);
}
@@ -172,6 +161,8 @@ public final class RequestCacheControl implements CacheControl {
return new Builder();
}
+ public static final RequestCacheControl DEFAULT = builder().build();
+
public static class Builder {
private long maxAge = -1;
@@ -181,7 +172,6 @@ public final class RequestCacheControl implements CacheControl {
private boolean noStore;
private boolean onlyIfCached;
private long staleIfError = -1;
- private boolean noTransform;
Builder() {
}
@@ -249,18 +239,8 @@ public final class RequestCacheControl implements CacheControl {
return this;
}
- public boolean isNoTransform() {
- return noTransform;
- }
-
- public Builder setNoTransform(final boolean noTransform) {
- this.noTransform = noTransform;
- return this;
- }
-
-
public RequestCacheControl build() {
- return new RequestCacheControl(maxAge, maxStale, minFresh, noCache, noStore, onlyIfCached, staleIfError, noTransform);
+ return new RequestCacheControl(maxAge, maxStale, minFresh, noCache, noStore, onlyIfCached, staleIfError);
}
}
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/ResponseCacheControl.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/ResponseCacheControl.java
index c9b71a066..7e7f4b01f 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/ResponseCacheControl.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/ResponseCacheControl.java
@@ -341,6 +341,8 @@ public final class ResponseCacheControl implements CacheControl {
return new Builder();
}
+ public static final ResponseCacheControl DEFAULT = builder().build();
+
public static class Builder {
private long maxAge = -1;
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
index 428b8c9e2..8386558a0 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
@@ -239,7 +239,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
final String exchangeId = scope.exchangeId;
- final HttpClientContext context = scope.clientContext;
+ final HttpCacheContext context = HttpCacheContext.adapt(scope.clientContext);
final CancellableDependency operation = scope.cancellableDependency;
if (LOG.isDebugEnabled()) {
@@ -247,6 +247,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
}
context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MISS);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, null);
if (clientRequestsOurOptions(request)) {
context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
@@ -254,7 +255,15 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
return;
}
- final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
+ final RequestCacheControl requestCacheControl;
+ if (request.containsHeader(HttpHeaders.CACHE_CONTROL)) {
+ requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
+ context.setRequestCacheControl(requestCacheControl);
+ } else {
+ requestCacheControl = context.getRequestCacheControl();
+ CacheControlHeaderGenerator.INSTANCE.generate(requestCacheControl, request);
+ }
+
if (LOG.isDebugEnabled()) {
LOG.debug("{} request cache control: {}", exchangeId, requestCacheControl);
}
@@ -273,6 +282,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
if (LOG.isDebugEnabled()) {
LOG.debug("{} response cache control: {}", exchangeId, responseCacheControl);
}
+ context.setResponseCacheControl(responseCacheControl);
handleCacheHit(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
}
}
@@ -540,6 +550,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
void triggerNewCacheEntryResponse(final HttpResponse backendResponse, final Instant responseDate, final ByteArrayBuffer buffer) {
final String exchangeId = scope.exchangeId;
+ final HttpClientContext context = scope.clientContext;
final CancellableDependency operation = scope.cancellableDependency;
operation.setDependency(responseCache.store(
target,
@@ -557,6 +568,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
}
try {
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} catch (final ResourceIOException ex) {
asyncExecCallback.failed(ex);
@@ -578,8 +590,10 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
}
void triggerCachedResponse(final HttpCacheEntry entry) {
+ final HttpClientContext context = scope.clientContext;
try {
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, entry);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, entry);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} catch (final ResourceIOException ex) {
asyncExecCallback.failed(ex);
@@ -731,6 +745,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
}
try {
final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, now);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} catch (final ResourceIOException ex) {
if (requestCacheControl.isOnlyIfCached()) {
@@ -802,7 +817,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
asyncExecCallback,
c -> revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, fork, chain, c));
context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
- final SimpleHttpResponse cacheResponse = unvalidatedCacheHit(request, hit.entry);
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} catch (final IOException ex) {
asyncExecCallback.failed(ex);
@@ -861,6 +877,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
public void completed(final CacheHit updated) {
try {
final SimpleHttpResponse cacheResponse = generateCachedResponse(request, updated.entry, responseDate);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} catch (final ResourceIOException ex) {
asyncExecCallback.failed(ex);
@@ -1080,7 +1097,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
if (response == null) {
try {
context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
- final SimpleHttpResponse cacheResponse = unvalidatedCacheHit(request, hit.entry);
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} catch (final IOException ex) {
asyncExecCallback.failed(ex);
@@ -1104,7 +1122,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
LOG.debug("{} serving stale response due to IOException and stale-if-error enabled", exchangeId);
}
try {
- final SimpleHttpResponse cacheResponse = unvalidatedCacheHit(request, hit.entry);
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} catch (final IOException ex) {
asyncExecCallback.failed(cause);
@@ -1219,16 +1238,12 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
@Override
public void completed(final CacheHit hit) {
- if (shouldSendNotModifiedResponse(request, hit.entry, Instant.now())) {
- final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(hit.entry);
+ try {
+ final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, responseDate);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
triggerResponse(cacheResponse, scope, asyncExecCallback);
- } else {
- try {
- final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
- triggerResponse(cacheResponse, scope, asyncExecCallback);
- } catch (final ResourceIOException ex) {
- asyncExecCallback.failed(ex);
- }
+ } catch (final ResourceIOException ex) {
+ asyncExecCallback.failed(ex);
}
}
@@ -1249,6 +1264,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
public AsyncDataConsumer handleResponse(
final HttpResponse backendResponse,
final EntityDetails entityDetails) throws HttpException, IOException {
+ final HttpClientContext context = scope.clientContext;
final Instant responseDate = getCurrentDate();
final AsyncExecCallback callback;
if (backendResponse.getCode() != HttpStatus.SC_NOT_MODIFIED) {
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderGenerator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderGenerator.java
new file mode 100644
index 000000000..360908be0
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderGenerator.java
@@ -0,0 +1,106 @@
+/*
+ * ====================================================================
+ * 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
+ * .
+ *
+ */
+package org.apache.hc.client5.http.impl.cache;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.hc.client5.http.cache.RequestCacheControl;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpMessage;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.message.BasicHeader;
+import org.apache.hc.core5.http.message.BasicHeaderValueFormatter;
+import org.apache.hc.core5.http.message.BufferedHeader;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.CharArrayBuffer;
+
+@Internal
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+class CacheControlHeaderGenerator {
+
+ public static final CacheControlHeaderGenerator INSTANCE = new CacheControlHeaderGenerator();
+
+ public List convert(final RequestCacheControl cacheControl) {
+ Args.notNull(cacheControl, "Cache control");
+ final List params = new ArrayList<>(10);
+ if (cacheControl.getMaxAge() >= 0) {
+ params.add(new BasicHeader("max-age", cacheControl.getMaxAge()));
+ }
+ if (cacheControl.getMaxStale() >= 0) {
+ params.add(new BasicHeader("max-stale", cacheControl.getMaxStale()));
+ }
+ if (cacheControl.getMinFresh() >= 0) {
+ params.add(new BasicHeader("min-fresh", cacheControl.getMinFresh()));
+ }
+ if (cacheControl.isNoCache()) {
+ params.add(new BasicHeader("no-cache", null));
+ }
+ if (cacheControl.isNoStore()) {
+ params.add(new BasicHeader("no-store", null));
+ }
+ if (cacheControl.isOnlyIfCached()) {
+ params.add(new BasicHeader("only-if-cached", null));
+ }
+ if (cacheControl.getStaleIfError() >= 0) {
+ params.add(new BasicHeader("stale-if-error", cacheControl.getStaleIfError()));
+ }
+ return params;
+ }
+
+ public Header generate(final RequestCacheControl cacheControl) {
+ final List params = convert(cacheControl);
+ if (!params.isEmpty()) {
+ final CharArrayBuffer buf = new CharArrayBuffer(1024);
+ buf.append(HttpHeaders.CACHE_CONTROL);
+ buf.append(": ");
+ for (int i = 0; i < params.size(); i++) {
+ if (i > 0) {
+ buf.append(", ");
+ }
+ BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(buf, params.get(i), false);
+ }
+ return BufferedHeader.create(buf);
+ } else {
+ return null;
+ }
+ }
+
+ public void generate(final RequestCacheControl cacheControl, final HttpMessage message) {
+ final Header h = generate(cacheControl);
+ if (h != null) {
+ message.addHeader(h);
+ }
+ }
+
+}
+
+
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 79804907b..5d7b6c2d2 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
@@ -70,7 +70,7 @@ import org.slf4j.LoggerFactory;
*
*/
@Internal
-@Contract(threading = ThreadingBehavior.SAFE)
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
class CacheControlHeaderParser {
/**
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
index 322cc1ef8..6ce584945 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
@@ -145,19 +145,28 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
final ExecChain.Scope scope,
final ExecChain chain) throws IOException, HttpException {
final String exchangeId = scope.exchangeId;
- final HttpClientContext context = scope.clientContext;
+ final HttpCacheContext context = HttpCacheContext.adapt(scope.clientContext);
if (LOG.isDebugEnabled()) {
LOG.debug("{} request via cache: {}", exchangeId, new RequestLine(request));
}
context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MISS);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, null);
if (clientRequestsOurOptions(request)) {
context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
return new BasicClassicHttpResponse(HttpStatus.SC_NOT_IMPLEMENTED);
}
- final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
+
+ final RequestCacheControl requestCacheControl;
+ if (request.containsHeader(HttpHeaders.CACHE_CONTROL)) {
+ requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
+ } else {
+ requestCacheControl = context.getRequestCacheControl();
+ CacheControlHeaderGenerator.INSTANCE.generate(requestCacheControl, request);
+ }
+
if (LOG.isDebugEnabled()) {
LOG.debug("Request cache control: {}", requestCacheControl);
}
@@ -176,6 +185,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
return handleCacheMiss(requestCacheControl, root, target, request, scope, chain);
} else {
final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(hit.entry);
+ context.setResponseCacheControl(responseCacheControl);
if (LOG.isDebugEnabled()) {
LOG.debug("{} response cache control: {}", exchangeId, responseCacheControl);
}
@@ -220,7 +230,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
}
final ClassicHttpResponse backendResponse = chain.proceed(request, scope);
try {
- return handleBackendResponse(exchangeId, target, request, requestDate, getCurrentDate(), backendResponse);
+ return handleBackendResponse(target, request, scope, requestDate, getCurrentDate(), backendResponse);
} catch (final IOException | RuntimeException ex) {
backendResponse.close();
throw ex;
@@ -256,7 +266,9 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
LOG.debug("{} cache hit is fresh enough", exchangeId);
}
try {
- return convert(generateCachedResponse(request, hit.entry, now));
+ final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, now);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+ return convert(cacheResponse);
} catch (final ResourceIOException ex) {
if (requestCacheControl.isOnlyIfCached()) {
if (LOG.isDebugEnabled()) {
@@ -315,7 +327,9 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
hit.getEntryKey(),
() -> revalidateCacheEntry(responseCacheControl, hit, target, request, fork, chain));
context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
- return convert(unvalidatedCacheHit(request, hit.entry));
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+ return convert(cacheResponse);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("{} revalidating stale cache entry (asynchronous revalidation disabled)", exchangeId);
@@ -368,9 +382,11 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
}
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
final CacheHit updated = responseCache.update(hit, target, request, backendResponse, requestDate, responseDate);
- return convert(generateCachedResponse(request, updated.entry, responseDate));
+ final SimpleHttpResponse cacheResponse = generateCachedResponse(request, updated.entry, responseDate);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, updated.entry);
+ return convert(cacheResponse);
}
- return handleBackendResponse(scope.exchangeId, target, conditionalRequest, requestDate, responseDate, backendResponse);
+ return handleBackendResponse(target, conditionalRequest, scope, requestDate, responseDate, backendResponse);
} catch (final IOException | RuntimeException ex) {
backendResponse.close();
throw ex;
@@ -419,7 +435,9 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
if (LOG.isDebugEnabled()) {
LOG.debug("{} serving stale response due to IOException and stale-if-error enabled", exchangeId);
}
- return convert(unvalidatedCacheHit(request, hit.entry));
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+ return convert(cacheResponse);
} else {
return convert(generateGatewayTimeout());
}
@@ -432,19 +450,21 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
}
EntityUtils.consume(response.getEntity());
context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
- return convert(unvalidatedCacheHit(request, hit.entry));
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+ return convert(cacheResponse);
}
return response;
}
ClassicHttpResponse handleBackendResponse(
- final String exchangeId,
final HttpHost target,
final ClassicHttpRequest request,
+ final ExecChain.Scope scope,
final Instant requestDate,
final Instant responseDate,
final ClassicHttpResponse backendResponse) throws IOException {
-
+ final String exchangeId = scope.exchangeId;
responseCache.evictInvalidatedEntries(target, request, backendResponse);
if (isResponseTooBig(backendResponse.getEntity())) {
if (LOG.isDebugEnabled()) {
@@ -459,7 +479,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
if (LOG.isDebugEnabled()) {
LOG.debug("{} caching backend response", exchangeId);
}
- return cacheAndReturnResponse(exchangeId, target, request, backendResponse, requestDate, responseDate);
+ return cacheAndReturnResponse(target, request, scope, backendResponse, requestDate, responseDate);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("{} backend response is not cacheable", exchangeId);
@@ -469,12 +489,14 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
}
ClassicHttpResponse cacheAndReturnResponse(
- final String exchangeId,
final HttpHost target,
final HttpRequest request,
+ final ExecChain.Scope scope,
final ClassicHttpResponse backendResponse,
final Instant requestSent,
final Instant responseReceived) throws IOException {
+ final String exchangeId = scope.exchangeId;
+ final HttpClientContext context = scope.clientContext;
final int statusCode = backendResponse.getCode();
// handle 304 Not Modified responses
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
@@ -488,7 +510,9 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
backendResponse,
requestSent,
responseReceived);
- return convert(responseGenerator.generateResponse(request, updated.entry));
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updated.entry);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+ return convert(cacheResponse);
}
}
@@ -536,7 +560,9 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
LOG.debug("{} backend response successfully cached (freshness check skipped)", exchangeId);
}
}
- return convert(responseGenerator.generateResponse(request, hit.entry));
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+ return convert(cacheResponse);
}
private ClassicHttpResponse handleCacheMiss(
@@ -597,7 +623,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
final Instant responseDate = getCurrentDate();
if (backendResponse.getCode() != HttpStatus.SC_NOT_MODIFIED) {
- return handleBackendResponse(exchangeId, target, request, requestDate, responseDate, backendResponse);
+ return handleBackendResponse(target, request, scope, requestDate, responseDate, backendResponse);
} else {
// 304 response are not expected to have an enclosed content body, but still
backendResponse.close();
@@ -630,11 +656,9 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
cacheUpdates.getAndIncrement();
final CacheHit hit = responseCache.storeFromNegotiated(match, target, request, backendResponse, requestDate, responseDate);
- if (shouldSendNotModifiedResponse(request, hit.entry, responseDate)) {
- return convert(responseGenerator.generateNotModifiedResponse(hit.entry));
- } else {
- return convert(responseGenerator.generateResponse(request, hit.entry));
- }
+ final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, responseDate);
+ context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+ return convert(cacheResponse);
} catch (final IOException | RuntimeException ex) {
backendResponse.close();
throw ex;
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
index c2facf4ab..f886d5b3b 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
@@ -26,7 +26,6 @@
*/
package org.apache.hc.client5.http.impl.cache;
-import java.io.IOException;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicLong;
@@ -124,10 +123,6 @@ public class CachingExecBase {
return SimpleHttpResponse.create(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
}
- SimpleHttpResponse unvalidatedCacheHit(final HttpRequest request, final HttpCacheEntry entry) throws IOException {
- return responseGenerator.generateResponse(request, entry);
- }
-
Instant getCurrentDate() {
return Instant.now();
}
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/AsyncClientCacheControl.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/AsyncClientCacheControl.java
new file mode 100644
index 000000000..4535cebcd
--- /dev/null
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/AsyncClientCacheControl.java
@@ -0,0 +1,209 @@
+/*
+ * ====================================================================
+ * 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
+ * .
+ *
+ */
+package org.apache.hc.client5.http.cache.example;
+
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
+import org.apache.hc.client5.http.async.methods.SimpleRequestProducer;
+import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer;
+import org.apache.hc.client5.http.cache.CacheContextBuilder;
+import org.apache.hc.client5.http.cache.HttpCacheContext;
+import org.apache.hc.client5.http.cache.HttpCacheEntry;
+import org.apache.hc.client5.http.cache.RequestCacheControl;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.cache.CacheConfig;
+import org.apache.hc.client5.http.impl.cache.CachingHttpAsyncClients;
+import org.apache.hc.client5.http.impl.cache.HeapResourceFactory;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.message.StatusLine;
+import org.apache.hc.core5.io.CloseMode;
+
+/**
+ * This is an example demonstrating how to control execution of cache
+ * operation and determine its outcome using the async HTTP cache API.
+ */
+public class AsyncClientCacheControl {
+
+ public static void main(final String[] args) throws Exception {
+
+ final HttpHost target = new HttpHost("https", "www.apache.org");
+
+ try (final CloseableHttpAsyncClient httpclient = CachingHttpAsyncClients.custom()
+ .setCacheConfig(CacheConfig.custom()
+ .setMaxObjectSize(200000)
+ .setHeuristicCachingEnabled(true)
+ .build())
+ .setResourceFactory(HeapResourceFactory.INSTANCE)
+ .build()) {
+
+ httpclient.start();
+
+ final SimpleHttpRequest httpget1 = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/")
+ .build();
+
+ // Use default cache control
+ final HttpCacheContext context = CacheContextBuilder.create()
+ .setCacheControl(RequestCacheControl.DEFAULT)
+ .build();
+
+ System.out.println("Executing request " + httpget1.getMethod() + " " + httpget1.getUri());
+ final Future future = httpclient.execute(
+ SimpleRequestProducer.create(httpget1),
+ SimpleResponseConsumer.create(),
+ context,
+ new FutureCallback() {
+
+ @Override
+ public void completed(final SimpleHttpResponse response) {
+ System.out.println(httpget1 + "->" + new StatusLine(response));
+ System.out.println("Cache status: " + context.getCacheResponseStatus());
+ System.out.println("Request cache control: " + context.getRequestCacheControl());
+ System.out.println("Response cache control: " + context.getResponseCacheControl());
+ final HttpCacheEntry cacheEntry = context.getCacheEntry();
+ if (cacheEntry != null) {
+ System.out.println("Cache entry resource: " + cacheEntry.getResource());
+ System.out.println("Date: " + cacheEntry.getInstant());
+ System.out.println("Expires: " + cacheEntry.getExpires());
+ System.out.println("Last modified: " + cacheEntry.getLastModified());
+ }
+ }
+
+ @Override
+ public void failed(final Exception ex) {
+ System.out.println(httpget1 + "->" + ex);
+ }
+
+ @Override
+ public void cancelled() {
+ System.out.println(httpget1 + " cancelled");
+ }
+
+ });
+ future.get();
+
+ final SimpleHttpRequest httpget2 = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/")
+ .build();
+
+ // Ensure a custom freshness for the cache entry
+ context.setRequestCacheControl(RequestCacheControl.builder()
+ .setMinFresh(100)
+ .build());
+
+ System.out.println("Executing request " + httpget2.getMethod() + " " + httpget2.getUri());
+ final Future future2 = httpclient.execute(
+ SimpleRequestProducer.create(httpget2),
+ SimpleResponseConsumer.create(),
+ context,
+ new FutureCallback() {
+
+ @Override
+ public void completed(final SimpleHttpResponse response) {
+ System.out.println(httpget2 + "->" + new StatusLine(response));
+ System.out.println("Cache status: " + context.getCacheResponseStatus());
+ System.out.println("Request cache control: " + context.getRequestCacheControl());
+ System.out.println("Response cache control: " + context.getResponseCacheControl());
+ final HttpCacheEntry cacheEntry = context.getCacheEntry();
+ if (cacheEntry != null) {
+ System.out.println("Cache entry resource: " + cacheEntry.getResource());
+ System.out.println("Date: " + cacheEntry.getInstant());
+ System.out.println("Expires: " + cacheEntry.getExpires());
+ System.out.println("Last modified: " + cacheEntry.getLastModified());
+ }
+ }
+
+ @Override
+ public void failed(final Exception ex) {
+ System.out.println(httpget2 + "->" + ex);
+ }
+
+ @Override
+ public void cancelled() {
+ System.out.println(httpget2 + " cancelled");
+ }
+
+ });
+ future2.get();
+
+ Thread.sleep(2000);
+
+ final SimpleHttpRequest httpget3 = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/")
+ .build();
+
+ // Try to force cache entry re-validation
+ context.setRequestCacheControl(RequestCacheControl.builder()
+ .setMaxAge(0)
+ .build());
+
+ System.out.println("Executing request " + httpget3.getMethod() + " " + httpget3.getUri());
+ final Future future3 = httpclient.execute(
+ SimpleRequestProducer.create(httpget3),
+ SimpleResponseConsumer.create(),
+ context,
+ new FutureCallback() {
+
+ @Override
+ public void completed(final SimpleHttpResponse response) {
+ System.out.println(httpget3 + "->" + new StatusLine(response));
+ System.out.println("Cache status: " + context.getCacheResponseStatus());
+ System.out.println("Request cache control: " + context.getRequestCacheControl());
+ System.out.println("Response cache control: " + context.getResponseCacheControl());
+ final HttpCacheEntry cacheEntry = context.getCacheEntry();
+ if (cacheEntry != null) {
+ System.out.println("Cache entry resource: " + cacheEntry.getResource());
+ System.out.println("Date: " + cacheEntry.getInstant());
+ System.out.println("Expires: " + cacheEntry.getExpires());
+ System.out.println("Last modified: " + cacheEntry.getLastModified());
+ }
+ }
+
+ @Override
+ public void failed(final Exception ex) {
+ System.out.println(httpget3 + "->" + ex);
+ }
+
+ @Override
+ public void cancelled() {
+ System.out.println(httpget3 + " cancelled");
+ }
+
+ });
+ future3.get();
+
+ httpclient.close(CloseMode.GRACEFUL);
+ }
+ }
+}
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/ClientCacheControl.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/ClientCacheControl.java
new file mode 100644
index 000000000..b5ea29cd3
--- /dev/null
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/ClientCacheControl.java
@@ -0,0 +1,150 @@
+/*
+ * ====================================================================
+ * 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
+ * .
+ *
+ */
+package org.apache.hc.client5.http.cache.example;
+
+import org.apache.hc.client5.http.cache.CacheContextBuilder;
+import org.apache.hc.client5.http.cache.HttpCacheContext;
+import org.apache.hc.client5.http.cache.HttpCacheEntry;
+import org.apache.hc.client5.http.cache.RequestCacheControl;
+import org.apache.hc.client5.http.impl.cache.CacheConfig;
+import org.apache.hc.client5.http.impl.cache.CachingHttpClients;
+import org.apache.hc.client5.http.impl.cache.HeapResourceFactory;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
+import org.apache.hc.core5.http.message.StatusLine;
+
+/**
+ * This is an example demonstrating how to control execution of cache
+ * operation and determine its outcome using the classic HTTP cache API.
+ */
+public class ClientCacheControl {
+
+ public static void main(final String[] args) throws Exception {
+
+ final HttpHost target = new HttpHost("https", "www.apache.org");
+
+ try (final CloseableHttpClient httpclient = CachingHttpClients.custom()
+ .setCacheConfig(CacheConfig.custom()
+ .setMaxObjectSize(200000)
+ .setHeuristicCachingEnabled(true)
+ .build())
+ .setResourceFactory(HeapResourceFactory.INSTANCE)
+ .build()) {
+
+ final ClassicHttpRequest httpget1 = ClassicRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/")
+ .build();
+
+ // Use default cache control
+ final HttpCacheContext context = CacheContextBuilder.create()
+ .setCacheControl(RequestCacheControl.DEFAULT)
+ .build();
+
+ System.out.println("Executing request " + httpget1.getMethod() + " " + httpget1.getUri());
+ httpclient.execute(httpget1, context, response -> {
+ System.out.println("----------------------------------------");
+ System.out.println(httpget1 + "->" + new StatusLine(response));
+ EntityUtils.consume(response.getEntity());
+ System.out.println("Cache status: " + context.getCacheResponseStatus());
+ System.out.println("Request cache control: " + context.getRequestCacheControl());
+ System.out.println("Response cache control: " + context.getResponseCacheControl());
+ final HttpCacheEntry cacheEntry = context.getCacheEntry();
+ if (cacheEntry != null) {
+ System.out.println("Cache entry resource: " + cacheEntry.getResource());
+ System.out.println("Date: " + cacheEntry.getInstant());
+ System.out.println("Expires: " + cacheEntry.getExpires());
+ System.out.println("Last modified: " + cacheEntry.getLastModified());
+ }
+ return null;
+ });
+
+ final ClassicHttpRequest httpget2 = ClassicRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/")
+ .build();
+
+ // Ensure a custom freshness for the cache entry
+ context.setRequestCacheControl(RequestCacheControl.builder()
+ .setMinFresh(100)
+ .build());
+
+ System.out.println("Executing request " + httpget2.getMethod() + " " + httpget2.getUri());
+ httpclient.execute(httpget2, context, response -> {
+ System.out.println("----------------------------------------");
+ System.out.println(httpget2 + "->" + new StatusLine(response));
+ EntityUtils.consume(response.getEntity());
+ System.out.println("Cache status: " + context.getCacheResponseStatus());
+ System.out.println("Request cache control: " + context.getRequestCacheControl());
+ System.out.println("Response cache control: " + context.getResponseCacheControl());
+ final HttpCacheEntry cacheEntry = context.getCacheEntry();
+ if (cacheEntry != null) {
+ System.out.println("Cache entry resource: " + cacheEntry.getResource());
+ System.out.println("Date: " + cacheEntry.getInstant());
+ System.out.println("Expires: " + cacheEntry.getExpires());
+ System.out.println("Last modified: " + cacheEntry.getLastModified());
+ }
+ return null;
+ });
+
+ Thread.sleep(2000);
+
+ final ClassicHttpRequest httpget3 = ClassicRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/")
+ .build();
+
+ // Try to force cache entry re-validation
+ context.setRequestCacheControl(RequestCacheControl.builder()
+ .setMaxAge(0)
+ .build());
+
+ System.out.println("Executing request " + httpget3.getMethod() + " " + httpget3.getUri());
+ httpclient.execute(httpget3, context, response -> {
+ System.out.println("----------------------------------------");
+ System.out.println(httpget3 + "->" + new StatusLine(response));
+ EntityUtils.consume(response.getEntity());
+ System.out.println("Cache status: " + context.getCacheResponseStatus());
+ System.out.println("Request cache control: " + context.getRequestCacheControl());
+ System.out.println("Response cache control: " + context.getResponseCacheControl());
+ final HttpCacheEntry cacheEntry = context.getCacheEntry();
+ if (cacheEntry != null) {
+ System.out.println("Cache entry resource: " + cacheEntry.getResource());
+ System.out.println("Date: " + cacheEntry.getInstant());
+ System.out.println("Expires: " + cacheEntry.getExpires());
+ System.out.println("Last modified: " + cacheEntry.getLastModified());
+ }
+ return null;
+ });
+ }
+
+
+ }
+}
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/CacheControlGeneratorTest.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/CacheControlGeneratorTest.java
new file mode 100644
index 000000000..0efa6dad3
--- /dev/null
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/CacheControlGeneratorTest.java
@@ -0,0 +1,86 @@
+/*
+ * ====================================================================
+ * 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
+ * .
+ *
+ */
+package org.apache.hc.client5.http.impl.cache;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.apache.hc.client5.http.HeaderMatcher;
+import org.apache.hc.client5.http.cache.RequestCacheControl;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class CacheControlGeneratorTest {
+
+ private final CacheControlHeaderGenerator generator = CacheControlHeaderGenerator.INSTANCE;
+
+ @Test
+ public void testGenerateRequestCacheControlHeader() {
+ assertThat(generator.generate(
+ RequestCacheControl.builder()
+ .setMaxAge(12)
+ .setMaxStale(23)
+ .setMinFresh(34)
+ .setNoCache(true)
+ .setNoStore(true)
+ .setOnlyIfCached(true)
+ .setStaleIfError(56)
+ .build()),
+ HeaderMatcher.same("Cache-Control", "max-age=12, max-stale=23, " +
+ "min-fresh=34, no-cache, no-store, only-if-cached, stale-if-error=56"));
+ assertThat(generator.generate(
+ RequestCacheControl.builder()
+ .setMaxAge(12)
+ .setNoCache(true)
+ .setMinFresh(34)
+ .setMaxStale(23)
+ .setNoStore(true)
+ .setStaleIfError(56)
+ .setOnlyIfCached(true)
+ .build()),
+ HeaderMatcher.same("Cache-Control", "max-age=12, max-stale=23, " +
+ "min-fresh=34, no-cache, no-store, only-if-cached, stale-if-error=56"));
+ assertThat(generator.generate(
+ RequestCacheControl.builder()
+ .setMaxAge(0)
+ .build()),
+ HeaderMatcher.same("Cache-Control", "max-age=0"));
+ assertThat(generator.generate(
+ RequestCacheControl.builder()
+ .setMaxAge(-1)
+ .setMinFresh(10)
+ .build()),
+ HeaderMatcher.same("Cache-Control", "min-fresh=10"));
+ }
+
+ @Test
+ public void testGenerateRequestCacheControlHeaderNoDirectives() {
+ final RequestCacheControl cacheControl = RequestCacheControl.builder()
+ .build();
+ Assertions.assertNull(generator.generate(cacheControl));
+ }
+
+}
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
index b1616c331..4d96eedc5 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
@@ -27,7 +27,6 @@
package org.apache.hc.client5.http.impl.cache;
-import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.mock;
import java.io.IOException;
@@ -50,7 +49,6 @@ import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecRuntime;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpOptions;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
@@ -91,6 +89,7 @@ public class TestCachingExecChain {
HttpCache cache;
CachingExec impl;
CacheConfig customConfig;
+ ExecChain.Scope scope;
@BeforeEach
public void setUp() {
@@ -101,6 +100,7 @@ public class TestCachingExecChain {
context = HttpCacheContext.create();
entry = HttpTestUtils.makeCacheEntry();
customConfig = CacheConfig.DEFAULT;
+ scope = new ExecChain.Scope("test", route, request, mockExecRuntime, context);
cache = Mockito.spy(new BasicHttpCache());
@@ -900,7 +900,7 @@ public class TestCachingExecChain {
originResponse.setHeader("Date", DateUtils.formatStandardDate(responseGenerated));
originResponse.setHeader("ETag", "\"etag\"");
- impl.cacheAndReturnResponse("exchange-id", host, request, originResponse, requestSent, responseReceived);
+ impl.cacheAndReturnResponse(host, request, scope, originResponse, requestSent, responseReceived);
Mockito.verify(cache, Mockito.never()).store(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
@@ -936,7 +936,7 @@ public class TestCachingExecChain {
Mockito.eq(requestSent),
Mockito.eq(responseReceived))).thenReturn(new CacheHit("key", httpCacheEntry));
- impl.cacheAndReturnResponse("exchange-id", host, request, originResponse, requestSent, responseReceived);
+ impl.cacheAndReturnResponse(host, request, scope, originResponse, requestSent, responseReceived);
Mockito.verify(mockCache).store(
Mockito.any(),
@@ -956,45 +956,6 @@ public class TestCachingExecChain {
Assertions.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, resp.getCode());
}
- @Test
- public void testSetsRequestInContextOnCacheHit() throws Exception {
- final ClassicHttpResponse response = HttpTestUtils.make200Response();
- response.setHeader("Cache-Control", "max-age=3600");
- Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(request), Mockito.any())).thenReturn(response);
-
- final HttpClientContext ctx = HttpClientContext.create();
- impl.execute(request, new ExecChain.Scope("test", route, request, mockExecRuntime, context), mockExecChain);
- impl.execute(request, new ExecChain.Scope("test", route, request, mockExecRuntime, ctx), mockExecChain);
- if (!HttpTestUtils.equivalent(request, ctx.getRequest())) {
- assertSame(request, ctx.getRequest());
- }
- }
-
- @Test
- public void testSetsResponseInContextOnCacheHit() throws Exception {
- final ClassicHttpResponse response = HttpTestUtils.make200Response();
- response.setHeader("Cache-Control", "max-age=3600");
- Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(request), Mockito.any())).thenReturn(response);
-
- final HttpClientContext ctx = HttpClientContext.create();
- impl.execute(request, new ExecChain.Scope("test", route, request, mockExecRuntime, context), mockExecChain);
- final ClassicHttpResponse result = impl.execute(request, new ExecChain.Scope("test", route, request, mockExecRuntime, ctx), null);
- if (!HttpTestUtils.equivalent(result, ctx.getResponse())) {
- assertSame(result, ctx.getResponse());
- }
- }
-
- @Test
- public void testSetsRequestSentInContextOnCacheHit() throws Exception {
- final ClassicHttpResponse response = HttpTestUtils.make200Response();
- response.setHeader("Cache-Control", "max-age=3600");
- Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(request), Mockito.any())).thenReturn(response);
-
- final HttpClientContext ctx = HttpClientContext.create();
- impl.execute(request, new ExecChain.Scope("test", route, request, mockExecRuntime, context), mockExecChain);
- impl.execute(request, new ExecChain.Scope("test", route, request, mockExecRuntime, ctx), mockExecChain);
- }
-
@Test
public void testCanCacheAResponseWithoutABody() throws Exception {
final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
@@ -1002,8 +963,8 @@ public class TestCachingExecChain {
response.setHeader("Cache-Control", "max-age=300");
Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(request), Mockito.any())).thenReturn(response);
- impl.execute(request, new ExecChain.Scope("test", route, request, mockExecRuntime, context), mockExecChain);
- impl.execute(request, new ExecChain.Scope("test", route, request, mockExecRuntime, context), mockExecChain);
+ impl.execute(request, scope, mockExecChain);
+ impl.execute(request, scope, mockExecChain);
Mockito.verify(mockExecChain).proceed(Mockito.any(), Mockito.any());
}
@@ -1148,16 +1109,16 @@ public class TestCachingExecChain {
response.setHeader("Cache-Control", "max-age=3600");
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(response);
- impl.execute(request, new ExecChain.Scope("test", route, request, mockExecRuntime, context), mockExecChain);
+ impl.execute(request, scope, mockExecChain);
Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
request.setAuthority(new URIAuthority("bar.example.com"));
- impl.execute(request, new ExecChain.Scope("test", route, request, mockExecRuntime, context), mockExecChain);
+ impl.execute(request, scope, mockExecChain);
Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
- impl.execute(request, new ExecChain.Scope("test", route, request, mockExecRuntime, context), mockExecChain);
+ impl.execute(request, scope, mockExecChain);
Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
}
@@ -1255,8 +1216,6 @@ public class TestCachingExecChain {
backendResponse.setHeader("Cache-Control", "public, max-age=3600");
backendResponse.setHeader("ETag", "\"etag\"");
- final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, mockExecRuntime, context);
-
final Header[] headers = new Header[5];
for (int i = 0; i < headers.length; i++) {
headers[i] = new BasicHeader("header" + i, "value" + i);
@@ -1274,7 +1233,7 @@ public class TestCachingExecChain {
.thenReturn(new CacheHit("key", cacheEntry));
// Call cacheAndReturnResponse with 304 Not Modified response
- final ClassicHttpResponse cachedResponse = impl.cacheAndReturnResponse("exchange-id", host, request, backendResponse, requestSent, responseReceived);
+ final ClassicHttpResponse cachedResponse = impl.cacheAndReturnResponse(host, request, scope, backendResponse, requestSent, responseReceived);
// Verify cache entry is updated
Mockito.verify(mockCache).update(