blob: 5f5f01a18c2b355c0f50180c6f978a1070020ad5 [file] [log] [blame]
/*
* ====================================================================
* 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.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.isA;
import static org.easymock.classextension.EasyMock.createNiceMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.cache.CacheResponseStatus;
import org.apache.hc.client5.http.cache.HttpCacheContext;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.HttpCacheStorage;
import org.apache.hc.client5.http.impl.ExecSupport;
import org.apache.hc.client5.http.protocol.ClientProtocolException;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.sync.ExecChain;
import org.apache.hc.client5.http.sync.ExecChainHandler;
import org.apache.hc.client5.http.sync.ExecRuntime;
import org.apache.hc.client5.http.sync.methods.HttpGet;
import org.apache.hc.client5.http.sync.methods.HttpOptions;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.io.ResponseHandler;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.InputStreamEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.net.URIAuthority;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.easymock.IExpectationSetters;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import junit.framework.AssertionFailedError;
@SuppressWarnings("boxing") // test code
public abstract class TestCachingExecChain {
private ExecChainHandler impl;
protected CacheValidityPolicy mockValidityPolicy;
protected CacheableRequestPolicy mockRequestPolicy;
protected ExecChain mockExecChain;
protected ExecRuntime mockEndpoint;
protected HttpCache mockCache;
private HttpCacheStorage mockStorage;
protected CachedResponseSuitabilityChecker mockSuitabilityChecker;
protected ResponseCachingPolicy mockResponsePolicy;
protected HttpCacheEntry mockCacheEntry;
protected CachedHttpResponseGenerator mockResponseGenerator;
private ResponseHandler<Object> mockHandler;
private ClassicHttpRequest mockUriRequest;
private ClassicHttpResponse mockCachedResponse;
protected ConditionalRequestBuilder mockConditionalRequestBuilder;
private HttpRequest mockConditionalRequest;
protected ResponseProtocolCompliance mockResponseProtocolCompliance;
protected RequestProtocolCompliance mockRequestProtocolCompliance;
protected CacheConfig config;
protected AsynchronousValidator asyncValidator;
protected HttpRoute route;
protected HttpHost host;
protected ClassicHttpRequest request;
protected HttpCacheContext context;
protected HttpCacheEntry entry;
@SuppressWarnings("unchecked")
@Before
public void setUp() {
mockRequestPolicy = createNiceMock(CacheableRequestPolicy.class);
mockValidityPolicy = createNiceMock(CacheValidityPolicy.class);
mockEndpoint = createNiceMock(ExecRuntime.class);
mockExecChain = createNiceMock(ExecChain.class);
mockCache = createNiceMock(HttpCache.class);
mockSuitabilityChecker = createNiceMock(CachedResponseSuitabilityChecker.class);
mockResponsePolicy = createNiceMock(ResponseCachingPolicy.class);
mockHandler = createNiceMock(ResponseHandler.class);
mockUriRequest = createNiceMock(ClassicHttpRequest.class);
mockCacheEntry = createNiceMock(HttpCacheEntry.class);
mockResponseGenerator = createNiceMock(CachedHttpResponseGenerator.class);
mockCachedResponse = createNiceMock(ClassicHttpResponse.class);
mockConditionalRequestBuilder = createNiceMock(ConditionalRequestBuilder.class);
mockConditionalRequest = createNiceMock(HttpRequest.class);
mockResponseProtocolCompliance = createNiceMock(ResponseProtocolCompliance.class);
mockRequestProtocolCompliance = createNiceMock(RequestProtocolCompliance.class);
mockStorage = createNiceMock(HttpCacheStorage.class);
config = CacheConfig.DEFAULT;
asyncValidator = new AsynchronousValidator(config);
host = new HttpHost("foo.example.com", 80);
route = new HttpRoute(host);
request = new BasicClassicHttpRequest("GET", "/stuff");
context = HttpCacheContext.create();
entry = HttpTestUtils.makeCacheEntry();
impl = createCachingExecChain(mockCache, mockValidityPolicy,
mockResponsePolicy, mockResponseGenerator, mockRequestPolicy, mockSuitabilityChecker,
mockConditionalRequestBuilder, mockResponseProtocolCompliance,
mockRequestProtocolCompliance, config, asyncValidator);
}
public abstract ExecChainHandler createCachingExecChain(HttpCache responseCache, CacheValidityPolicy validityPolicy,
ResponseCachingPolicy responseCachingPolicy, CachedHttpResponseGenerator responseGenerator,
CacheableRequestPolicy cacheableRequestPolicy,
CachedResponseSuitabilityChecker suitabilityChecker,
ConditionalRequestBuilder conditionalRequestBuilder,
ResponseProtocolCompliance responseCompliance, RequestProtocolCompliance requestCompliance,
CacheConfig config, AsynchronousValidator asynchRevalidator);
public abstract ExecChainHandler createCachingExecChain(HttpCache cache, CacheConfig config);
protected ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
return impl.execute(ExecSupport.copy(request), new ExecChain.Scope(route, request, mockEndpoint, context), mockExecChain);
}
public static ClassicHttpRequest eqRequest(final ClassicHttpRequest in) {
EasyMock.reportMatcher(new RequestEquivalent(in));
return null;
}
public static <R extends HttpResponse> R eqResponse(final R in) {
EasyMock.reportMatcher(new ResponseEquivalent(in));
return null;
}
protected void replayMocks() {
replay(mockRequestPolicy);
replay(mockValidityPolicy);
replay(mockSuitabilityChecker);
replay(mockResponsePolicy);
replay(mockCacheEntry);
replay(mockResponseGenerator);
replay(mockExecChain);
replay(mockCache);
replay(mockHandler);
replay(mockUriRequest);
replay(mockCachedResponse);
replay(mockConditionalRequestBuilder);
replay(mockConditionalRequest);
replay(mockResponseProtocolCompliance);
replay(mockRequestProtocolCompliance);
replay(mockStorage);
}
protected void verifyMocks() {
verify(mockRequestPolicy);
verify(mockValidityPolicy);
verify(mockSuitabilityChecker);
verify(mockResponsePolicy);
verify(mockCacheEntry);
verify(mockResponseGenerator);
verify(mockExecChain);
verify(mockCache);
verify(mockHandler);
verify(mockUriRequest);
verify(mockCachedResponse);
verify(mockConditionalRequestBuilder);
verify(mockConditionalRequest);
verify(mockResponseProtocolCompliance);
verify(mockRequestProtocolCompliance);
verify(mockStorage);
}
@Test
public void testCacheableResponsesGoIntoCache() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control", "max-age=3600");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
replayMocks();
execute(req1);
execute(req2);
verifyMocks();
}
@Test
public void testOlderCacheableResponsesDoNotGoIntoCache() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final Date now = new Date();
final Date fiveSecondsAgo = new Date(now.getTime() - 5 * 1000L);
final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", DateUtils.formatDate(now));
resp1.setHeader("Cache-Control", "max-age=3600");
resp1.setHeader("Etag", "\"new-etag\"");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "no-cache");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("ETag", "\"old-etag\"");
resp2.setHeader("Date", DateUtils.formatDate(fiveSecondsAgo));
resp2.setHeader("Cache-Control", "max-age=3600");
backendExpectsAnyRequestAndReturn(resp2);
final ClassicHttpRequest req3 = HttpTestUtils.makeDefaultRequest();
replayMocks();
execute(req1);
execute(req2);
final ClassicHttpResponse result = execute(req3);
verifyMocks();
assertEquals("\"new-etag\"", result.getFirstHeader("ETag").getValue());
}
@Test
public void testNewerCacheableResponsesReplaceExistingCacheEntry() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final Date now = new Date();
final Date fiveSecondsAgo = new Date(now.getTime() - 5 * 1000L);
final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", DateUtils.formatDate(fiveSecondsAgo));
resp1.setHeader("Cache-Control", "max-age=3600");
resp1.setHeader("Etag", "\"old-etag\"");
backendExpectsAnyRequestAndReturn(resp1);
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "max-age=0");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("ETag", "\"new-etag\"");
resp2.setHeader("Date", DateUtils.formatDate(now));
resp2.setHeader("Cache-Control", "max-age=3600");
backendExpectsAnyRequestAndReturn(resp2);
final ClassicHttpRequest req3 = HttpTestUtils.makeDefaultRequest();
replayMocks();
execute(req1);
execute(req2);
final ClassicHttpResponse result = execute(req3);
verifyMocks();
assertEquals("\"new-etag\"", result.getFirstHeader("ETag").getValue());
}
protected void requestIsFatallyNonCompliant(final RequestProtocolError error) {
final List<RequestProtocolError> errors = new ArrayList<>();
if (error != null) {
errors.add(error);
}
expect(mockRequestProtocolCompliance.requestIsFatallyNonCompliant(eqRequest(request)))
.andReturn(errors);
}
@Test
public void testSuitableCacheEntryDoesNotCauseBackendRequest() throws Exception {
cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true);
getCacheEntryReturns(mockCacheEntry);
cacheEntrySuitable(true);
responseIsGeneratedFromCache();
requestIsFatallyNonCompliant(null);
entryHasStaleness(0L);
replayMocks();
final ClassicHttpResponse result = execute(request);
verifyMocks();
Assert.assertSame(mockCachedResponse, result);
}
@Test
public void testNonCacheableResponseIsNotCachedAndIsReturnedAsIs() throws Exception {
final CacheConfig configDefault = CacheConfig.DEFAULT;
impl = createCachingExecChain(new BasicHttpCache(new HeapResourceFactory(),
mockStorage, configDefault), configDefault);
final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control", "no-cache");
expect(mockStorage.getEntry(isA(String.class))).andReturn(null).anyTimes();
mockStorage.removeEntry(isA(String.class));
expectLastCall().anyTimes();
backendExpectsAnyRequestAndReturn(resp1);
replayMocks();
final ClassicHttpResponse result = execute(req1);
verifyMocks();
assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result));
}
@Test
public void testResponseIsGeneratedWhenCacheEntryIsUsable() throws Exception {
requestIsFatallyNonCompliant(null);
cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true);
cacheEntrySuitable(true);
getCacheEntryReturns(mockCacheEntry);
responseIsGeneratedFromCache();
entryHasStaleness(0L);
replayMocks();
execute(request);
verifyMocks();
}
@Test
public void testNonCompliantRequestWrapsAndReThrowsProtocolException() throws Exception {
final ClientProtocolException expected = new ClientProtocolException("ouch");
requestIsFatallyNonCompliant(null);
mockRequestProtocolCompliance.makeRequestCompliant((ClassicHttpRequest) anyObject());
expectLastCall().andThrow(expected);
boolean gotException = false;
replayMocks();
try {
execute(request);
} catch (final ClientProtocolException ex) {
Assert.assertSame(expected, ex);
gotException = true;
}
verifyMocks();
Assert.assertTrue(gotException);
}
@Test
public void testSetsModuleGeneratedResponseContextForCacheOptionsResponse() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req = new BasicClassicHttpRequest("OPTIONS", "*");
req.setHeader("Max-Forwards", "0");
execute(req);
Assert.assertEquals(CacheResponseStatus.CACHE_MODULE_RESPONSE,
context.getCacheResponseStatus());
}
@Test
public void testSetsModuleGeneratedResponseContextForFatallyNoncompliantRequest() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req = new HttpGet("http://foo.example.com/");
req.setHeader("Range", "bytes=0-50");
req.setHeader("If-Range", "W/\"weak-etag\"");
execute(req);
Assert.assertEquals(CacheResponseStatus.CACHE_MODULE_RESPONSE,
context.getCacheResponseStatus());
}
@Test
public void testRecordsClientProtocolInViaHeaderIfRequestNotServableFromCache() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest originalRequest = new BasicClassicHttpRequest("GET", "/");
originalRequest.setVersion(HttpVersion.HTTP_1_0);
final ClassicHttpRequest req = originalRequest;
req.setHeader("Cache-Control", "no-cache");
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
final Capture<ClassicHttpRequest> cap = new Capture<>();
backendCaptureRequestAndReturn(cap, resp);
replayMocks();
execute(req);
verifyMocks();
final HttpRequest captured = cap.getValue();
final String via = captured.getFirstHeader("Via").getValue();
final String proto = via.split("\\s+")[0];
Assert.assertTrue("http/1.0".equalsIgnoreCase(proto) || "1.0".equalsIgnoreCase(proto));
}
@Test
public void testSetsCacheMissContextIfRequestNotServableFromCache() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req = new HttpGet("http://foo.example.com/");
req.setHeader("Cache-Control", "no-cache");
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
backendExpectsAnyRequestAndReturn(resp);
replayMocks();
execute(req);
verifyMocks();
Assert.assertEquals(CacheResponseStatus.CACHE_MISS, context.getCacheResponseStatus());
}
@Test
public void testSetsViaHeaderOnResponseIfRequestNotServableFromCache() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req = new HttpGet("http://foo.example.com/");
req.setHeader("Cache-Control", "no-cache");
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
backendExpectsAnyRequestAndReturn(resp);
replayMocks();
final ClassicHttpResponse result = execute(req);
verifyMocks();
Assert.assertNotNull(result.getFirstHeader("Via"));
}
@Test
public void testSetsViaHeaderOnResponseForCacheMiss() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(new Date()));
resp1.setHeader("Cache-Control", "public, max-age=3600");
backendExpectsAnyRequestAndReturn(resp1);
replayMocks();
final ClassicHttpResponse result = execute(req1);
verifyMocks();
Assert.assertNotNull(result.getFirstHeader("Via"));
}
@Test
public void testSetsCacheHitContextIfRequestServedFromCache() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(new Date()));
resp1.setHeader("Cache-Control", "public, max-age=3600");
backendExpectsAnyRequestAndReturn(resp1);
replayMocks();
execute(req1);
execute(req2);
verifyMocks();
Assert.assertEquals(CacheResponseStatus.CACHE_HIT, context.getCacheResponseStatus());
}
@Test
public void testSetsViaHeaderOnResponseIfRequestServedFromCache() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(new Date()));
resp1.setHeader("Cache-Control", "public, max-age=3600");
backendExpectsAnyRequestAndReturn(resp1);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertNotNull(result.getFirstHeader("Via"));
}
@Test
public void testReturns304ForIfModifiedSinceHeaderIfRequestServedFromCache() throws Exception {
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
req2.addHeader("If-Modified-Since", DateUtils.formatDate(now));
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=3600");
resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
backendExpectsAnyRequestAndReturn(resp1);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
}
@Test
public void testReturns304ForIfModifiedSinceHeaderIf304ResponseInCache() throws Exception {
final Date now = new Date();
final Date oneHourAgo = new Date(now.getTime() - 3600 * 1000L);
final Date inTenMinutes = new Date(now.getTime() + 600 * 1000L);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
req1.addHeader("If-Modified-Since", DateUtils.formatDate(oneHourAgo));
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
req2.addHeader("If-Modified-Since", DateUtils.formatDate(oneHourAgo));
final ClassicHttpResponse resp1 = HttpTestUtils.make304Response();
resp1.setHeader("Date", DateUtils.formatDate(now));
resp1.setHeader("Cache-control", "max-age=600");
resp1.setHeader("Expires", DateUtils.formatDate(inTenMinutes));
expect(
mockExecChain.proceed(isA(ClassicHttpRequest.class), isA(ExecChain.Scope.class))).andReturn(resp1).once();
expect(
mockExecChain.proceed(isA(ClassicHttpRequest.class), isA(ExecChain.Scope.class))).andThrow(
new AssertionFailedError("Should have reused cached 304 response")).anyTimes();
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
Assert.assertFalse(result.containsHeader("Last-Modified"));
}
@Test
public void testReturns200ForIfModifiedSinceDateIsLess() throws Exception {
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(new Date()));
resp1.setHeader("Cache-Control", "public, max-age=3600");
resp1.setHeader("Last-Modified", DateUtils.formatDate(new Date()));
// The variant has been modified since this date
req2.addHeader("If-Modified-Since", DateUtils.formatDate(tenSecondsAgo));
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_OK, result.getCode());
}
@Test
public void testReturns200ForIfModifiedSinceDateIsInvalid() throws Exception {
final Date now = new Date();
final Date tenSecondsAfter = new Date(now.getTime() + 10 * 1000L);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(new Date()));
resp1.setHeader("Cache-Control", "public, max-age=3600");
resp1.setHeader("Last-Modified", DateUtils.formatDate(new Date()));
// invalid date (date in the future)
req2.addHeader("If-Modified-Since", DateUtils.formatDate(tenSecondsAfter));
backendExpectsAnyRequestAndReturn(resp1).times(2);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_OK, result.getCode());
}
@Test
public void testReturns304ForIfNoneMatchHeaderIfRequestServedFromCache() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
req2.addHeader("If-None-Match", "*");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(new Date()));
resp1.setHeader("Cache-Control", "public, max-age=3600");
backendExpectsAnyRequestAndReturn(resp1);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
}
@Test
public void testReturns200ForIfNoneMatchHeaderFails() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(new Date()));
resp1.setHeader("Cache-Control", "public, max-age=3600");
req2.addHeader("If-None-Match", "\"abc\"");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(200, result.getCode());
}
@Test
public void testReturns304ForIfNoneMatchHeaderAndIfModifiedSinceIfRequestServedFromCache() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=3600");
resp1.setHeader("Last-Modified", DateUtils.formatDate(new Date()));
req2.addHeader("If-None-Match", "*");
req2.addHeader("If-Modified-Since", DateUtils.formatDate(now));
backendExpectsAnyRequestAndReturn(resp1);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
}
@Test
public void testReturns200ForIfNoneMatchHeaderFailsIfModifiedSinceIgnored() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
req2.addHeader("If-None-Match", "\"abc\"");
req2.addHeader("If-Modified-Since", DateUtils.formatDate(now));
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=3600");
resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp1);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(200, result.getCode());
}
@Test
public void testReturns200ForOptionsFollowedByGetIfAuthorizationHeaderAndSharedCache() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.custom()
.setSharedCache(true).build());
final Date now = new Date();
final ClassicHttpRequest req1 = new HttpOptions("http://foo.example.com/");
req1.setHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
req2.setHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
resp1.setHeader("Content-Length", "0");
resp1.setHeader("ETag", "\"options-etag\"");
resp1.setHeader("Date", DateUtils.formatDate(now));
resp1.setHeader("Cache-Control", "public, max-age=3600");
resp1.setHeader("Last-Modified", DateUtils.formatDate(now));
final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"get-etag\"");
resp1.setHeader("Date", DateUtils.formatDate(now));
resp1.setHeader("Cache-Control", "public, max-age=3600");
resp1.setHeader("Last-Modified", DateUtils.formatDate(now));
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(200, result.getCode());
}
@Test
public void testSetsValidatedContextIfRequestWasSuccessfullyValidated() throws Exception {
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=5");
final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp2.setEntity(HttpTestUtils.makeBody(128));
resp2.setHeader("Content-Length", "128");
resp2.setHeader("ETag", "\"etag\"");
resp2.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp2.setHeader("Cache-Control", "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
replayMocks();
execute(req1);
execute(req2);
verifyMocks();
Assert.assertEquals(CacheResponseStatus.VALIDATED, context.getCacheResponseStatus());
}
@Test
public void testSetsViaHeaderIfRequestWasSuccessfullyValidated() throws Exception {
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=5");
final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp2.setEntity(HttpTestUtils.makeBody(128));
resp2.setHeader("Content-Length", "128");
resp2.setHeader("ETag", "\"etag\"");
resp2.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp2.setHeader("Cache-Control", "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertNotNull(result.getFirstHeader("Via"));
}
@Test
public void testSetsModuleResponseContextIfValidationRequiredButFailed() throws Exception {
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=5, must-revalidate");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndThrows(new IOException());
replayMocks();
execute(req1);
execute(req2);
verifyMocks();
Assert.assertEquals(CacheResponseStatus.CACHE_MODULE_RESPONSE,
context.getCacheResponseStatus());
}
@Test
public void testSetsModuleResponseContextIfValidationFailsButNotRequired() throws Exception {
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndThrows(new IOException());
replayMocks();
execute(req1);
execute(req2);
verifyMocks();
Assert.assertEquals(CacheResponseStatus.CACHE_HIT, context.getCacheResponseStatus());
}
@Test
public void testSetViaHeaderIfValidationFailsButNotRequired() throws Exception {
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndThrows(new IOException());
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertNotNull(result.getFirstHeader("Via"));
}
@Test
public void testReturns304ForIfNoneMatchPassesIfRequestServedFromOrigin() throws Exception {
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=5");
req2.addHeader("If-None-Match", "\"etag\"");
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
resp2.setHeader("ETag", "\"etag\"");
resp2.setHeader("Date", DateUtils.formatDate(now));
resp2.setHeader("Cache-Control", "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
}
@Test
public void testReturns200ForIfNoneMatchFailsIfRequestServedFromOrigin() throws Exception {
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=5");
req2.addHeader("If-None-Match", "\"etag\"");
final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp2.setEntity(HttpTestUtils.makeBody(128));
resp2.setHeader("Content-Length", "128");
resp2.setHeader("ETag", "\"newetag\"");
resp2.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp2.setHeader("Cache-Control", "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_OK, result.getCode());
}
@Test
public void testReturns304ForIfModifiedSincePassesIfRequestServedFromOrigin() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=5");
req2.addHeader("If-Modified-Since", DateUtils.formatDate(tenSecondsAgo));
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
resp2.setHeader("ETag", "\"etag\"");
resp2.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
resp2.setHeader("Cache-Control", "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
}
@Test
public void testReturns200ForIfModifiedSinceFailsIfRequestServedFromOrigin() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=5");
req2.addHeader("If-Modified-Since", DateUtils.formatDate(tenSecondsAgo));
final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp2.setEntity(HttpTestUtils.makeBody(128));
resp2.setHeader("Content-Length", "128");
resp2.setHeader("ETag", "\"newetag\"");
resp2.setHeader("Date", DateUtils.formatDate(now));
resp1.setHeader("Last-Modified", DateUtils.formatDate(now));
resp2.setHeader("Cache-Control", "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
replayMocks();
execute(req1);
final ClassicHttpResponse result = execute(req2);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_OK, result.getCode());
}
@Test
public void testVariantMissServerIfReturns304CacheReturns200() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final Date now = new Date();
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com");
req1.addHeader("Accept-Encoding", "gzip");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("Etag", "\"gzip_etag\"");
resp1.setHeader("Date", DateUtils.formatDate(now));
resp1.setHeader("Vary", "Accept-Encoding");
resp1.setHeader("Cache-Control", "public, max-age=3600");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com");
req2.addHeader("Accept-Encoding", "deflate");
final ClassicHttpRequest req2Server = new HttpGet("http://foo.example.com");
req2Server.addHeader("Accept-Encoding", "deflate");
req2Server.addHeader("If-None-Match", "\"gzip_etag\"");
final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp2.setEntity(HttpTestUtils.makeBody(128));
resp2.setHeader("Content-Length", "128");
resp2.setHeader("Etag", "\"deflate_etag\"");
resp2.setHeader("Date", DateUtils.formatDate(now));
resp2.setHeader("Vary", "Accept-Encoding");
resp2.setHeader("Cache-Control", "public, max-age=3600");
final ClassicHttpRequest req3 = new HttpGet("http://foo.example.com");
req3.addHeader("Accept-Encoding", "gzip,deflate");
final ClassicHttpRequest req3Server = new HttpGet("http://foo.example.com");
req3Server.addHeader("Accept-Encoding", "gzip,deflate");
req3Server.addHeader("If-None-Match", "\"gzip_etag\",\"deflate_etag\"");
final ClassicHttpResponse resp3 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp3.setEntity(HttpTestUtils.makeBody(128));
resp3.setHeader("Content-Length", "128");
resp3.setHeader("Etag", "\"gzip_etag\"");
resp3.setHeader("Date", DateUtils.formatDate(now));
resp3.setHeader("Vary", "Accept-Encoding");
resp3.setHeader("Cache-Control", "public, max-age=3600");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
backendExpectsAnyRequestAndReturn(resp3);
replayMocks();
final ClassicHttpResponse result1 = execute(req1);
final ClassicHttpResponse result2 = execute(req2);
final ClassicHttpResponse result3 = execute(req3);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_OK, result1.getCode());
Assert.assertEquals(HttpStatus.SC_OK, result2.getCode());
Assert.assertEquals(HttpStatus.SC_OK, result3.getCode());
}
@Test
public void testVariantsMissServerReturns304CacheReturns304() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final Date now = new Date();
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com");
req1.addHeader("Accept-Encoding", "gzip");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(HttpTestUtils.makeBody(128));
resp1.setHeader("Content-Length", "128");
resp1.setHeader("Etag", "\"gzip_etag\"");
resp1.setHeader("Date", DateUtils.formatDate(now));
resp1.setHeader("Vary", "Accept-Encoding");
resp1.setHeader("Cache-Control", "public, max-age=3600");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com");
req2.addHeader("Accept-Encoding", "deflate");
final ClassicHttpRequest req2Server = new HttpGet("http://foo.example.com");
req2Server.addHeader("Accept-Encoding", "deflate");
req2Server.addHeader("If-None-Match", "\"gzip_etag\"");
final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp2.setEntity(HttpTestUtils.makeBody(128));
resp2.setHeader("Content-Length", "128");
resp2.setHeader("Etag", "\"deflate_etag\"");
resp2.setHeader("Date", DateUtils.formatDate(now));
resp2.setHeader("Vary", "Accept-Encoding");
resp2.setHeader("Cache-Control", "public, max-age=3600");
final ClassicHttpRequest req4 = new HttpGet("http://foo.example.com");
req4.addHeader("Accept-Encoding", "gzip,identity");
req4.addHeader("If-None-Match", "\"gzip_etag\"");
final ClassicHttpRequest req4Server = new HttpGet("http://foo.example.com");
req4Server.addHeader("Accept-Encoding", "gzip,identity");
req4Server.addHeader("If-None-Match", "\"gzip_etag\"");
final ClassicHttpResponse resp4 = HttpTestUtils.make304Response();
resp4.setHeader("Etag", "\"gzip_etag\"");
resp4.setHeader("Date", DateUtils.formatDate(now));
resp4.setHeader("Vary", "Accept-Encoding");
resp4.setHeader("Cache-Control", "public, max-age=3600");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
backendExpectsAnyRequestAndReturn(resp4);
replayMocks();
final ClassicHttpResponse result1 = execute(req1);
final ClassicHttpResponse result2 = execute(req2);
final ClassicHttpResponse result4 = execute(req4);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_OK, result1.getCode());
Assert.assertEquals(HttpStatus.SC_OK, result2.getCode());
Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result4.getCode());
}
@Test
public void testSocketTimeoutExceptionIsNotSilentlyCatched() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final Date now = new Date();
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com");
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"OK");
resp1.setEntity(new InputStreamEntity(new InputStream() {
private boolean closed = false;
@Override
public void close() throws IOException {
closed = true;
}
@Override
public int read() throws IOException {
if (closed) {
throw new SocketException("Socket closed");
}
throw new SocketTimeoutException("Read timed out");
}
}, 128));
resp1.setHeader("Date", DateUtils.formatDate(now));
backendExpectsAnyRequestAndReturn(resp1);
replayMocks();
try {
final ClassicHttpResponse result1 = execute(req1);
EntityUtils.toString(result1.getEntity());
Assert.fail("We should have had a SocketTimeoutException");
} catch (final SocketTimeoutException e) {
}
verifyMocks();
}
@Test
public void testIsSharedCache() {
Assert.assertTrue(config.isSharedCache());
}
@Test
public void testTreatsCacheIOExceptionsAsCacheMiss() throws Exception {
impl = createCachingExecChain(mockCache, CacheConfig.DEFAULT);
final ClassicHttpResponse resp = HttpTestUtils.make200Response();
mockCache.flushInvalidatedCacheEntriesFor(host, request);
expectLastCall().andThrow(new IOException()).anyTimes();
mockCache.flushInvalidatedCacheEntriesFor(isA(HttpHost.class), isA(HttpRequest.class),
isA(HttpResponse.class));
expectLastCall().anyTimes();
expect(mockCache.getCacheEntry(eq(host), isA(HttpRequest.class))).andThrow(
new IOException()).anyTimes();
expect(mockCache.getVariantCacheEntriesWithEtags(eq(host), isA(HttpRequest.class)))
.andThrow(new IOException()).anyTimes();
expect(
mockCache.cacheAndReturnResponse(eq(host), isA(HttpRequest.class),
isA(ClassicHttpResponse.class), isA(Date.class), isA(Date.class)))
.andReturn(resp).anyTimes();
expect(
mockExecChain.proceed(isA(ClassicHttpRequest.class), isA(ExecChain.Scope.class))).andReturn(resp);
replayMocks();
final ClassicHttpResponse result = execute(request);
verifyMocks();
Assert.assertSame(resp, result);
}
@Test
public void testIfOnlyIfCachedAndNoCacheEntryBackendNotCalled() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
request.addHeader("Cache-Control", "only-if-cached");
final ClassicHttpResponse resp = execute(request);
Assert.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, resp.getCode());
}
@Test
public void testIfOnlyIfCachedAndEntryNotSuitableBackendNotCalled() throws Exception {
request.setHeader("Cache-Control", "only-if-cached");
entry = HttpTestUtils.makeCacheEntry(new Header[] { new BasicHeader("Cache-Control",
"must-revalidate") });
requestIsFatallyNonCompliant(null);
cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true);
getCacheEntryReturns(entry);
cacheEntrySuitable(false);
replayMocks();
final ClassicHttpResponse resp = execute(request);
verifyMocks();
Assert.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, resp.getCode());
}
@Test
public void testIfOnlyIfCachedAndEntryExistsAndIsSuitableReturnsEntry() throws Exception {
request.setHeader("Cache-Control", "only-if-cached");
requestIsFatallyNonCompliant(null);
cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true);
getCacheEntryReturns(entry);
cacheEntrySuitable(true);
responseIsGeneratedFromCache();
entryHasStaleness(0);
replayMocks();
final ClassicHttpResponse resp = execute(request);
verifyMocks();
Assert.assertSame(mockCachedResponse, resp);
}
@Test
public void testDoesNotSetConnectionInContextOnCacheHit() throws Exception {
final DummyBackend backend = new DummyBackend();
final ClassicHttpResponse response = HttpTestUtils.make200Response();
response.setHeader("Cache-Control", "max-age=3600");
backend.setResponse(response);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final HttpClientContext ctx = HttpClientContext.create();
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, context), backend);
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, ctx), backend);
}
@Test
public void testSetsTargetHostInContextOnCacheHit() throws Exception {
final DummyBackend backend = new DummyBackend();
final ClassicHttpResponse response = HttpTestUtils.make200Response();
response.setHeader("Cache-Control", "max-age=3600");
backend.setResponse(response);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final HttpClientContext ctx = HttpClientContext.create();
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, context), backend);
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, ctx), backend);
}
@Test
public void testSetsRouteInContextOnCacheHit() throws Exception {
final DummyBackend backend = new DummyBackend();
final ClassicHttpResponse response = HttpTestUtils.make200Response();
response.setHeader("Cache-Control", "max-age=3600");
backend.setResponse(response);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final HttpClientContext ctx = HttpClientContext.create();
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, context), backend);
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, ctx), backend);
assertEquals(route, ctx.getHttpRoute());
}
@Test
public void testSetsRequestInContextOnCacheHit() throws Exception {
final DummyBackend backend = new DummyBackend();
final ClassicHttpResponse response = HttpTestUtils.make200Response();
response.setHeader("Cache-Control", "max-age=3600");
backend.setResponse(response);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final HttpClientContext ctx = HttpClientContext.create();
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, context), backend);
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, ctx), backend);
if (!HttpTestUtils.equivalent(request, ctx.getRequest())) {
assertSame(request, ctx.getRequest());
}
}
@Test
public void testSetsResponseInContextOnCacheHit() throws Exception {
final DummyBackend backend = new DummyBackend();
final ClassicHttpResponse response = HttpTestUtils.make200Response();
response.setHeader("Cache-Control", "max-age=3600");
backend.setResponse(response);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final HttpClientContext ctx = HttpClientContext.create();
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, context), backend);
final ClassicHttpResponse result = impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, ctx), null);
if (!HttpTestUtils.equivalent(result, ctx.getResponse())) {
assertSame(result, ctx.getResponse());
}
}
@Test
public void testSetsRequestSentInContextOnCacheHit() throws Exception {
final DummyBackend backend = new DummyBackend();
final ClassicHttpResponse response = HttpTestUtils.make200Response();
response.setHeader("Cache-Control", "max-age=3600");
backend.setResponse(response);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final HttpClientContext ctx = HttpClientContext.create();
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, context), backend);
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, ctx), backend);
}
@Test
public void testCanCacheAResponseWithoutABody() throws Exception {
final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
response.setHeader("Date", DateUtils.formatDate(new Date()));
response.setHeader("Cache-Control", "max-age=300");
final DummyBackend backend = new DummyBackend();
backend.setResponse(response);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, context), backend);
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, context), backend);
assertEquals(1, backend.getExecutions());
}
@Test
public void testNoEntityForIfNoneMatchRequestNotYetInCache() throws Exception {
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
req1.addHeader("If-None-Match", "\"etag\"");
final ClassicHttpResponse resp1 = HttpTestUtils.make304Response();
resp1.setHeader("Content-Length", "128");
resp1.setHeader("ETag", "\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
replayMocks();
final ClassicHttpResponse result = execute(req1);
verifyMocks();
assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
assertNull("The 304 response messages MUST NOT contain a message-body", result.getEntity());
}
@Test
public void testNotModifiedResponseUpdatesCacheEntryWhenNoEntity() throws Exception {
final Date now = new Date();
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
req1.addHeader("If-None-Match", "etag");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
req2.addHeader("If-None-Match", "etag");
final ClassicHttpResponse resp1 = HttpTestUtils.make304Response();
resp1.setHeader("Date", DateUtils.formatDate(now));
resp1.setHeader("Cache-Control", "max-age=0");
resp1.setHeader("Etag", "etag");
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
resp2.setHeader("Date", DateUtils.formatDate(now));
resp2.setHeader("Cache-Control", "max-age=0");
resp1.setHeader("Etag", "etag");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
replayMocks();
final ClassicHttpResponse result1 = execute(req1);
final ClassicHttpResponse result2 = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_NOT_MODIFIED, result1.getCode());
assertEquals("etag", result1.getFirstHeader("Etag").getValue());
assertEquals(HttpStatus.SC_NOT_MODIFIED, result2.getCode());
assertEquals("etag", result2.getFirstHeader("Etag").getValue());
}
@Test
public void testNotModifiedResponseWithVaryUpdatesCacheEntryWhenNoEntity() throws Exception {
final Date now = new Date();
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
req1.addHeader("If-None-Match", "etag");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
req2.addHeader("If-None-Match", "etag");
final ClassicHttpResponse resp1 = HttpTestUtils.make304Response();
resp1.setHeader("Date", DateUtils.formatDate(now));
resp1.setHeader("Cache-Control", "max-age=0");
resp1.setHeader("Etag", "etag");
resp1.setHeader("Vary", "Accept-Encoding");
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
resp2.setHeader("Date", DateUtils.formatDate(now));
resp2.setHeader("Cache-Control", "max-age=0");
resp1.setHeader("Etag", "etag");
resp1.setHeader("Vary", "Accept-Encoding");
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2);
replayMocks();
final ClassicHttpResponse result1 = execute(req1);
final ClassicHttpResponse result2 = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_NOT_MODIFIED, result1.getCode());
assertEquals("etag", result1.getFirstHeader("Etag").getValue());
assertEquals(HttpStatus.SC_NOT_MODIFIED, result2.getCode());
assertEquals("etag", result2.getFirstHeader("Etag").getValue());
}
@Test
public void testDoesNotSend304ForNonConditionalRequest() throws Exception {
final Date now = new Date();
final Date inOneMinute = new Date(System.currentTimeMillis() + 60000);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
req1.addHeader("If-None-Match", "etag");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
final ClassicHttpResponse resp1 = HttpTestUtils.make304Response();
resp1.setHeader("Date", DateUtils.formatDate(now));
resp1.setHeader("Cache-Control", "public, max-age=60");
resp1.setHeader("Expires", DateUtils.formatDate(inOneMinute));
resp1.setHeader("Etag", "etag");
resp1.setHeader("Vary", "Accept-Encoding");
final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_OK,
"Ok");
resp2.setHeader("Date", DateUtils.formatDate(now));
resp2.setHeader("Cache-Control", "public, max-age=60");
resp2.setHeader("Expires", DateUtils.formatDate(inOneMinute));
resp2.setHeader("Etag", "etag");
resp2.setHeader("Vary", "Accept-Encoding");
resp2.setEntity(HttpTestUtils.makeBody(128));
backendExpectsAnyRequestAndReturn(resp1);
backendExpectsAnyRequestAndReturn(resp2).anyTimes();
replayMocks();
final ClassicHttpResponse result1 = execute(req1);
final ClassicHttpResponse result2 = execute(req2);
verifyMocks();
assertEquals(HttpStatus.SC_NOT_MODIFIED, result1.getCode());
assertNull(result1.getEntity());
assertEquals(HttpStatus.SC_OK, result2.getCode());
Assert.assertNotNull(result2.getEntity());
}
@Test
public void testUsesVirtualHostForCacheKey() throws Exception {
final DummyBackend backend = new DummyBackend();
final ClassicHttpResponse response = HttpTestUtils.make200Response();
response.setHeader("Cache-Control", "max-age=3600");
backend.setResponse(response);
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, context), backend);
assertEquals(1, backend.getExecutions());
request.setAuthority(new URIAuthority("bar.example.com"));
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, context), backend);
assertEquals(2, backend.getExecutions());
impl.execute(request, new ExecChain.Scope(route, request, mockEndpoint, context), backend);
assertEquals(2, backend.getExecutions());
}
protected IExpectationSetters<ClassicHttpResponse> backendExpectsRequestAndReturn(
final ClassicHttpRequest request, final ClassicHttpResponse response) throws Exception {
final ClassicHttpResponse resp = mockExecChain.proceed(
EasyMock.eq(request), EasyMock.isA(ExecChain.Scope.class));
return EasyMock.expect(resp).andReturn(response);
}
private IExpectationSetters<ClassicHttpResponse> backendExpectsAnyRequestAndReturn(
final ClassicHttpResponse response) throws Exception {
final ClassicHttpResponse resp = mockExecChain.proceed(
EasyMock.isA(ClassicHttpRequest.class), EasyMock.isA(ExecChain.Scope.class));
return EasyMock.expect(resp).andReturn(response);
}
protected IExpectationSetters<ClassicHttpResponse> backendExpectsAnyRequestAndThrows(
final Throwable throwable) throws Exception {
final ClassicHttpResponse resp = mockExecChain.proceed(
EasyMock.isA(ClassicHttpRequest.class), EasyMock.isA(ExecChain.Scope.class));
return EasyMock.expect(resp).andThrow(throwable);
}
protected IExpectationSetters<ClassicHttpResponse> backendCaptureRequestAndReturn(
final Capture<ClassicHttpRequest> cap, final ClassicHttpResponse response) throws Exception {
final ClassicHttpResponse resp = mockExecChain.proceed(
EasyMock.capture(cap), EasyMock.isA(ExecChain.Scope.class));
return EasyMock.expect(resp).andReturn(response);
}
protected void getCacheEntryReturns(final HttpCacheEntry result) throws IOException {
expect(mockCache.getCacheEntry(eq(host), eqRequest(request))).andReturn(result);
}
private void cacheInvalidatorWasCalled() throws IOException {
mockCache
.flushInvalidatedCacheEntriesFor((HttpHost) anyObject(), (HttpRequest) anyObject());
}
protected void cacheEntryValidatable(final boolean b) {
expect(mockValidityPolicy.isRevalidatable((HttpCacheEntry) anyObject())).andReturn(b)
.anyTimes();
}
protected void cacheEntryMustRevalidate(final boolean b) {
expect(mockValidityPolicy.mustRevalidate(mockCacheEntry)).andReturn(b);
}
protected void cacheEntryProxyRevalidate(final boolean b) {
expect(mockValidityPolicy.proxyRevalidate(mockCacheEntry)).andReturn(b);
}
protected void mayReturnStaleWhileRevalidating(final boolean b) {
expect(
mockValidityPolicy.mayReturnStaleWhileRevalidating((HttpCacheEntry) anyObject(),
(Date) anyObject())).andReturn(b);
}
protected void conditionalRequestBuilderReturns(final ClassicHttpRequest validate) throws Exception {
expect(mockConditionalRequestBuilder.buildConditionalRequest(request, entry)).andReturn(
validate);
}
protected void requestPolicyAllowsCaching(final boolean allow) {
expect(mockRequestPolicy.isServableFromCache((HttpRequest) anyObject())).andReturn(allow);
}
protected void cacheEntrySuitable(final boolean suitable) {
expect(
mockSuitabilityChecker.canCachedResponseBeUsed((HttpHost) anyObject(),
(HttpRequest) anyObject(), (HttpCacheEntry) anyObject(), (Date) anyObject()))
.andReturn(suitable);
}
private void entryHasStaleness(final long staleness) {
expect(
mockValidityPolicy.getStalenessSecs((HttpCacheEntry) anyObject(), (Date) anyObject()))
.andReturn(staleness);
}
protected void responseIsGeneratedFromCache() {
expect(
mockResponseGenerator.generateResponse((ClassicHttpRequest) anyObject(), (HttpCacheEntry) anyObject()))
.andReturn(mockCachedResponse);
}
}