Cache control and context API
This commit is contained in:
parent
b3da0ae138
commit
7c0d083b12
117
httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheContextBuilder.java
vendored
Normal file
117
httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheContextBuilder.java
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ import org.slf4j.LoggerFactory;
|
|||
* </p>
|
||||
*/
|
||||
@Internal
|
||||
@Contract(threading = ThreadingBehavior.SAFE)
|
||||
@Contract(threading = ThreadingBehavior.IMMUTABLE)
|
||||
class CacheControlHeaderParser {
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
150
httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/ClientCacheControl.java
vendored
Normal file
150
httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/ClientCacheControl.java
vendored
Normal 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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue