Cache control and context API

This commit is contained in:
Oleg Kalnichevski 2023-12-28 16:39:07 +01:00
parent b3da0ae138
commit 7c0d083b12
13 changed files with 827 additions and 117 deletions

View File

@ -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
* <http://www.apache.org/>.
*
*/
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<HttpCacheContext> {
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<CookieSpecFactory> cookieSpecRegistry) {
super.useCookieSpecRegistry(cookieSpecRegistry);
return this;
}
@Override
public CacheContextBuilder useAuthSchemeRegistry(final Lookup<AuthSchemeFactory> 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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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,18 +1238,14 @@ 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);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} else {
try {
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, responseDate);
context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} catch (final ResourceIOException ex) {
asyncExecCallback.failed(ex);
}
}
}
@Override
public void failed(final Exception 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) {

View File

@ -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
* <http://www.apache.org/>.
*
*/
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<NameValuePair> convert(final RequestCacheControl cacheControl) {
Args.notNull(cacheControl, "Cache control");
final List<NameValuePair> 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<NameValuePair> 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);
}
}
}

View File

@ -70,7 +70,7 @@ import org.slf4j.LoggerFactory;
* </p>
*/
@Internal
@Contract(threading = ThreadingBehavior.SAFE)
@Contract(threading = ThreadingBehavior.IMMUTABLE)
class CacheControlHeaderParser {
/**

View File

@ -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;

View File

@ -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();
}

View File

@ -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
* <http://www.apache.org/>.
*
*/
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<SimpleHttpResponse> future = httpclient.execute(
SimpleRequestProducer.create(httpget1),
SimpleResponseConsumer.create(),
context,
new FutureCallback<SimpleHttpResponse>() {
@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<SimpleHttpResponse> future2 = httpclient.execute(
SimpleRequestProducer.create(httpget2),
SimpleResponseConsumer.create(),
context,
new FutureCallback<SimpleHttpResponse>() {
@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<SimpleHttpResponse> future3 = httpclient.execute(
SimpleRequestProducer.create(httpget3),
SimpleResponseConsumer.create(),
context,
new FutureCallback<SimpleHttpResponse>() {
@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);
}
}
}

View File

@ -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
* <http://www.apache.org/>.
*
*/
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;
});
}
}
}

View File

@ -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
* <http://www.apache.org/>.
*
*/
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));
}
}

View File

@ -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(