HTTPCLIENT-975: committed patch for stale-while-revalidate from
Michajlo Matijkiw (michajlo_matijkiw at comcast dot com). Stale-while-revalidate functionality is currently off by default until we can add bounding to the revalidation queue (or make it configurable). git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1049941 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
318d74af0c
commit
5f88f05627
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* ====================================================================
|
||||||
|
* 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.http.impl.client.cache;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.HttpRequest;
|
||||||
|
import org.apache.http.ProtocolException;
|
||||||
|
import org.apache.http.client.cache.HttpCacheEntry;
|
||||||
|
import org.apache.http.protocol.HttpContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used to represent an asynchronous revalidation event, such as with "stale-while-revalidate"
|
||||||
|
*/
|
||||||
|
class AsynchronousValidationRequest implements Runnable {
|
||||||
|
private final AsynchronousValidator parent;
|
||||||
|
private final CachingHttpClient cachingClient;
|
||||||
|
private final HttpHost target;
|
||||||
|
private final HttpRequest request;
|
||||||
|
private final HttpContext context;
|
||||||
|
private final HttpCacheEntry cacheEntry;
|
||||||
|
private final String identifier;
|
||||||
|
|
||||||
|
private final Log log = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used internally by {@link AsynchronousValidator} to schedule a revalidation. Once revalidation
|
||||||
|
* is complete, the {@link Set} bookKeeping will be locked and the {@link String} identifier will be
|
||||||
|
* removed from it.
|
||||||
|
*
|
||||||
|
* @param cachingClient
|
||||||
|
* @param target
|
||||||
|
* @param request
|
||||||
|
* @param context
|
||||||
|
* @param cacheEntry
|
||||||
|
* @param bookKeeping
|
||||||
|
* @param identifier
|
||||||
|
*/
|
||||||
|
AsynchronousValidationRequest(AsynchronousValidator parent,
|
||||||
|
CachingHttpClient cachingClient, HttpHost target,
|
||||||
|
HttpRequest request, HttpContext context,
|
||||||
|
HttpCacheEntry cacheEntry,
|
||||||
|
String identifier) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.cachingClient = cachingClient;
|
||||||
|
this.target = target;
|
||||||
|
this.request = request;
|
||||||
|
this.context = context;
|
||||||
|
this.cacheEntry = cacheEntry;
|
||||||
|
this.identifier = identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
cachingClient.revalidateCacheEntry(target, request, context, cacheEntry);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
log.debug("Asynchronous revalidation failed due to exception: " + ioe);
|
||||||
|
} catch (ProtocolException pe) {
|
||||||
|
log.error("ProtocolException thrown during asynchronous revalidation: " + pe);
|
||||||
|
} finally {
|
||||||
|
parent.markComplete(identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getIdentifier() {
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
129
httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidator.java
vendored
Normal file
129
httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidator.java
vendored
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
* ====================================================================
|
||||||
|
* 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.http.impl.client.cache;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.HttpRequest;
|
||||||
|
import org.apache.http.client.cache.HttpCacheEntry;
|
||||||
|
import org.apache.http.protocol.HttpContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used for asynchronous revalidations to be used when the "stale-
|
||||||
|
* while-revalidate" directive is present
|
||||||
|
*/
|
||||||
|
public class AsynchronousValidator {
|
||||||
|
private final CachingHttpClient cachingClient;
|
||||||
|
private final ExecutorService executor;
|
||||||
|
private final Set<String> queued;
|
||||||
|
private final CacheKeyGenerator cacheKeyGenerator;
|
||||||
|
|
||||||
|
private final Log log = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create AsynchronousValidator which will make revalidation requests using the supplied {@link CachingHttpClient}, and
|
||||||
|
* a {@link ThreadPoolExecutor} returned by {@link Executors#newFixedThreadPool(int)}.
|
||||||
|
* @param cachingClient
|
||||||
|
* @param numThreads
|
||||||
|
*/
|
||||||
|
public AsynchronousValidator(CachingHttpClient cachingClient, int numThreads) {
|
||||||
|
this(cachingClient, Executors.newFixedThreadPool(numThreads));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create AsynchronousValidator which will make revalidation requests using the supplied {@link CachingHttpClient} and
|
||||||
|
* {@link ExecutorService}.
|
||||||
|
* @param cachingClient
|
||||||
|
* @param executor
|
||||||
|
*/
|
||||||
|
AsynchronousValidator(CachingHttpClient cachingClient, ExecutorService executor) {
|
||||||
|
this.cachingClient = cachingClient;
|
||||||
|
this.executor = executor;
|
||||||
|
this.queued = new HashSet<String>();
|
||||||
|
this.cacheKeyGenerator = new CacheKeyGenerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules an asynchronous revalidation
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @param request
|
||||||
|
* @param context
|
||||||
|
* @param entry
|
||||||
|
*/
|
||||||
|
public synchronized void revalidateCacheEntry(HttpHost target, HttpRequest request, HttpContext context, HttpCacheEntry entry) {
|
||||||
|
// getVariantURI will fall back on getURI if no variants exist
|
||||||
|
String uri = cacheKeyGenerator.getVariantURI(target, request, entry);
|
||||||
|
|
||||||
|
if (!queued.contains(uri)) {
|
||||||
|
AsynchronousValidationRequest revalidationRequest = new AsynchronousValidationRequest(
|
||||||
|
this, cachingClient, target, request, context, entry, uri);
|
||||||
|
|
||||||
|
try {
|
||||||
|
executor.execute(revalidationRequest);
|
||||||
|
queued.add(uri);
|
||||||
|
} catch (RejectedExecutionException ree) {
|
||||||
|
log.debug("Revalidation for [" + uri + "] not scheduled: " + ree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will remove identifier from internal list of jobs in progress. This is meant to be called
|
||||||
|
* by {@link AsynchrnousValidationRequest#run()} once the revalidation is complete, using the identifier
|
||||||
|
* passed in durinc constructions.
|
||||||
|
* @param identifier
|
||||||
|
*/
|
||||||
|
synchronized void markComplete(String identifier) {
|
||||||
|
queued.remove(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of identifiers (URIs) for revalidations
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Set<String> getScheduledIdentifiers() {
|
||||||
|
return Collections.unmodifiableSet(queued);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return underlying {@link ExecutorService}
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ExecutorService getExecutor() {
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,14 +52,21 @@ public class CacheConfig {
|
||||||
public final static boolean DEFAULT_HEURISTIC_CACHING_ENABLED = false;
|
public final static boolean DEFAULT_HEURISTIC_CACHING_ENABLED = false;
|
||||||
|
|
||||||
/** Default coefficient used to heuristically determine freshness lifetime from
|
/** Default coefficient used to heuristically determine freshness lifetime from
|
||||||
* cache entry.
|
* cache entry.
|
||||||
*/
|
*/
|
||||||
public final static float DEFAULT_HEURISTIC_COEFFICIENT = 0.1f;
|
public final static float DEFAULT_HEURISTIC_COEFFICIENT = 0.1f;
|
||||||
|
|
||||||
/** Default lifetime to be assumed when we cannot calculate freshness heuristically
|
/** Default lifetime in seconds to be assumed when we cannot calculate freshness
|
||||||
|
* heuristically
|
||||||
*/
|
*/
|
||||||
public final static long DEFAULT_HEURISTIC_LIFETIME = 0;
|
public final static long DEFAULT_HEURISTIC_LIFETIME = 0;
|
||||||
|
|
||||||
|
/** Default number of worker threads to allow for background revalidations
|
||||||
|
* resulting from the stale-while-revalidate directive; 0 disables handling
|
||||||
|
* asynchronous revalidations.
|
||||||
|
*/
|
||||||
|
private static final int DEFAULT_STALE_WHILE_REVALIDATE_WORKERS = 0;
|
||||||
|
|
||||||
private int maxObjectSizeBytes = DEFAULT_MAX_OBJECT_SIZE_BYTES;
|
private int maxObjectSizeBytes = DEFAULT_MAX_OBJECT_SIZE_BYTES;
|
||||||
private int maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES;
|
private int maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES;
|
||||||
private int maxUpdateRetries = DEFAULT_MAX_UPDATE_RETRIES;
|
private int maxUpdateRetries = DEFAULT_MAX_UPDATE_RETRIES;
|
||||||
|
@ -67,6 +74,7 @@ public class CacheConfig {
|
||||||
private float heuristicCoefficient = DEFAULT_HEURISTIC_COEFFICIENT;
|
private float heuristicCoefficient = DEFAULT_HEURISTIC_COEFFICIENT;
|
||||||
private long heuristicDefaultLifetime = DEFAULT_HEURISTIC_LIFETIME;
|
private long heuristicDefaultLifetime = DEFAULT_HEURISTIC_LIFETIME;
|
||||||
private boolean isSharedCache = true;
|
private boolean isSharedCache = true;
|
||||||
|
private int staleWhileRevalidateWorkers = DEFAULT_STALE_WHILE_REVALIDATE_WORKERS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current maximum object size that will be cached.
|
* Returns the current maximum object size that will be cached.
|
||||||
|
@ -173,5 +181,21 @@ public class CacheConfig {
|
||||||
this.heuristicDefaultLifetime = heuristicDefaultLifetime;
|
this.heuristicDefaultLifetime = heuristicDefaultLifetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set number of worker threads to allow for background revalidations resulting from,
|
||||||
|
* the stale-while-revalidate directive, 0 disables handling of directive
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getStaleWhileRevalidateWorkers() {
|
||||||
|
return staleWhileRevalidateWorkers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get number of worker threads to allow for background revalidations resulting from,
|
||||||
|
* the stale-while-revalidate directive, 0 disables handling of directive
|
||||||
|
*/
|
||||||
|
public void setStaleWhileRevalidateWorkers(int staleWhileRevalidateWorkers) {
|
||||||
|
this.staleWhileRevalidateWorkers = staleWhileRevalidateWorkers;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ class CacheKeyGenerator {
|
||||||
*
|
*
|
||||||
* @param host The host for this request
|
* @param host The host for this request
|
||||||
* @param req the {@link HttpRequest}
|
* @param req the {@link HttpRequest}
|
||||||
* @param entry the parent entry used to track the varients
|
* @param entry the parent entry used to track the variants
|
||||||
* @return String the extracted variant URI
|
* @return String the extracted variant URI
|
||||||
*/
|
*/
|
||||||
public String getVariantURI(HttpHost host, HttpRequest req, HttpCacheEntry entry) {
|
public String getVariantURI(HttpHost host, HttpRequest req, HttpCacheEntry entry) {
|
||||||
|
|
|
@ -120,6 +120,25 @@ class CacheValidityPolicy {
|
||||||
return hasCacheControlDirective(entry, "proxy-revalidate");
|
return hasCacheControlDirective(entry, "proxy-revalidate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean mayReturnStaleWhileRevalidating(final HttpCacheEntry entry, Date now) {
|
||||||
|
for (Header h : entry.getHeaders("Cache-Control")) {
|
||||||
|
for(HeaderElement elt : h.getElements()) {
|
||||||
|
if ("stale-while-revalidate".equalsIgnoreCase(elt.getName())) {
|
||||||
|
try {
|
||||||
|
int allowedStalenessLifetime = Integer.parseInt(elt.getValue());
|
||||||
|
if (getStalenessSecs(entry, now) <= allowedStalenessLifetime) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
// skip malformed directive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean mayReturnStaleIfError(HttpRequest request,
|
public boolean mayReturnStaleIfError(HttpRequest request,
|
||||||
HttpCacheEntry entry, Date now) {
|
HttpCacheEntry entry, Date now) {
|
||||||
long stalenessSecs = getStalenessSecs(entry, now);
|
long stalenessSecs = getStalenessSecs(entry, now);
|
||||||
|
|
|
@ -95,6 +95,8 @@ public class CachingHttpClient implements HttpClient {
|
||||||
private final ResponseProtocolCompliance responseCompliance;
|
private final ResponseProtocolCompliance responseCompliance;
|
||||||
private final RequestProtocolCompliance requestCompliance;
|
private final RequestProtocolCompliance requestCompliance;
|
||||||
|
|
||||||
|
private final AsynchronousValidator asynchRevalidator;
|
||||||
|
|
||||||
private final Log log = LogFactory.getLog(getClass());
|
private final Log log = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
CachingHttpClient(
|
CachingHttpClient(
|
||||||
|
@ -124,6 +126,8 @@ public class CachingHttpClient implements HttpClient {
|
||||||
|
|
||||||
this.responseCompliance = new ResponseProtocolCompliance();
|
this.responseCompliance = new ResponseProtocolCompliance();
|
||||||
this.requestCompliance = new RequestProtocolCompliance();
|
this.requestCompliance = new RequestProtocolCompliance();
|
||||||
|
|
||||||
|
this.asynchRevalidator = makeAsynchronousValidator(config.getStaleWhileRevalidateWorkers());
|
||||||
}
|
}
|
||||||
|
|
||||||
public CachingHttpClient() {
|
public CachingHttpClient() {
|
||||||
|
@ -193,6 +197,15 @@ public class CachingHttpClient implements HttpClient {
|
||||||
this.conditionalRequestBuilder = conditionalRequestBuilder;
|
this.conditionalRequestBuilder = conditionalRequestBuilder;
|
||||||
this.responseCompliance = responseCompliance;
|
this.responseCompliance = responseCompliance;
|
||||||
this.requestCompliance = requestCompliance;
|
this.requestCompliance = requestCompliance;
|
||||||
|
this.asynchRevalidator = makeAsynchronousValidator(config.getStaleWhileRevalidateWorkers());
|
||||||
|
}
|
||||||
|
|
||||||
|
private AsynchronousValidator makeAsynchronousValidator(
|
||||||
|
int numWorkers) {
|
||||||
|
if (numWorkers > 0) {
|
||||||
|
return new AsynchronousValidator(this, numWorkers);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -461,6 +474,14 @@ public class CachingHttpClient implements HttpClient {
|
||||||
log.debug("Revalidating the cache entry");
|
log.debug("Revalidating the cache entry");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (asynchRevalidator != null && validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) {
|
||||||
|
final HttpResponse resp = responseGenerator.generateResponse(entry);
|
||||||
|
resp.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
|
||||||
|
|
||||||
|
asynchRevalidator.revalidateCacheEntry(target, request, context, entry);
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
return revalidateCacheEntry(target, request, context, entry);
|
return revalidateCacheEntry(target, request, context, entry);
|
||||||
} catch (IOException ioex) {
|
} catch (IOException ioex) {
|
||||||
if (validityPolicy.mustRevalidate(entry)
|
if (validityPolicy.mustRevalidate(entry)
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
* ====================================================================
|
||||||
|
* 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.http.impl.client.cache;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.HttpRequest;
|
||||||
|
import org.apache.http.ProtocolException;
|
||||||
|
import org.apache.http.client.cache.HttpCacheEntry;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.protocol.HttpContext;
|
||||||
|
import org.easymock.classextension.EasyMock;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class TestAsynchronousValidationRequest {
|
||||||
|
|
||||||
|
private AsynchronousValidator mockParent;
|
||||||
|
private CachingHttpClient mockClient;
|
||||||
|
private HttpHost target;
|
||||||
|
private HttpRequest request;
|
||||||
|
private HttpContext mockContext;
|
||||||
|
private HttpCacheEntry mockCacheEntry;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mockParent = EasyMock.createMock(AsynchronousValidator.class);
|
||||||
|
mockClient = EasyMock.createMock(CachingHttpClient.class);
|
||||||
|
target = new HttpHost("foo.example.com");
|
||||||
|
request = new HttpGet("/");
|
||||||
|
mockContext = EasyMock.createMock(HttpContext.class);
|
||||||
|
mockCacheEntry = EasyMock.createMock(HttpCacheEntry.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunCallsCachingClientAndRemovesIdentifier() throws ProtocolException, IOException {
|
||||||
|
String identifier = "foo";
|
||||||
|
|
||||||
|
AsynchronousValidationRequest asynchRequest = new AsynchronousValidationRequest(
|
||||||
|
mockParent, mockClient, target, request, mockContext, mockCacheEntry,
|
||||||
|
identifier);
|
||||||
|
|
||||||
|
// response not used
|
||||||
|
EasyMock.expect(mockClient.revalidateCacheEntry(target, request, mockContext, mockCacheEntry)).andReturn(null);
|
||||||
|
mockParent.markComplete(identifier);
|
||||||
|
|
||||||
|
replayMocks();
|
||||||
|
asynchRequest.run();
|
||||||
|
verifyMocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunGracefullyHandlesProtocolException() throws IOException, ProtocolException {
|
||||||
|
String identifier = "foo";
|
||||||
|
|
||||||
|
AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
|
||||||
|
mockParent, mockClient, target, request, mockContext, mockCacheEntry,
|
||||||
|
identifier);
|
||||||
|
|
||||||
|
// response not used
|
||||||
|
EasyMock.expect(
|
||||||
|
mockClient.revalidateCacheEntry(target, request, mockContext,
|
||||||
|
mockCacheEntry)).andThrow(new ProtocolException());
|
||||||
|
mockParent.markComplete(identifier);
|
||||||
|
|
||||||
|
replayMocks();
|
||||||
|
impl.run();
|
||||||
|
verifyMocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunGracefullyHandlesIOException() throws IOException, ProtocolException {
|
||||||
|
String identifier = "foo";
|
||||||
|
|
||||||
|
AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
|
||||||
|
mockParent, mockClient, target, request, mockContext, mockCacheEntry,
|
||||||
|
identifier);
|
||||||
|
|
||||||
|
// response not used
|
||||||
|
EasyMock.expect(
|
||||||
|
mockClient.revalidateCacheEntry(target, request, mockContext,
|
||||||
|
mockCacheEntry)).andThrow(new IOException());
|
||||||
|
mockParent.markComplete(identifier);
|
||||||
|
|
||||||
|
replayMocks();
|
||||||
|
impl.run();
|
||||||
|
verifyMocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replayMocks() {
|
||||||
|
EasyMock.replay(mockClient);
|
||||||
|
EasyMock.replay(mockContext);
|
||||||
|
EasyMock.replay(mockCacheEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void verifyMocks() {
|
||||||
|
EasyMock.verify(mockClient);
|
||||||
|
EasyMock.verify(mockContext);
|
||||||
|
EasyMock.verify(mockCacheEntry);
|
||||||
|
}
|
||||||
|
}
|
204
httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidator.java
vendored
Normal file
204
httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidator.java
vendored
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
/*
|
||||||
|
* ====================================================================
|
||||||
|
* 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.http.impl.client.cache;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import junit.framework.Assert;
|
||||||
|
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.HttpRequest;
|
||||||
|
import org.apache.http.ProtocolException;
|
||||||
|
import org.apache.http.client.cache.HeaderConstants;
|
||||||
|
import org.apache.http.client.cache.HttpCacheEntry;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.message.BasicHeader;
|
||||||
|
import org.apache.http.protocol.HttpContext;
|
||||||
|
import org.easymock.Capture;
|
||||||
|
import org.easymock.classextension.EasyMock;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class TestAsynchronousValidator {
|
||||||
|
|
||||||
|
private AsynchronousValidator impl;
|
||||||
|
|
||||||
|
private CachingHttpClient mockClient;
|
||||||
|
private HttpHost target;
|
||||||
|
private HttpRequest request;
|
||||||
|
private HttpContext mockContext;
|
||||||
|
private HttpCacheEntry mockCacheEntry;
|
||||||
|
|
||||||
|
private ExecutorService mockExecutor;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mockClient = EasyMock.createMock(CachingHttpClient.class);
|
||||||
|
target = new HttpHost("foo.example.com");
|
||||||
|
request = new HttpGet("/");
|
||||||
|
mockContext = EasyMock.createMock(HttpContext.class);
|
||||||
|
mockCacheEntry = EasyMock.createMock(HttpCacheEntry.class);
|
||||||
|
|
||||||
|
mockExecutor = EasyMock.createMock(ExecutorService.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRevalidateCacheEntrySchedulesExecutionAndPopulatesIdentifier() {
|
||||||
|
impl = new AsynchronousValidator(mockClient, mockExecutor);
|
||||||
|
|
||||||
|
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
|
||||||
|
mockExecutor.execute(EasyMock.isA(AsynchronousValidationRequest.class));
|
||||||
|
|
||||||
|
replayMocks();
|
||||||
|
impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
|
||||||
|
verifyMocks();
|
||||||
|
|
||||||
|
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMarkCompleteRemovesIdentifier() {
|
||||||
|
impl = new AsynchronousValidator(mockClient, mockExecutor);
|
||||||
|
|
||||||
|
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
|
||||||
|
Capture<AsynchronousValidationRequest> cap = new Capture<AsynchronousValidationRequest>();
|
||||||
|
mockExecutor.execute(EasyMock.capture(cap));
|
||||||
|
|
||||||
|
replayMocks();
|
||||||
|
impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
|
||||||
|
verifyMocks();
|
||||||
|
|
||||||
|
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
|
||||||
|
|
||||||
|
impl.markComplete(cap.getValue().getIdentifier());
|
||||||
|
|
||||||
|
Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRevalidateCacheEntryDoesNotPopulateIdentifierOnRejectedExecutionException() {
|
||||||
|
impl = new AsynchronousValidator(mockClient, mockExecutor);
|
||||||
|
|
||||||
|
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
|
||||||
|
mockExecutor.execute(EasyMock.isA(AsynchronousValidationRequest.class));
|
||||||
|
EasyMock.expectLastCall().andThrow(new RejectedExecutionException());
|
||||||
|
|
||||||
|
replayMocks();
|
||||||
|
impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
|
||||||
|
verifyMocks();
|
||||||
|
|
||||||
|
Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRevalidateCacheEntryProperlyCollapsesRequest() {
|
||||||
|
impl = new AsynchronousValidator(mockClient, mockExecutor);
|
||||||
|
|
||||||
|
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
|
||||||
|
mockExecutor.execute(EasyMock.isA(AsynchronousValidationRequest.class));
|
||||||
|
|
||||||
|
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
|
||||||
|
|
||||||
|
replayMocks();
|
||||||
|
impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
|
||||||
|
impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
|
||||||
|
verifyMocks();
|
||||||
|
|
||||||
|
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVariantsBothRevalidated() {
|
||||||
|
impl = new AsynchronousValidator(mockClient, mockExecutor);
|
||||||
|
|
||||||
|
HttpRequest req1 = new HttpGet("/");
|
||||||
|
req1.addHeader(new BasicHeader("Accept-Encoding", "identity"));
|
||||||
|
|
||||||
|
HttpRequest req2 = new HttpGet("/");
|
||||||
|
req2.addHeader(new BasicHeader("Accept-Encoding", "gzip"));
|
||||||
|
|
||||||
|
Header[] variantHeaders = new Header[] {
|
||||||
|
new BasicHeader(HeaderConstants.VARY, "Accept-Encoding")
|
||||||
|
};
|
||||||
|
|
||||||
|
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(true).times(2);
|
||||||
|
EasyMock.expect(mockCacheEntry.getHeaders(HeaderConstants.VARY)).andReturn(variantHeaders).times(2);
|
||||||
|
mockExecutor.execute(EasyMock.isA(AsynchronousValidationRequest.class));
|
||||||
|
EasyMock.expectLastCall().times(2);
|
||||||
|
|
||||||
|
replayMocks();
|
||||||
|
impl.revalidateCacheEntry(target, req1, mockContext, mockCacheEntry);
|
||||||
|
impl.revalidateCacheEntry(target, req2, mockContext, mockCacheEntry);
|
||||||
|
verifyMocks();
|
||||||
|
|
||||||
|
Assert.assertEquals(2, impl.getScheduledIdentifiers().size());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRevalidateCacheEntryEndToEnd() throws ProtocolException, IOException, InterruptedException {
|
||||||
|
impl = new AsynchronousValidator(mockClient, 1);
|
||||||
|
|
||||||
|
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
|
||||||
|
EasyMock.expect(mockClient.revalidateCacheEntry(target, request, mockContext, mockCacheEntry)).andReturn(null);
|
||||||
|
|
||||||
|
replayMocks();
|
||||||
|
impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// shut down backend executor and make sure all finishes properly, 1 second should be sufficient
|
||||||
|
ExecutorService implExecutor = impl.getExecutor();
|
||||||
|
implExecutor.shutdown();
|
||||||
|
implExecutor.awaitTermination(1, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
verifyMocks();
|
||||||
|
|
||||||
|
Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replayMocks() {
|
||||||
|
EasyMock.replay(mockExecutor);
|
||||||
|
EasyMock.replay(mockClient);
|
||||||
|
EasyMock.replay(mockContext);
|
||||||
|
EasyMock.replay(mockCacheEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void verifyMocks() {
|
||||||
|
EasyMock.verify(mockExecutor);
|
||||||
|
EasyMock.verify(mockClient);
|
||||||
|
EasyMock.verify(mockContext);
|
||||||
|
EasyMock.verify(mockCacheEntry);
|
||||||
|
}
|
||||||
|
}
|
|
@ -411,7 +411,7 @@ public class TestCacheValidityPolicy {
|
||||||
HttpRequest req = new BasicHttpRequest("GET","/",HttpVersion.HTTP_1_1);
|
HttpRequest req = new BasicHttpRequest("GET","/",HttpVersion.HTTP_1_1);
|
||||||
assertTrue(impl.mayReturnStaleIfError(req, entry, now));
|
assertTrue(impl.mayReturnStaleIfError(req, entry, now));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMayReturnStaleIfErrorInRequestIsTrueWithinStaleness(){
|
public void testMayReturnStaleIfErrorInRequestIsTrueWithinStaleness(){
|
||||||
Header[] headers = new Header[] {
|
Header[] headers = new Header[] {
|
||||||
|
@ -446,4 +446,61 @@ public class TestCacheValidityPolicy {
|
||||||
req.setHeader("Cache-Control","stale-if-error=1");
|
req.setHeader("Cache-Control","stale-if-error=1");
|
||||||
assertFalse(impl.mayReturnStaleIfError(req, entry, now));
|
assertFalse(impl.mayReturnStaleIfError(req, entry, now));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMayReturnStaleWhileRevalidatingIsFalseWhenDirectiveIsAbsent() {
|
||||||
|
Date now = new Date();
|
||||||
|
|
||||||
|
Header[] headers = new Header[] { new BasicHeader("Cache-control", "public") };
|
||||||
|
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
|
||||||
|
|
||||||
|
CacheValidityPolicy impl = new CacheValidityPolicy();
|
||||||
|
|
||||||
|
assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMayReturnStaleWhileRevalidatingIsTrueWhenWithinStaleness() {
|
||||||
|
Date now = new Date();
|
||||||
|
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||||
|
Header[] headers = new Header[] {
|
||||||
|
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
|
||||||
|
new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=15")
|
||||||
|
};
|
||||||
|
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
|
||||||
|
|
||||||
|
CacheValidityPolicy impl = new CacheValidityPolicy();
|
||||||
|
|
||||||
|
assertTrue(impl.mayReturnStaleWhileRevalidating(entry, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMayReturnStaleWhileRevalidatingIsFalseWhenPastStaleness() {
|
||||||
|
Date now = new Date();
|
||||||
|
Date twentyFiveSecondsAgo = new Date(now.getTime() - 25 * 1000L);
|
||||||
|
Header[] headers = new Header[] {
|
||||||
|
new BasicHeader("Date", DateUtils.formatDate(twentyFiveSecondsAgo)),
|
||||||
|
new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=15")
|
||||||
|
};
|
||||||
|
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
|
||||||
|
|
||||||
|
CacheValidityPolicy impl = new CacheValidityPolicy();
|
||||||
|
|
||||||
|
assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMayReturnStaleWhileRevalidatingIsFalseWhenDirectiveEmpty() {
|
||||||
|
Date now = new Date();
|
||||||
|
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||||
|
Header[] headers = new Header[] {
|
||||||
|
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
|
||||||
|
new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=")
|
||||||
|
};
|
||||||
|
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
|
||||||
|
|
||||||
|
CacheValidityPolicy impl = new CacheValidityPolicy();
|
||||||
|
|
||||||
|
assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ import org.easymock.Capture;
|
||||||
import org.easymock.classextension.EasyMock;
|
import org.easymock.classextension.EasyMock;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class TestCachingHttpClient {
|
public class TestCachingHttpClient {
|
||||||
|
@ -340,6 +341,9 @@ public class TestCachingHttpClient {
|
||||||
Assert.assertEquals(0, impl.getCacheUpdates());
|
Assert.assertEquals(0, impl.getCacheUpdates());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: re-enable when background validation enabled by default, or adjust
|
||||||
|
// test to specify background validation in CacheConfig
|
||||||
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
public void testUnsuitableValidatableCacheEntryCausesRevalidation() throws Exception {
|
public void testUnsuitableValidatableCacheEntryCausesRevalidation() throws Exception {
|
||||||
mockImplMethods(REVALIDATE_CACHE_ENTRY);
|
mockImplMethods(REVALIDATE_CACHE_ENTRY);
|
||||||
|
@ -351,6 +355,7 @@ public class TestCachingHttpClient {
|
||||||
getCacheEntryReturns(mockCacheEntry);
|
getCacheEntryReturns(mockCacheEntry);
|
||||||
cacheEntrySuitable(false);
|
cacheEntrySuitable(false);
|
||||||
cacheEntryValidatable(true);
|
cacheEntryValidatable(true);
|
||||||
|
mayReturnStaleWhileRevalidating(false);
|
||||||
revalidateCacheEntryReturns(mockBackendResponse);
|
revalidateCacheEntryReturns(mockBackendResponse);
|
||||||
|
|
||||||
replayMocks();
|
replayMocks();
|
||||||
|
@ -1942,6 +1947,11 @@ public class TestCachingHttpClient {
|
||||||
EasyMock.expect(mockValidityPolicy.isRevalidatable(
|
EasyMock.expect(mockValidityPolicy.isRevalidatable(
|
||||||
EasyMock.<HttpCacheEntry>anyObject())).andReturn(b);
|
EasyMock.<HttpCacheEntry>anyObject())).andReturn(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void mayReturnStaleWhileRevalidating(boolean b) {
|
||||||
|
EasyMock.expect(mockValidityPolicy.mayReturnStaleWhileRevalidating(
|
||||||
|
EasyMock.<HttpCacheEntry>anyObject(), EasyMock.<Date>anyObject())).andReturn(b);
|
||||||
|
}
|
||||||
|
|
||||||
private void conditionalVariantRequestBuilderReturns(Map<String,Variant> variantEntries, HttpRequest validate)
|
private void conditionalVariantRequestBuilderReturns(Map<String,Variant> variantEntries, HttpRequest validate)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
|
@ -27,12 +27,17 @@
|
||||||
package org.apache.http.impl.client.cache;
|
package org.apache.http.impl.client.cache;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.apache.http.Header;
|
||||||
import org.apache.http.HttpRequest;
|
import org.apache.http.HttpRequest;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.apache.http.HttpVersion;
|
||||||
|
import org.apache.http.impl.cookie.DateUtils;
|
||||||
|
import org.apache.http.message.BasicHttpRequest;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,4 +159,49 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
||||||
assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
|
assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
|
||||||
result.getStatusLine().getStatusCode());
|
result.getStatusLine().getStatusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When present in an HTTP response, the stale-while-revalidate Cache-
|
||||||
|
* Control extension indicates that caches MAY serve the response in
|
||||||
|
* which it appears after it becomes stale, up to the indicated number
|
||||||
|
* of seconds.
|
||||||
|
*
|
||||||
|
* http://tools.ietf.org/html/rfc5861
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testStaleWhileRevalidateReturnsStaleEntryWithWarning()
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
params.setStaleWhileRevalidateWorkers(1);
|
||||||
|
impl = new CachingHttpClient(mockBackend, cache, params);
|
||||||
|
|
||||||
|
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||||
|
HttpResponse resp1 = HttpTestUtils.make200Response();
|
||||||
|
Date now = new Date();
|
||||||
|
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||||
|
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
|
||||||
|
resp1.setHeader("ETag","\"etag\"");
|
||||||
|
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
|
||||||
|
|
||||||
|
backendExpectsAnyRequest().andReturn(resp1).times(1,2);
|
||||||
|
|
||||||
|
HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||||
|
|
||||||
|
replayMocks();
|
||||||
|
impl.execute(host, req1);
|
||||||
|
HttpResponse result = impl.execute(host, req2);
|
||||||
|
verifyMocks();
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode());
|
||||||
|
boolean warning110Found = false;
|
||||||
|
for(Header h : result.getHeaders("Warning")) {
|
||||||
|
for(WarningValue wv : WarningValue.getWarningValues(h)) {
|
||||||
|
if (wv.getWarnCode() == 110) {
|
||||||
|
warning110Found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(warning110Found);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue