You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2010/04/30 23:00:10 UTC
svn commit: r939814 [4/6] - in /httpcomponents/httpclient/trunk: ./
httpclient-cache/ httpclient-cache/src/ httpclient-cache/src/main/
httpclient-cache/src/main/java/ httpclient-cache/src/main/java/org/
httpclient-cache/src/main/java/org/apache/ httpcl...
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachedResponseSuitabilityChecker.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachedResponseSuitabilityChecker.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachedResponseSuitabilityChecker.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachedResponseSuitabilityChecker.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,257 @@
+/*
+ * ====================================================================
+ * 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.client.cache.impl;
+
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.client.cache.impl.CacheEntry;
+import org.apache.http.client.cache.impl.CachedResponseSuitabilityChecker;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicHttpRequest;
+import org.easymock.classextension.EasyMock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestCachedResponseSuitabilityChecker {
+
+ private CachedResponseSuitabilityChecker impl;
+ private HttpHost host;
+ private HttpRequest request;
+ private CacheEntry mockEntry;
+ private HttpRequest mockRequest;
+
+ @Before
+ public void setUp() {
+ host = new HttpHost("foo.example.com");
+ request = new BasicHttpRequest("GET", "/foo");
+ mockEntry = EasyMock.createMock(CacheEntry.class);
+ mockRequest = EasyMock.createMock(HttpRequest.class);
+
+ impl = new CachedResponseSuitabilityChecker();
+ }
+
+ public void replayMocks() {
+ EasyMock.replay(mockEntry, mockRequest);
+ }
+
+ public void verifyMocks() {
+ EasyMock.verify(mockEntry, mockRequest);
+ }
+
+ @Test
+ public void testNotSuitableIfContentLengthHeaderIsWrong() {
+ responseIsFresh(true);
+ contentLengthMatchesActualLength(false);
+
+ replayMocks();
+ boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
+
+ verifyMocks();
+
+ Assert.assertFalse(result);
+ }
+
+ @Test
+ public void testSuitableIfContentLengthHeaderIsRight() {
+ responseIsFresh(true);
+ contentLengthMatchesActualLength(true);
+ modifiedSince(false, request);
+
+ replayMocks();
+ boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
+
+ verifyMocks();
+
+ Assert.assertTrue(result);
+ }
+
+ @Test
+ public void testSuitableIfCacheEntryIsFresh() {
+ responseIsFresh(true);
+ contentLengthMatchesActualLength(true);
+ modifiedSince(false, request);
+
+ replayMocks();
+
+ boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
+
+ verifyMocks();
+
+ Assert.assertTrue(result);
+ }
+
+ @Test
+ public void testNotSuitableIfCacheEntryIsNotFresh() {
+ responseIsFresh(false);
+ replayMocks();
+
+ boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
+
+ verifyMocks();
+
+ Assert.assertFalse(result);
+ }
+
+ @Test
+ public void testNotSuitableIfRequestHasNoCache() {
+ request.addHeader("Cache-Control", "no-cache");
+ responseIsFresh(true);
+ contentLengthMatchesActualLength(true);
+ modifiedSince(false, request);
+
+ replayMocks();
+
+ boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
+ verifyMocks();
+ Assert.assertFalse(result);
+ }
+
+ @Test
+ public void testNotSuitableIfAgeExceedsRequestMaxAge() {
+ request.addHeader("Cache-Control", "max-age=10");
+ responseIsFresh(true);
+ contentLengthMatchesActualLength(true);
+ modifiedSince(false, request);
+
+ org.easymock.EasyMock.expect(mockEntry.getCurrentAgeSecs()).andReturn(20L);
+ replayMocks();
+
+ boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
+ verifyMocks();
+ Assert.assertFalse(result);
+ }
+
+ @Test
+ public void testSuitableIfFreshAndAgeIsUnderRequestMaxAge() {
+ request.addHeader("Cache-Control", "max-age=10");
+ responseIsFresh(true);
+ contentLengthMatchesActualLength(true);
+ modifiedSince(false, request);
+
+ org.easymock.EasyMock.expect(mockEntry.getCurrentAgeSecs()).andReturn(5L);
+ replayMocks();
+
+ boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
+ verifyMocks();
+ Assert.assertTrue(result);
+ }
+
+ @Test
+ public void testSuitableIfFreshAndFreshnessLifetimeGreaterThanRequestMinFresh() {
+ request.addHeader("Cache-Control", "min-fresh=10");
+ responseIsFresh(true);
+ contentLengthMatchesActualLength(true);
+ modifiedSince(false, request);
+
+ org.easymock.EasyMock.expect(mockEntry.getFreshnessLifetimeSecs()).andReturn(15L);
+ replayMocks();
+
+ boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
+ verifyMocks();
+ Assert.assertTrue(result);
+ }
+
+ @Test
+ public void testNotSuitableIfFreshnessLifetimeLessThanRequestMinFresh() {
+ request.addHeader("Cache-Control", "min-fresh=10");
+ responseIsFresh(true);
+ contentLengthMatchesActualLength(true);
+ modifiedSince(false, request);
+
+ org.easymock.EasyMock.expect(mockEntry.getFreshnessLifetimeSecs()).andReturn(5L);
+ replayMocks();
+
+ boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
+ verifyMocks();
+ Assert.assertFalse(result);
+ }
+
+ // this is compliant but possibly misses some cache hits; would
+ // need to change logic to add Warning header if we allowed this
+ @Test
+ public void testNotSuitableEvenIfStaleButPermittedByRequestMaxStale() {
+ request.addHeader("Cache-Control", "max-stale=10");
+ responseIsFresh(false);
+
+ replayMocks();
+
+ boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
+ verifyMocks();
+ Assert.assertFalse(result);
+ }
+
+ @Test
+ public void testMalformedCacheControlMaxAgeRequestHeaderCausesUnsuitableEntry() {
+
+ Header[] hdrs = new Header[] { new BasicHeader("Cache-Control", "max-age=foo") };
+ responseIsFresh(true);
+ contentLengthMatchesActualLength(true);
+ modifiedSince(false, mockRequest);
+
+ org.easymock.EasyMock.expect(mockRequest.getHeaders("Cache-Control")).andReturn(hdrs);
+ replayMocks();
+
+ boolean result = impl.canCachedResponseBeUsed(host, mockRequest, mockEntry);
+
+ verifyMocks();
+
+ Assert.assertFalse(result);
+ }
+
+ @Test
+ public void testMalformedCacheControlMinFreshRequestHeaderCausesUnsuitableEntry() {
+
+ Header[] hdrs = new Header[] { new BasicHeader("Cache-Control", "min-fresh=foo") };
+ responseIsFresh(true);
+ contentLengthMatchesActualLength(true);
+ modifiedSince(false, mockRequest);
+
+ org.easymock.EasyMock.expect(mockRequest.getHeaders("Cache-Control")).andReturn(hdrs);
+ replayMocks();
+
+ boolean result = impl.canCachedResponseBeUsed(host, mockRequest, mockEntry);
+
+ verifyMocks();
+
+ Assert.assertFalse(result);
+ }
+
+ private void responseIsFresh(boolean fresh) {
+ org.easymock.EasyMock.expect(mockEntry.isResponseFresh()).andReturn(fresh);
+ }
+
+ private void modifiedSince(boolean modified, HttpRequest request) {
+ org.easymock.EasyMock.expect(mockEntry.modifiedSince(request)).andReturn(modified);
+ }
+
+ private void contentLengthMatchesActualLength(boolean b) {
+ org.easymock.EasyMock.expect(mockEntry.contentLengthHeaderMatchesActualLength()).andReturn(
+ b);
+ }
+}
\ No newline at end of file
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachingHttpClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachingHttpClient.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachingHttpClient.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachingHttpClient.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,1126 @@
+/*
+ * ====================================================================
+ * 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.client.cache.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.ProtocolException;
+import org.apache.http.RequestLine;
+import org.apache.http.StatusLine;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.cache.HttpCacheUpdateCallback;
+import org.apache.http.client.cache.HttpCache;
+import org.apache.http.client.cache.impl.BasicHttpCache;
+import org.apache.http.client.cache.impl.CacheEntry;
+import org.apache.http.client.cache.impl.CacheEntryGenerator;
+import org.apache.http.client.cache.impl.CacheEntryUpdater;
+import org.apache.http.client.cache.impl.CacheInvalidator;
+import org.apache.http.client.cache.impl.CacheableRequestPolicy;
+import org.apache.http.client.cache.impl.CachedHttpResponseGenerator;
+import org.apache.http.client.cache.impl.CachedResponseSuitabilityChecker;
+import org.apache.http.client.cache.impl.CachingHttpClient;
+import org.apache.http.client.cache.impl.ConditionalRequestBuilder;
+import org.apache.http.client.cache.impl.RequestProtocolCompliance;
+import org.apache.http.client.cache.impl.RequestProtocolError;
+import org.apache.http.client.cache.impl.ResponseCachingPolicy;
+import org.apache.http.client.cache.impl.ResponseProtocolCompliance;
+import org.apache.http.client.cache.impl.SizeLimitedResponseReader;
+import org.apache.http.client.cache.impl.URIExtractor;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+import org.easymock.classextension.EasyMock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class TestCachingHttpClient {
+
+ private static final String GET_CURRENT_DATE = "getCurrentDate";
+
+ private static final String HANDLE_BACKEND_RESPONSE = "handleBackendResponse";
+
+ private static final String CALL_BACKEND = "callBackend";
+
+ private static final String REVALIDATE_CACHE_ENTRY = "revalidateCacheEntry";
+
+ private static final String GET_CACHE_ENTRY = "getCacheEntry";
+
+ private static final String STORE_IN_CACHE = "storeInCache";
+
+ private static final String GET_RESPONSE_READER = "getResponseReader";
+
+ private CachingHttpClient impl;
+
+ private CacheInvalidator mockInvalidator;
+ private CacheableRequestPolicy mockRequestPolicy;
+ private HttpClient mockBackend;
+ private HttpCache<CacheEntry> mockCache;
+ private CachedResponseSuitabilityChecker mockSuitabilityChecker;
+ private ResponseCachingPolicy mockResponsePolicy;
+ private HttpRequest mockRequest;
+ private HttpResponse mockBackendResponse;
+ private CacheEntry mockCacheEntry;
+ private CacheEntry mockVariantCacheEntry;
+ private URIExtractor mockExtractor;
+ private CacheEntryGenerator mockEntryGenerator;
+ private CachedHttpResponseGenerator mockResponseGenerator;
+
+ private SizeLimitedResponseReader mockResponseReader;
+ private HttpHost host;
+ private ClientConnectionManager mockConnectionManager;
+ private HttpContext mockContext;
+ private ResponseHandler<Object> mockHandler;
+ private HttpParams mockParams;
+ private HttpUriRequest mockUriRequest;
+ private HttpResponse mockCachedResponse;
+ private HttpResponse mockReconstructedResponse;
+
+ private ConditionalRequestBuilder mockConditionalRequestBuilder;
+
+ private HttpRequest mockConditionalRequest;
+
+ private StatusLine mockStatusLine;
+ private Date requestDate;
+ private Date responseDate;
+
+ private boolean mockedImpl;
+
+ private CacheEntryUpdater mockCacheEntryUpdater;
+ private ResponseProtocolCompliance mockResponseProtocolCompliance;
+ private RequestProtocolCompliance mockRequestProtocolCompliance;
+ private RequestLine mockRequestLine;
+
+ @SuppressWarnings("unchecked")
+ @Before
+ public void setUp() {
+
+ mockInvalidator = EasyMock.createMock(CacheInvalidator.class);
+ mockRequestPolicy = EasyMock.createMock(CacheableRequestPolicy.class);
+ mockBackend = EasyMock.createMock(HttpClient.class);
+ mockCache = EasyMock.createMock(HttpCache.class);
+ mockSuitabilityChecker = EasyMock.createMock(CachedResponseSuitabilityChecker.class);
+ mockResponsePolicy = EasyMock.createMock(ResponseCachingPolicy.class);
+ mockConnectionManager = EasyMock.createMock(ClientConnectionManager.class);
+ mockContext = EasyMock.createMock(HttpContext.class);
+ mockHandler = EasyMock.createMock(ResponseHandler.class);
+ mockParams = EasyMock.createMock(HttpParams.class);
+ mockRequest = EasyMock.createMock(HttpRequest.class);
+ mockBackendResponse = EasyMock.createMock(HttpResponse.class);
+ mockUriRequest = EasyMock.createMock(HttpUriRequest.class);
+ mockCacheEntry = EasyMock.createMock(CacheEntry.class);
+ mockVariantCacheEntry = EasyMock.createMock(CacheEntry.class);
+ mockExtractor = EasyMock.createMock(URIExtractor.class);
+ mockEntryGenerator = EasyMock.createMock(CacheEntryGenerator.class);
+ mockResponseGenerator = EasyMock.createMock(CachedHttpResponseGenerator.class);
+ mockCachedResponse = EasyMock.createMock(HttpResponse.class);
+ mockConditionalRequestBuilder = EasyMock.createMock(ConditionalRequestBuilder.class);
+ mockConditionalRequest = EasyMock.createMock(HttpRequest.class);
+ mockStatusLine = EasyMock.createMock(StatusLine.class);
+ mockCacheEntryUpdater = EasyMock.createMock(CacheEntryUpdater.class);
+ mockResponseReader = EasyMock.createMock(SizeLimitedResponseReader.class);
+ mockReconstructedResponse = EasyMock.createMock(HttpResponse.class);
+ mockResponseProtocolCompliance = EasyMock.createMock(ResponseProtocolCompliance.class);
+ mockRequestProtocolCompliance = EasyMock.createMock(RequestProtocolCompliance.class);
+ mockRequestLine = EasyMock.createMock(RequestLine.class);
+
+ requestDate = new Date(System.currentTimeMillis() - 1000);
+ responseDate = new Date();
+ host = new HttpHost("foo.example.com");
+ impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator,
+ mockExtractor, mockCache, mockResponseGenerator, mockInvalidator,
+ mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder,
+ mockCacheEntryUpdater, mockResponseProtocolCompliance,
+ mockRequestProtocolCompliance);
+ }
+
+ private void replayMocks() {
+
+ EasyMock.replay(mockInvalidator);
+ EasyMock.replay(mockRequestPolicy);
+ EasyMock.replay(mockSuitabilityChecker);
+ EasyMock.replay(mockResponsePolicy);
+ EasyMock.replay(mockCacheEntry);
+ EasyMock.replay(mockVariantCacheEntry);
+ EasyMock.replay(mockEntryGenerator);
+ EasyMock.replay(mockResponseGenerator);
+ EasyMock.replay(mockExtractor);
+ EasyMock.replay(mockBackend);
+ EasyMock.replay(mockCache);
+ EasyMock.replay(mockConnectionManager);
+ EasyMock.replay(mockContext);
+ EasyMock.replay(mockHandler);
+ EasyMock.replay(mockParams);
+ EasyMock.replay(mockRequest);
+ EasyMock.replay(mockBackendResponse);
+ EasyMock.replay(mockUriRequest);
+ EasyMock.replay(mockCachedResponse);
+ EasyMock.replay(mockConditionalRequestBuilder);
+ EasyMock.replay(mockConditionalRequest);
+ EasyMock.replay(mockStatusLine);
+ EasyMock.replay(mockCacheEntryUpdater);
+ EasyMock.replay(mockResponseReader);
+ EasyMock.replay(mockReconstructedResponse);
+ EasyMock.replay(mockResponseProtocolCompliance);
+ EasyMock.replay(mockRequestProtocolCompliance);
+
+ if (mockedImpl) {
+ EasyMock.replay(impl);
+ }
+ }
+
+ private void verifyMocks() {
+ EasyMock.verify(mockInvalidator);
+ EasyMock.verify(mockRequestPolicy);
+ EasyMock.verify(mockSuitabilityChecker);
+ EasyMock.verify(mockResponsePolicy);
+ EasyMock.verify(mockCacheEntry);
+ EasyMock.verify(mockVariantCacheEntry);
+ EasyMock.verify(mockEntryGenerator);
+ EasyMock.verify(mockResponseGenerator);
+ EasyMock.verify(mockExtractor);
+ EasyMock.verify(mockBackend);
+ EasyMock.verify(mockCache);
+ EasyMock.verify(mockConnectionManager);
+ EasyMock.verify(mockContext);
+ EasyMock.verify(mockHandler);
+ EasyMock.verify(mockParams);
+ EasyMock.verify(mockRequest);
+ EasyMock.verify(mockBackendResponse);
+ EasyMock.verify(mockUriRequest);
+ EasyMock.verify(mockCachedResponse);
+ EasyMock.verify(mockConditionalRequestBuilder);
+ EasyMock.verify(mockConditionalRequest);
+ EasyMock.verify(mockStatusLine);
+ EasyMock.verify(mockCacheEntryUpdater);
+ EasyMock.verify(mockResponseReader);
+ EasyMock.verify(mockReconstructedResponse);
+ EasyMock.verify(mockResponseProtocolCompliance);
+ EasyMock.verify(mockRequestProtocolCompliance);
+
+ if (mockedImpl) {
+ EasyMock.verify(impl);
+ }
+ }
+
+ @Test
+ public void testCacheableResponsesGoIntoCache() throws Exception {
+ mockImplMethods(STORE_IN_CACHE, GET_RESPONSE_READER);
+ responsePolicyAllowsCaching(true);
+
+ responseProtocolValidationIsCalled();
+
+ getMockResponseReader();
+ responseIsTooLarge(false);
+ byte[] buf = responseReaderReturnsBufferOfSize(100);
+
+ generateCacheEntry(requestDate, responseDate, buf);
+ storeInCacheWasCalled();
+ responseIsGeneratedFromCache();
+
+ replayMocks();
+ HttpResponse result = impl.handleBackendResponse(host, mockRequest, requestDate,
+ responseDate, mockBackendResponse);
+ verifyMocks();
+
+ Assert.assertSame(mockCachedResponse, result);
+ }
+
+ @Test
+ public void testRequestThatCannotBeServedFromCacheCausesBackendRequest() throws Exception {
+ cacheInvalidatorWasCalled();
+ requestPolicyAllowsCaching(false);
+ mockImplMethods(CALL_BACKEND);
+
+ callBackendReturnsResponse(mockBackendResponse);
+ requestProtocolValidationIsCalled();
+ requestIsFatallyNonCompliant(null);
+ requestInspectsRequestLine();
+
+ replayMocks();
+ HttpResponse result = impl.execute(host, mockRequest, mockContext);
+ verifyMocks();
+
+ Assert.assertSame(mockBackendResponse, result);
+ }
+
+ private void requestInspectsRequestLine() {
+ org.easymock.EasyMock.expect(mockRequest.getRequestLine()).andReturn(mockRequestLine);
+ }
+
+ private void requestIsFatallyNonCompliant(RequestProtocolError error) {
+ List<RequestProtocolError> errors = new ArrayList<RequestProtocolError>();
+ if (error != null) {
+ errors.add(error);
+ }
+ org.easymock.EasyMock.expect(
+ mockRequestProtocolCompliance.requestIsFatallyNonCompliant(mockRequest)).andReturn(
+ errors);
+ }
+
+ @Test
+ public void testStoreInCachePutsNonVariantEntryInPlace() throws Exception {
+
+ final String theURI = "theURI";
+
+ cacheEntryHasVariants(false);
+ extractTheURI(theURI);
+ putInCache(theURI);
+
+ replayMocks();
+ impl.storeInCache(host, mockRequest, mockCacheEntry);
+ verifyMocks();
+ }
+
+ @Test
+ public void testCacheUpdateCallbackCreatesNewParentEntryWhenParentEntryNull() throws Exception {
+
+ final String variantURI = "variantURI";
+
+ extractVariantURI(variantURI);
+ putInCache(variantURI);
+
+ variantURIAddedToCacheEntry(variantURI);
+
+ replayMocks();
+ HttpCacheUpdateCallback<CacheEntry> callbackImpl = impl
+ .storeVariantEntry(host, mockRequest, mockCacheEntry);
+ callbackImpl.getUpdatedEntry(null);
+ verifyMocks();
+ }
+
+ private void variantURIAddedToCacheEntry(String variantURI) {
+ mockCacheEntry.addVariantURI(variantURI);
+ }
+
+ @Test
+ public void testCacheMissCausesBackendRequest() throws Exception {
+ mockImplMethods(GET_CACHE_ENTRY, CALL_BACKEND);
+ cacheInvalidatorWasCalled();
+ requestPolicyAllowsCaching(true);
+ getCacheEntryReturns(null);
+ requestProtocolValidationIsCalled();
+ requestIsFatallyNonCompliant(null);
+ requestInspectsRequestLine();
+
+ callBackendReturnsResponse(mockBackendResponse);
+
+ replayMocks();
+ HttpResponse result = impl.execute(host, mockRequest, mockContext);
+ verifyMocks();
+
+ Assert.assertSame(mockBackendResponse, result);
+ Assert.assertEquals(1, impl.getCacheMisses());
+ Assert.assertEquals(0, impl.getCacheHits());
+ Assert.assertEquals(0, impl.getCacheUpdates());
+ }
+
+ @Test
+ public void testUnsuitableUnvalidatableCacheEntryCausesBackendRequest() throws Exception {
+ mockImplMethods(GET_CACHE_ENTRY, CALL_BACKEND);
+ cacheInvalidatorWasCalled();
+ requestPolicyAllowsCaching(true);
+ requestProtocolValidationIsCalled();
+ requestIsFatallyNonCompliant(null);
+ requestInspectsRequestLine();
+
+ getCacheEntryReturns(mockCacheEntry);
+ cacheEntrySuitable(false);
+ cacheEntryValidatable(false);
+ callBackendReturnsResponse(mockBackendResponse);
+
+ replayMocks();
+ HttpResponse result = impl.execute(host, mockRequest, mockContext);
+ verifyMocks();
+
+ Assert.assertSame(mockBackendResponse, result);
+ Assert.assertEquals(0, impl.getCacheMisses());
+ Assert.assertEquals(1, impl.getCacheHits());
+ Assert.assertEquals(0, impl.getCacheUpdates());
+ }
+
+ @Test
+ public void testUnsuitableValidatableCacheEntryCausesRevalidation() throws Exception {
+ mockImplMethods(GET_CACHE_ENTRY, REVALIDATE_CACHE_ENTRY);
+ cacheInvalidatorWasCalled();
+ requestPolicyAllowsCaching(true);
+ requestProtocolValidationIsCalled();
+ requestIsFatallyNonCompliant(null);
+ requestInspectsRequestLine();
+
+ getCacheEntryReturns(mockCacheEntry);
+ cacheEntrySuitable(false);
+ cacheEntryValidatable(true);
+ revalidateCacheEntryReturns(mockBackendResponse);
+
+ replayMocks();
+ HttpResponse result = impl.execute(host, mockRequest, mockContext);
+ verifyMocks();
+
+ Assert.assertSame(mockBackendResponse, result);
+ Assert.assertEquals(0, impl.getCacheMisses());
+ Assert.assertEquals(1, impl.getCacheHits());
+ Assert.assertEquals(0, impl.getCacheUpdates());
+ }
+
+ @Test
+ public void testRevalidationCallsHandleBackEndResponseWhenNot304() throws Exception {
+ mockImplMethods(GET_CURRENT_DATE, HANDLE_BACKEND_RESPONSE);
+
+ conditionalRequestBuilderCalled();
+ getCurrentDateReturns(requestDate);
+ backendCallWasMadeWithRequest(mockConditionalRequest);
+ getCurrentDateReturns(responseDate);
+ backendResponseCodeIs(HttpStatus.SC_OK);
+ cacheEntryUpdaterCalled();
+ cacheEntryHasVariants(false);
+ extractTheURI("http://foo.example.com");
+ putInCache("http://foo.example.com");
+ responseIsGeneratedFromCache();
+
+ replayMocks();
+
+ HttpResponse result = impl.revalidateCacheEntry(host, mockRequest, mockContext,
+ mockCacheEntry);
+
+ verifyMocks();
+
+ Assert.assertEquals(mockCachedResponse, result);
+ Assert.assertEquals(0, impl.getCacheMisses());
+ Assert.assertEquals(0, impl.getCacheHits());
+ Assert.assertEquals(1, impl.getCacheUpdates());
+ }
+
+ @Test
+ public void testRevalidationUpdatesCacheEntryAndPutsItToCacheWhen304ReturningCachedResponse()
+ throws Exception {
+ mockImplMethods(GET_CURRENT_DATE, STORE_IN_CACHE);
+ conditionalRequestBuilderCalled();
+ getCurrentDateReturns(requestDate);
+ backendCallWasMadeWithRequest(mockConditionalRequest);
+ getCurrentDateReturns(responseDate);
+ backendResponseCodeIs(HttpStatus.SC_NOT_MODIFIED);
+
+ cacheEntryUpdaterCalled();
+ storeInCacheWasCalled();
+
+ responseIsGeneratedFromCache();
+
+ replayMocks();
+
+ HttpResponse result = impl.revalidateCacheEntry(host, mockRequest, mockContext,
+ mockCacheEntry);
+
+ verifyMocks();
+
+ Assert.assertEquals(mockCachedResponse, result);
+ Assert.assertEquals(0, impl.getCacheMisses());
+ Assert.assertEquals(0, impl.getCacheHits());
+ Assert.assertEquals(1, impl.getCacheUpdates());
+ }
+
+ @Test
+ public void testSuitableCacheEntryDoesNotCauseBackendRequest() throws Exception {
+ mockImplMethods(GET_CACHE_ENTRY);
+ cacheInvalidatorWasCalled();
+ requestPolicyAllowsCaching(true);
+ requestProtocolValidationIsCalled();
+ getCacheEntryReturns(mockCacheEntry);
+ cacheEntrySuitable(true);
+ responseIsGeneratedFromCache();
+ requestIsFatallyNonCompliant(null);
+ requestInspectsRequestLine();
+
+ replayMocks();
+ HttpResponse result = impl.execute(host, mockRequest, mockContext);
+ verifyMocks();
+
+ Assert.assertSame(mockCachedResponse, result);
+ }
+
+ @Test
+ public void testCallBackendMakesBackEndRequestAndHandlesResponse() throws Exception {
+ mockImplMethods(GET_CURRENT_DATE, HANDLE_BACKEND_RESPONSE);
+ getCurrentDateReturns(requestDate);
+ backendCallWasMadeWithRequest(mockRequest);
+ getCurrentDateReturns(responseDate);
+ handleBackendResponseReturnsResponse(mockRequest, mockBackendResponse);
+
+ replayMocks();
+
+ impl.callBackend(host, mockRequest, mockContext);
+
+ verifyMocks();
+ }
+
+ @Test
+ public void testNonCacheableResponseIsNotCachedAndIsReturnedAsIs() throws Exception {
+ final String theURI = "theURI";
+ Date currentDate = new Date();
+ responsePolicyAllowsCaching(false);
+ responseProtocolValidationIsCalled();
+
+ extractTheURI(theURI);
+ removeFromCache(theURI);
+
+ replayMocks();
+ HttpResponse result = impl.handleBackendResponse(host, mockRequest, currentDate,
+ currentDate, mockBackendResponse);
+ verifyMocks();
+
+ Assert.assertSame(mockBackendResponse, result);
+ }
+
+ @Test
+ public void testGetCacheEntryReturnsNullOnCacheMiss() throws Exception {
+
+ final String theURI = "theURI";
+ extractTheURI(theURI);
+ gotCacheMiss(theURI);
+
+ replayMocks();
+ CacheEntry result = impl.getCacheEntry(host, mockRequest);
+ verifyMocks();
+ Assert.assertNull(result);
+ }
+
+ @Test
+ public void testGetCacheEntryFetchesFromCacheOnCacheHitIfNoVariants() throws Exception {
+
+ final String theURI = "theURI";
+ extractTheURI(theURI);
+ gotCacheHit(theURI);
+ cacheEntryHasVariants(false);
+
+ replayMocks();
+ CacheEntry result = impl.getCacheEntry(host, mockRequest);
+ verifyMocks();
+ Assert.assertSame(mockCacheEntry, result);
+ }
+
+ @Test
+ public void testGetCacheEntryReturnsNullIfNoVariantInCache() throws Exception {
+
+ final String theURI = "theURI";
+ final String variantURI = "variantURI";
+ extractTheURI(theURI);
+ gotCacheHit(theURI);
+ cacheEntryHasVariants(true);
+ extractVariantURI(variantURI);
+ gotCacheMiss(variantURI);
+
+ replayMocks();
+ CacheEntry result = impl.getCacheEntry(host, mockRequest);
+ verifyMocks();
+ Assert.assertNull(result);
+ }
+
+ @Test
+ public void testGetCacheEntryReturnsVariantIfPresentInCache() throws Exception {
+
+ final String theURI = "theURI";
+ final String variantURI = "variantURI";
+ extractTheURI(theURI);
+ gotCacheHit(theURI, mockCacheEntry);
+ cacheEntryHasVariants(true);
+ extractVariantURI(variantURI);
+ gotCacheHit(variantURI, mockVariantCacheEntry);
+
+ replayMocks();
+ CacheEntry result = impl.getCacheEntry(host, mockRequest);
+ verifyMocks();
+ Assert.assertSame(mockVariantCacheEntry, result);
+ }
+
+ @Test
+ public void testTooLargeResponsesAreNotCached() throws Exception {
+ mockImplMethods(GET_CURRENT_DATE, GET_RESPONSE_READER, STORE_IN_CACHE);
+ getCurrentDateReturns(requestDate);
+ backendCallWasMadeWithRequest(mockRequest);
+ responseProtocolValidationIsCalled();
+
+ getCurrentDateReturns(responseDate);
+ responsePolicyAllowsCaching(true);
+ getMockResponseReader();
+ responseIsTooLarge(true);
+ readerReturnsReconstructedResponse();
+
+ replayMocks();
+
+ impl.callBackend(host, mockRequest, mockContext);
+
+ verifyMocks();
+ }
+
+ @Test
+ public void testSmallEnoughResponsesAreCached() throws Exception {
+ requestDate = new Date();
+ responseDate = new Date();
+ mockImplMethods(GET_CURRENT_DATE, GET_RESPONSE_READER, STORE_IN_CACHE);
+ getCurrentDateReturns(requestDate);
+ responseProtocolValidationIsCalled();
+
+ backendCallWasMadeWithRequest(mockRequest);
+ getCurrentDateReturns(responseDate);
+ responsePolicyAllowsCaching(true);
+ getMockResponseReader();
+ responseIsTooLarge(false);
+ byte[] buf = responseReaderReturnsBufferOfSize(100);
+ generateCacheEntry(requestDate, responseDate, buf);
+ storeInCacheWasCalled();
+ responseIsGeneratedFromCache();
+
+ replayMocks();
+
+ impl.callBackend(host, mockRequest, mockContext);
+
+ verifyMocks();
+ }
+
+ @Test
+ public void testCallsSelfForExecuteOnHostRequestWithNullContext() throws Exception {
+ final Counter c = new Counter();
+ final HttpHost theHost = host;
+ final HttpRequest theRequest = mockRequest;
+ final HttpResponse theResponse = mockBackendResponse;
+ impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator,
+ mockExtractor, mockCache, mockResponseGenerator, mockInvalidator,
+ mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder,
+ mockCacheEntryUpdater, mockResponseProtocolCompliance,
+ mockRequestProtocolCompliance) {
+ @Override
+ public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) {
+ Assert.assertSame(theHost, target);
+ Assert.assertSame(theRequest, request);
+ Assert.assertNull(context);
+ c.incr();
+ return theResponse;
+ }
+ };
+
+ replayMocks();
+ HttpResponse result = impl.execute(host, mockRequest);
+ verifyMocks();
+ Assert.assertSame(mockBackendResponse, result);
+ Assert.assertEquals(1, c.getCount());
+ }
+
+ @Test
+ public void testCallsSelfWithDefaultContextForExecuteOnHostRequestWithHandler()
+ throws Exception {
+
+ final Counter c = new Counter();
+ final HttpHost theHost = host;
+ final HttpRequest theRequest = mockRequest;
+ final HttpResponse theResponse = mockBackendResponse;
+ final ResponseHandler<Object> theHandler = mockHandler;
+ final Object value = new Object();
+ impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator,
+ mockExtractor, mockCache, mockResponseGenerator, mockInvalidator,
+ mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder,
+ mockCacheEntryUpdater, mockResponseProtocolCompliance,
+ mockRequestProtocolCompliance) {
+ @Override
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> rh, HttpContext context) {
+ Assert.assertSame(theHost, target);
+ Assert.assertSame(theRequest, request);
+ Assert.assertSame(theHandler, rh);
+ Assert.assertNull(context);
+ c.incr();
+ try {
+ return rh.handleResponse(theResponse);
+ } catch (Exception wrong) {
+ throw new RuntimeException("unexpected exn", wrong);
+ }
+ }
+ };
+
+ org.easymock.EasyMock.expect(mockHandler.handleResponse(mockBackendResponse)).andReturn(
+ value);
+
+ replayMocks();
+ Object result = impl.execute(host, mockRequest, mockHandler);
+ verifyMocks();
+
+ Assert.assertSame(value, result);
+ Assert.assertEquals(1, c.getCount());
+ }
+
+ @Test
+ public void testCallsSelfOnExecuteHostRequestWithHandlerAndContext() throws Exception {
+
+ final Counter c = new Counter();
+ final HttpHost theHost = host;
+ final HttpRequest theRequest = mockRequest;
+ final HttpResponse theResponse = mockBackendResponse;
+ final HttpContext theContext = mockContext;
+ impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator,
+ mockExtractor, mockCache, mockResponseGenerator, mockInvalidator,
+ mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder,
+ mockCacheEntryUpdater, mockResponseProtocolCompliance,
+ mockRequestProtocolCompliance) {
+ @Override
+ public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) {
+ Assert.assertSame(theHost, target);
+ Assert.assertSame(theRequest, request);
+ Assert.assertSame(theContext, context);
+ c.incr();
+ return theResponse;
+ }
+ };
+
+ final Object theObject = new Object();
+
+ org.easymock.EasyMock.expect(mockHandler.handleResponse(mockBackendResponse)).andReturn(
+ theObject);
+
+ replayMocks();
+ Object result = impl.execute(host, mockRequest, mockHandler, mockContext);
+ verifyMocks();
+ Assert.assertEquals(1, c.getCount());
+ Assert.assertSame(theObject, result);
+ }
+
+ @Test
+ public void testCallsSelfWithNullContextOnExecuteUriRequest() throws Exception {
+ final Counter c = new Counter();
+ final HttpUriRequest theRequest = mockUriRequest;
+ final HttpResponse theResponse = mockBackendResponse;
+ impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator,
+ mockExtractor, mockCache, mockResponseGenerator, mockInvalidator,
+ mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder,
+ mockCacheEntryUpdater, mockResponseProtocolCompliance,
+ mockRequestProtocolCompliance) {
+ @Override
+ public HttpResponse execute(HttpUriRequest request, HttpContext context) {
+ Assert.assertSame(theRequest, request);
+ Assert.assertNull(context);
+ c.incr();
+ return theResponse;
+ }
+ };
+
+ replayMocks();
+ HttpResponse result = impl.execute(mockUriRequest);
+ verifyMocks();
+
+ Assert.assertEquals(1, c.getCount());
+ Assert.assertSame(theResponse, result);
+ }
+
+ @Test
+ public void testCallsSelfWithExtractedHostOnExecuteUriRequestWithContext() throws Exception {
+
+ final URI uri = new URI("sch://host:8888");
+ final Counter c = new Counter();
+ final HttpRequest theRequest = mockUriRequest;
+ final HttpContext theContext = mockContext;
+ final HttpResponse theResponse = mockBackendResponse;
+ impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator,
+ mockExtractor, mockCache, mockResponseGenerator, mockInvalidator,
+ mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder,
+ mockCacheEntryUpdater, mockResponseProtocolCompliance,
+ mockRequestProtocolCompliance) {
+ @Override
+ public HttpResponse execute(HttpHost hh, HttpRequest req, HttpContext ctx) {
+ Assert.assertEquals("sch", hh.getSchemeName());
+ Assert.assertEquals("host", hh.getHostName());
+ Assert.assertEquals(8888, hh.getPort());
+ Assert.assertSame(theRequest, req);
+ Assert.assertSame(theContext, ctx);
+ c.incr();
+ return theResponse;
+ }
+ };
+
+ org.easymock.EasyMock.expect(mockUriRequest.getURI()).andReturn(uri);
+
+ replayMocks();
+ HttpResponse result = impl.execute(mockUriRequest, mockContext);
+ verifyMocks();
+
+ Assert.assertEquals(1, c.getCount());
+ Assert.assertSame(mockBackendResponse, result);
+ }
+
+ @Test
+ public void testCallsSelfWithNullContextOnExecuteUriRequestWithHandler() throws Exception {
+ final Counter c = new Counter();
+ final HttpUriRequest theRequest = mockUriRequest;
+ final HttpResponse theResponse = mockBackendResponse;
+ final Object theValue = new Object();
+ impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator,
+ mockExtractor, mockCache, mockResponseGenerator, mockInvalidator,
+ mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder,
+ mockCacheEntryUpdater, mockResponseProtocolCompliance,
+ mockRequestProtocolCompliance) {
+ @Override
+ public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> handler,
+ HttpContext context) throws IOException {
+ Assert.assertSame(theRequest, request);
+ Assert.assertNull(context);
+ c.incr();
+ return handler.handleResponse(theResponse);
+ }
+ };
+
+ org.easymock.EasyMock.expect(mockHandler.handleResponse(mockBackendResponse)).andReturn(
+ theValue);
+
+ replayMocks();
+ Object result = impl.execute(mockUriRequest, mockHandler);
+ verifyMocks();
+
+ Assert.assertEquals(1, c.getCount());
+ Assert.assertSame(theValue, result);
+ }
+
+ @Test
+ public void testCallsSelfAndRunsHandlerOnExecuteUriRequestWithHandlerAndContext()
+ throws Exception {
+
+ final Counter c = new Counter();
+ final HttpUriRequest theRequest = mockUriRequest;
+ final HttpContext theContext = mockContext;
+ final HttpResponse theResponse = mockBackendResponse;
+ final Object theValue = new Object();
+ impl = new CachingHttpClient(mockBackend, mockResponsePolicy, mockEntryGenerator,
+ mockExtractor, mockCache, mockResponseGenerator, mockInvalidator,
+ mockRequestPolicy, mockSuitabilityChecker, mockConditionalRequestBuilder,
+ mockCacheEntryUpdater, mockResponseProtocolCompliance,
+ mockRequestProtocolCompliance) {
+ @Override
+ public HttpResponse execute(HttpUriRequest request, HttpContext context)
+ throws IOException {
+ Assert.assertSame(theRequest, request);
+ Assert.assertSame(theContext, context);
+ c.incr();
+ return theResponse;
+ }
+ };
+
+ org.easymock.EasyMock.expect(mockHandler.handleResponse(mockBackendResponse)).andReturn(
+ theValue);
+
+ replayMocks();
+ Object result = impl.execute(mockUriRequest, mockHandler, mockContext);
+ verifyMocks();
+ Assert.assertEquals(1, c.getCount());
+ Assert.assertSame(theValue, result);
+ }
+
+ @Test
+ public void testUsesBackendsConnectionManager() {
+ org.easymock.EasyMock.expect(mockBackend.getConnectionManager()).andReturn(
+ mockConnectionManager);
+ replayMocks();
+ ClientConnectionManager result = impl.getConnectionManager();
+ verifyMocks();
+ Assert.assertSame(result, mockConnectionManager);
+ }
+
+ @Test
+ public void testUsesBackendsHttpParams() {
+ org.easymock.EasyMock.expect(mockBackend.getParams()).andReturn(mockParams);
+ replayMocks();
+ HttpParams result = impl.getParams();
+ verifyMocks();
+ Assert.assertSame(mockParams, result);
+ }
+
+ @Test
+ @Ignore
+ public void testRealResultsMatch() throws IOException {
+
+ SchemeRegistry schemeRegistry = new SchemeRegistry();
+ schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
+ schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
+
+ ClientConnectionManager cm = new ThreadSafeClientConnManager(schemeRegistry);
+ HttpClient httpClient = new DefaultHttpClient(cm);
+
+ HttpCache<CacheEntry> cacheImpl = new BasicHttpCache(100);
+
+ CachingHttpClient cachingClient = new CachingHttpClient(httpClient, cacheImpl, 8192);
+
+ HttpUriRequest request = new HttpGet("http://www.fancast.com/static-28262/styles/base.css");
+
+ HttpClient baseClient = new DefaultHttpClient();
+
+ HttpResponse cachedResponse = cachingClient.execute(request);
+ HttpResponse realResponse = baseClient.execute(request);
+
+ byte[] cached = readResponse(cachedResponse);
+ byte[] real = readResponse(realResponse);
+
+ Assert.assertArrayEquals(cached, real);
+ }
+
+ @Test
+ public void testResponseIsGeneratedWhenCacheEntryIsUsable() throws Exception {
+
+ final String theURI = "http://foo";
+
+ requestIsFatallyNonCompliant(null);
+ requestInspectsRequestLine();
+ requestProtocolValidationIsCalled();
+ cacheInvalidatorWasCalled();
+ requestPolicyAllowsCaching(true);
+ cacheEntrySuitable(true);
+ extractTheURI(theURI);
+ gotCacheHit(theURI);
+ responseIsGeneratedFromCache();
+ cacheEntryHasVariants(false);
+
+ replayMocks();
+ impl.execute(host, mockRequest, mockContext);
+ verifyMocks();
+ }
+
+ @Test
+ public void testNonCompliantRequestWrapsAndReThrowsProtocolException() throws Exception {
+
+ ProtocolException expected = new ProtocolException("ouch");
+
+ requestInspectsRequestLine();
+ requestIsFatallyNonCompliant(null);
+ requestCannotBeMadeCompliantThrows(expected);
+
+ boolean gotException = false;
+ replayMocks();
+ try {
+ impl.execute(host, mockRequest, mockContext);
+ } catch (ClientProtocolException ex) {
+ Assert.assertTrue(ex.getCause().getMessage().equals(expected.getMessage()));
+ gotException = true;
+ }
+ verifyMocks();
+ Assert.assertTrue(gotException);
+ }
+
+ private byte[] readResponse(HttpResponse response) {
+ try {
+ ByteArrayOutputStream s1 = new ByteArrayOutputStream();
+ response.getEntity().writeTo(s1);
+ return s1.toByteArray();
+ } catch (Exception ex) {
+ return new byte[] {};
+
+ }
+
+ }
+
+ private void cacheInvalidatorWasCalled() {
+ mockInvalidator.flushInvalidatedCacheEntries(host, mockRequest);
+ }
+
+ private void callBackendReturnsResponse(HttpResponse response) throws IOException {
+ org.easymock.EasyMock.expect(impl.callBackend(host, mockRequest, mockContext)).andReturn(
+ response);
+ }
+
+ private void revalidateCacheEntryReturns(HttpResponse response) throws IOException,
+ ProtocolException {
+ org.easymock.EasyMock.expect(
+ impl.revalidateCacheEntry(host, mockRequest, mockContext, mockCacheEntry))
+ .andReturn(response);
+ }
+
+ private void cacheEntryValidatable(boolean b) {
+ org.easymock.EasyMock.expect(mockCacheEntry.isRevalidatable()).andReturn(b);
+ }
+
+ private void cacheEntryUpdaterCalled() {
+ mockCacheEntryUpdater.updateCacheEntry(mockCacheEntry, requestDate, responseDate,
+ mockBackendResponse);
+ }
+
+ private void getCacheEntryReturns(CacheEntry entry) {
+ org.easymock.EasyMock.expect(impl.getCacheEntry(host, mockRequest)).andReturn(entry);
+ }
+
+ private void backendResponseCodeIs(int code) {
+ org.easymock.EasyMock.expect(mockBackendResponse.getStatusLine()).andReturn(mockStatusLine);
+ org.easymock.EasyMock.expect(mockStatusLine.getStatusCode()).andReturn(code);
+ }
+
+ private void conditionalRequestBuilderCalled() throws ProtocolException {
+ org.easymock.EasyMock.expect(
+ mockConditionalRequestBuilder.buildConditionalRequest(mockRequest, mockCacheEntry))
+ .andReturn(mockConditionalRequest);
+ }
+
+ private void getCurrentDateReturns(Date date) {
+ org.easymock.EasyMock.expect(impl.getCurrentDate()).andReturn(date);
+ }
+
+ private void getMockResponseReader() throws IOException {
+ org.easymock.EasyMock.expect(impl.getResponseReader(mockBackendResponse)).andReturn(
+ mockResponseReader);
+ }
+
+ private void removeFromCache(String theURI) throws Exception {
+ mockCache.removeEntry(theURI);
+ }
+
+ private void requestPolicyAllowsCaching(boolean allow) {
+ org.easymock.EasyMock.expect(mockRequestPolicy.isServableFromCache(mockRequest)).andReturn(
+ allow);
+ }
+
+ private byte[] responseReaderReturnsBufferOfSize(int bufferSize) {
+ byte[] buffer = new byte[bufferSize];
+ org.easymock.EasyMock.expect(mockResponseReader.getResponseBytes()).andReturn(buffer);
+ return buffer;
+ }
+
+ private void readerReturnsReconstructedResponse() throws IOException {
+ org.easymock.EasyMock.expect(mockResponseReader.getReconstructedResponse()).andReturn(
+ mockReconstructedResponse);
+ }
+
+ private void responseIsTooLarge(boolean tooLarge) throws Exception {
+ org.easymock.EasyMock.expect(mockResponseReader.isResponseTooLarge()).andReturn(tooLarge);
+ }
+
+ private void backendCallWasMadeWithRequest(HttpRequest request) throws IOException {
+ org.easymock.EasyMock.expect(mockBackend.execute(host, request, mockContext)).andReturn(
+ mockBackendResponse);
+ }
+
+ private void responsePolicyAllowsCaching(boolean allow) {
+ org.easymock.EasyMock.expect(
+ mockResponsePolicy.isResponseCacheable(mockRequest, mockBackendResponse))
+ .andReturn(allow);
+ }
+
+ private void gotCacheMiss(String theURI) throws Exception {
+ org.easymock.EasyMock.expect(mockCache.getEntry(theURI)).andReturn(null);
+ }
+
+ private void cacheEntrySuitable(boolean suitable) {
+ org.easymock.EasyMock.expect(
+ mockSuitabilityChecker.canCachedResponseBeUsed(host, mockRequest, mockCacheEntry))
+ .andReturn(suitable);
+ }
+
+ private void gotCacheHit(String theURI) throws Exception {
+ org.easymock.EasyMock.expect(mockCache.getEntry(theURI)).andReturn(mockCacheEntry);
+ }
+
+ private void gotCacheHit(String theURI, CacheEntry entry) throws Exception {
+ org.easymock.EasyMock.expect(mockCache.getEntry(theURI)).andReturn(entry);
+ }
+
+ private void cacheEntryHasVariants(boolean b) {
+ org.easymock.EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(b);
+ }
+
+ private void responseIsGeneratedFromCache() {
+ org.easymock.EasyMock.expect(mockResponseGenerator.generateResponse(mockCacheEntry))
+ .andReturn(mockCachedResponse);
+ }
+
+ private void extractTheURI(String theURI) {
+ org.easymock.EasyMock.expect(mockExtractor.getURI(host, mockRequest)).andReturn(theURI);
+ }
+
+ private void extractVariantURI(String variantURI) {
+ org.easymock.EasyMock
+ .expect(mockExtractor.getVariantURI(host, mockRequest, mockCacheEntry)).andReturn(
+ variantURI);
+ }
+
+ private void putInCache(String theURI) throws Exception {
+ mockCache.putEntry(theURI, mockCacheEntry);
+ }
+
+ private void generateCacheEntry(Date requestDate, Date responseDate, byte[] bytes)
+ throws IOException {
+ org.easymock.EasyMock.expect(
+ mockEntryGenerator.generateEntry(requestDate, responseDate, mockBackendResponse,
+ bytes)).andReturn(mockCacheEntry);
+ }
+
+ private void handleBackendResponseReturnsResponse(HttpRequest request, HttpResponse response)
+ throws IOException {
+ org.easymock.EasyMock.expect(
+ impl.handleBackendResponse(host, request, requestDate, responseDate,
+ mockBackendResponse)).andReturn(response);
+ }
+
+ private void storeInCacheWasCalled() {
+ impl.storeInCache(host, mockRequest, mockCacheEntry);
+ }
+
+ private void responseProtocolValidationIsCalled() throws ClientProtocolException {
+ mockResponseProtocolCompliance.ensureProtocolCompliance(mockRequest, mockBackendResponse);
+ }
+
+ private void requestProtocolValidationIsCalled() throws Exception {
+ org.easymock.EasyMock.expect(
+ mockRequestProtocolCompliance.makeRequestCompliant(mockRequest)).andReturn(
+ mockRequest);
+ }
+
+ private void requestCannotBeMadeCompliantThrows(ProtocolException exception) throws Exception {
+ org.easymock.EasyMock.expect(
+ mockRequestProtocolCompliance.makeRequestCompliant(mockRequest))
+ .andThrow(exception);
+ }
+
+ private void mockImplMethods(String... methods) {
+ mockedImpl = true;
+ impl = EasyMock.createMockBuilder(CachingHttpClient.class).withConstructor(mockBackend,
+ mockResponsePolicy, mockEntryGenerator, mockExtractor, mockCache,
+ mockResponseGenerator, mockInvalidator, mockRequestPolicy, mockSuitabilityChecker,
+ mockConditionalRequestBuilder, mockCacheEntryUpdater,
+ mockResponseProtocolCompliance, mockRequestProtocolCompliance).addMockedMethods(
+ methods).createMock();
+ }
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCombinedInputStream.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCombinedInputStream.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCombinedInputStream.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCombinedInputStream.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,119 @@
+/*
+ * ====================================================================
+ * 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.client.cache.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import org.apache.http.client.cache.impl.CombinedInputStream;
+import org.easymock.classextension.EasyMock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ */
+public class TestCombinedInputStream {
+
+ private InputStream mockInputStream1;
+ private InputStream mockInputStream2;
+ private CombinedInputStream impl;
+
+ @Before
+ public void setUp() {
+ mockInputStream1 = EasyMock.createMock(InputStream.class);
+ mockInputStream2 = EasyMock.createMock(InputStream.class);
+
+ impl = new CombinedInputStream(mockInputStream1, mockInputStream2);
+ }
+
+ @Test
+ public void testCreatingInputStreamWithNullInputFails() {
+
+ boolean gotex1 = false;
+ boolean gotex2 = false;
+
+ try {
+ impl = new CombinedInputStream(null, mockInputStream2);
+ } catch (Exception ex) {
+ gotex1 = true;
+ }
+
+ try {
+ impl = new CombinedInputStream(mockInputStream1, null);
+ } catch (Exception ex) {
+ gotex2 = true;
+ }
+
+ Assert.assertTrue(gotex1);
+ Assert.assertTrue(gotex2);
+
+ }
+
+ @Test
+ public void testAvailableReturnsCorrectSize() throws Exception {
+ ByteArrayInputStream s1 = new ByteArrayInputStream(new byte[] { 1, 1, 1, 1, 1 });
+ ByteArrayInputStream s2 = new ByteArrayInputStream(new byte[] { 1, 1, 1, 1, 1 });
+
+ impl = new CombinedInputStream(s1, s2);
+ int avail = impl.available();
+
+ Assert.assertEquals(10, avail);
+ }
+
+ @Test
+ public void testFirstEmptyStreamReadsFromOtherStream() throws Exception {
+ org.easymock.EasyMock.expect(mockInputStream1.read()).andReturn(-1);
+ org.easymock.EasyMock.expect(mockInputStream2.read()).andReturn(500);
+
+ replayMocks();
+ int result = impl.read();
+ verifyMocks();
+
+ Assert.assertEquals(500, result);
+ }
+
+ @Test
+ public void testThatWeReadTheFirstInputStream() throws Exception {
+ org.easymock.EasyMock.expect(mockInputStream1.read()).andReturn(500);
+
+ replayMocks();
+ int result = impl.read();
+ verifyMocks();
+
+ Assert.assertEquals(500, result);
+ }
+
+ private void verifyMocks() {
+ EasyMock.verify(mockInputStream1, mockInputStream2);
+ }
+
+ private void replayMocks() {
+ EasyMock.replay(mockInputStream1, mockInputStream2);
+ }
+
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestConditionalRequestBuilder.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestConditionalRequestBuilder.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestConditionalRequestBuilder.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestConditionalRequestBuilder.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,116 @@
+/*
+ * ====================================================================
+ * 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.client.cache.impl;
+
+import java.util.Date;
+
+import org.apache.http.Header;
+import org.apache.http.HttpRequest;
+import org.apache.http.ProtocolException;
+import org.apache.http.client.cache.impl.CacheEntry;
+import org.apache.http.client.cache.impl.ConditionalRequestBuilder;
+import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicHttpRequest;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestConditionalRequestBuilder {
+
+ private ConditionalRequestBuilder impl;
+
+ @Before
+ public void setUp() throws Exception {
+ impl = new ConditionalRequestBuilder();
+ }
+
+ @Test
+ public void testBuildConditionalRequestWithLastModified() throws ProtocolException {
+ String theMethod = "GET";
+ String theUri = "/theuri";
+ String lastModified = "this is my last modified date";
+
+ HttpRequest request = new BasicHttpRequest(theMethod, theUri);
+ request.addHeader("Accept-Encoding", "gzip");
+
+ CacheEntry cacheEntry = new CacheEntry();
+ cacheEntry.setResponseHeaders(new Header[] {
+ new BasicHeader("Date", DateUtils.formatDate(new Date())),
+ new BasicHeader("Last-Modified", lastModified) });
+
+ HttpRequest newRequest = impl.buildConditionalRequest(request, cacheEntry);
+
+ Assert.assertNotSame(request, newRequest);
+
+ Assert.assertEquals(theMethod, newRequest.getRequestLine().getMethod());
+ Assert.assertEquals(theUri, newRequest.getRequestLine().getUri());
+ Assert.assertEquals(request.getRequestLine().getProtocolVersion(), newRequest
+ .getRequestLine().getProtocolVersion());
+ Assert.assertEquals(2, newRequest.getAllHeaders().length);
+
+ Assert.assertEquals("Accept-Encoding", newRequest.getAllHeaders()[0].getName());
+ Assert.assertEquals("gzip", newRequest.getAllHeaders()[0].getValue());
+
+ Assert.assertEquals("If-Modified-Since", newRequest.getAllHeaders()[1].getName());
+ Assert.assertEquals(lastModified, newRequest.getAllHeaders()[1].getValue());
+ }
+
+ @Test
+ public void testBuildConditionalRequestWithETag() throws ProtocolException {
+ String theMethod = "GET";
+ String theUri = "/theuri";
+ String theETag = "this is my eTag";
+
+ HttpRequest request = new BasicHttpRequest(theMethod, theUri);
+ request.addHeader("Accept-Encoding", "gzip");
+
+ CacheEntry cacheEntry = new CacheEntry();
+ cacheEntry.setResponseHeaders(new Header[] {
+ new BasicHeader("Date", DateUtils.formatDate(new Date())),
+ new BasicHeader("Last-Modified", DateUtils.formatDate(new Date())),
+ new BasicHeader("ETag", theETag) });
+
+ HttpRequest newRequest = impl.buildConditionalRequest(request, cacheEntry);
+
+ Assert.assertNotSame(request, newRequest);
+
+ Assert.assertEquals(theMethod, newRequest.getRequestLine().getMethod());
+ Assert.assertEquals(theUri, newRequest.getRequestLine().getUri());
+ Assert.assertEquals(request.getRequestLine().getProtocolVersion(), newRequest
+ .getRequestLine().getProtocolVersion());
+
+ Assert.assertEquals(2, newRequest.getAllHeaders().length);
+
+ Assert.assertEquals("Accept-Encoding", newRequest.getAllHeaders()[0].getName());
+ Assert.assertEquals("gzip", newRequest.getAllHeaders()[0].getValue());
+
+ Assert.assertEquals("If-None-Match", newRequest.getAllHeaders()[1].getName());
+ Assert.assertEquals(theETag, newRequest.getAllHeaders()[1].getValue());
+ }
+
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestDefaultCacheEntrySerializer.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestDefaultCacheEntrySerializer.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestDefaultCacheEntrySerializer.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestDefaultCacheEntrySerializer.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,112 @@
+/*
+ * ====================================================================
+ * 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.client.cache.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+import java.util.Date;
+
+import org.apache.http.Header;
+import org.apache.http.HttpVersion;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.client.cache.HttpCacheEntrySerializer;
+import org.apache.http.client.cache.impl.CacheEntry;
+import org.apache.http.client.cache.impl.DefaultCacheEntrySerializer;
+import org.apache.http.message.BasicHeader;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestDefaultCacheEntrySerializer {
+
+ @Test
+ public void testSerialization() throws Exception {
+
+ HttpCacheEntrySerializer<CacheEntry> serializer = new DefaultCacheEntrySerializer();
+
+ // write the entry
+ CacheEntry writeEntry = newCacheEntry();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ serializer.writeTo(writeEntry, out);
+
+ // read the entry
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ CacheEntry readEntry = serializer.readFrom(in);
+
+ // compare
+ Assert.assertTrue(areEqual(readEntry, writeEntry));
+
+ }
+
+ private CacheEntry newCacheEntry() {
+
+ CacheEntry cacheEntry = new CacheEntry();
+
+ Header[] headers = new Header[5];
+ for (int i = 0; i < headers.length; i++) {
+ headers[i] = new BasicHeader("header" + i, "value" + i);
+ }
+ ProtocolVersion version = new HttpVersion(1, 1);
+ String body = "Lorem ipsum dolor sit amet";
+
+ cacheEntry.setResponseHeaders(headers);
+ cacheEntry.setProtocolVersion(version);
+ cacheEntry.setRequestDate(new Date());
+ cacheEntry.setResponseDate(new Date());
+ cacheEntry.setBody(body.getBytes());
+
+ return cacheEntry;
+
+ }
+
+ private boolean areEqual(CacheEntry one, CacheEntry two) {
+
+ if (!one.getRequestDate().equals(two.getRequestDate()))
+ return false;
+ if (!one.getResponseDate().equals(two.getResponseDate()))
+ return false;
+ if (!one.getProtocolVersion().equals(two.getProtocolVersion()))
+ return false;
+ if (!Arrays.equals(one.getBody(), two.getBody()))
+ return false;
+
+ Header[] oneHeaders = one.getAllHeaders();
+ Header[] twoHeaders = one.getAllHeaders();
+ if (!(oneHeaders.length == twoHeaders.length))
+ return false;
+ for (int i = 0; i < oneHeaders.length; i++) {
+ if (!oneHeaders[i].getName().equals(twoHeaders[i].getName()))
+ return false;
+ if (!oneHeaders[i].getValue().equals(twoHeaders[i].getValue()))
+ return false;
+ }
+
+ return true;
+
+ }
+
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolDeviations.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolDeviations.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolDeviations.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolDeviations.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,378 @@
+/*
+ * ====================================================================
+ * 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.client.cache.impl;
+
+import java.util.Date;
+import java.util.Random;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.cache.HttpCache;
+import org.apache.http.client.cache.impl.BasicHttpCache;
+import org.apache.http.client.cache.impl.CachingHttpClient;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.protocol.HttpContext;
+import org.easymock.Capture;
+import org.easymock.classextension.EasyMock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * We are a conditionally-compliant HTTP/1.1 client with a cache. However, a lot
+ * of the rules for proxies apply to us, as far as proper operation of the
+ * requests that pass through us. Generally speaking, we want to make sure that
+ * any response returned from our HttpClient.execute() methods is conditionally
+ * compliant with the rules for an HTTP/1.1 server, and that any requests we
+ * pass downstream to the backend HttpClient are are conditionally compliant
+ * with the rules for an HTTP/1.1 client.
+ *
+ * There are some cases where strictly behaving as a compliant caching proxy
+ * would result in strange behavior, since we're attached as part of a client
+ * and are expected to be a drop-in replacement. The test cases captured here
+ * document the places where we differ from the HTTP RFC.
+ */
+public class TestProtocolDeviations {
+
+ private static ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1);
+
+ private static int MAX_BYTES = 1024;
+ private static int MAX_ENTRIES = 100;
+ private int entityLength = 128;
+
+ private HttpHost host;
+ private HttpEntity body;
+ private HttpEntity mockEntity;
+ private HttpClient mockBackend;
+ private HttpCache<CacheEntry> mockCache;
+ private HttpRequest request;
+ private HttpResponse originResponse;
+
+ private CachingHttpClient impl;
+
+ @SuppressWarnings("unchecked")
+ @Before
+ public void setUp() {
+ host = new HttpHost("foo.example.com");
+
+ body = makeBody(entityLength);
+
+ request = new BasicHttpRequest("GET", "/foo", HTTP_1_1);
+
+ originResponse = make200Response();
+
+ HttpCache<CacheEntry> cache = new BasicHttpCache(MAX_ENTRIES);
+ mockBackend = EasyMock.createMock(HttpClient.class);
+ mockEntity = EasyMock.createMock(HttpEntity.class);
+ mockCache = EasyMock.createMock(HttpCache.class);
+ impl = new CachingHttpClient(mockBackend, cache, MAX_BYTES);
+ }
+
+ private HttpResponse make200Response() {
+ HttpResponse out = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_OK, "OK");
+ out.setHeader("Date", DateUtils.formatDate(new Date()));
+ out.setHeader("Server", "MockOrigin/1.0");
+ out.setEntity(makeBody(128));
+ return out;
+ }
+
+ private void replayMocks() {
+ EasyMock.replay(mockBackend);
+ EasyMock.replay(mockCache);
+ EasyMock.replay(mockEntity);
+ }
+
+ private void verifyMocks() {
+ EasyMock.verify(mockBackend);
+ EasyMock.verify(mockCache);
+ EasyMock.verify(mockEntity);
+ }
+
+ private HttpEntity makeBody(int nbytes) {
+ byte[] bytes = new byte[nbytes];
+ (new Random()).nextBytes(bytes);
+ return new ByteArrayEntity(bytes);
+ }
+
+ public static HttpRequest eqRequest(HttpRequest in) {
+ org.easymock.EasyMock.reportMatcher(new RequestEquivalent(in));
+ return null;
+ }
+
+ /*
+ * "For compatibility with HTTP/1.0 applications, HTTP/1.1 requests
+ * containing a message-body MUST include a valid Content-Length header
+ * field unless the server is known to be HTTP/1.1 compliant. If a request
+ * contains a message-body and a Content-Length is not given, the server
+ * SHOULD respond with 400 (bad request) if it cannot determine the length
+ * of the message, or with 411 (length required) if it wishes to insist on
+ * receiving a valid Content-Length."
+ *
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
+ */
+ @Test
+ public void testHTTP1_1RequestsWithBodiesOfKnownLengthMustHaveContentLength() throws Exception {
+ BasicHttpEntityEnclosingRequest post = new BasicHttpEntityEnclosingRequest("POST", "/",
+ HTTP_1_1);
+ post.setEntity(mockEntity);
+
+ replayMocks();
+
+ HttpResponse response = impl.execute(host, post);
+
+ verifyMocks();
+
+ Assert
+ .assertEquals(HttpStatus.SC_LENGTH_REQUIRED, response.getStatusLine()
+ .getStatusCode());
+ }
+
+ /*
+ * Discussion: if an incoming request has a body, but the HttpEntity
+ * attached has an unknown length (meaning entity.getContentLength() is
+ * negative), we have two choices if we want to be conditionally compliant.
+ * (1) we can slurp the whole body into a bytearray and compute its length
+ * before sending; or (2) we can push responsibility for (1) back onto the
+ * client by just generating a 411 response
+ *
+ * There is a third option, which is that we delegate responsibility for (1)
+ * onto the backend HttpClient, but because that is an injected dependency,
+ * we can't rely on it necessarily being conditionally compliant with
+ * HTTP/1.1. Currently, option (2) seems like the safest bet, as this
+ * exposes to the client application that the slurping required for (1)
+ * needs to happen in order to compute the content length.
+ *
+ * In any event, this test just captures the behavior required.
+ */
+ @Test
+ public void testHTTP1_1RequestsWithUnknownBodyLengthAreRejectedOrHaveContentLengthAdded()
+ throws Exception {
+ BasicHttpEntityEnclosingRequest post = new BasicHttpEntityEnclosingRequest("POST", "/",
+ HTTP_1_1);
+
+ byte[] bytes = new byte[128];
+ (new Random()).nextBytes(bytes);
+
+ HttpEntity mockBody = EasyMock.createMockBuilder(ByteArrayEntity.class).withConstructor(
+ new Object[] { bytes }).addMockedMethods("getContentLength").createMock();
+ org.easymock.EasyMock.expect(mockBody.getContentLength()).andReturn(-1L).anyTimes();
+ post.setEntity(mockBody);
+
+ Capture<HttpRequest> reqCap = new Capture<HttpRequest>();
+ org.easymock.EasyMock.expect(
+ mockBackend.execute(org.easymock.EasyMock.eq(host), org.easymock.EasyMock
+ .capture(reqCap), (HttpContext) org.easymock.EasyMock.isNull())).andReturn(
+ originResponse).times(0, 1);
+
+ replayMocks();
+ EasyMock.replay(mockBody);
+
+ HttpResponse result = impl.execute(host, post);
+
+ verifyMocks();
+ EasyMock.verify(mockBody);
+
+ if (reqCap.hasCaptured()) {
+ // backend request was made
+ HttpRequest forwarded = reqCap.getValue();
+ Assert.assertNotNull(forwarded.getFirstHeader("Content-Length"));
+ } else {
+ int status = result.getStatusLine().getStatusCode();
+ Assert.assertTrue(HttpStatus.SC_LENGTH_REQUIRED == status
+ || HttpStatus.SC_BAD_REQUEST == status);
+ }
+ }
+
+ /*
+ * "If the OPTIONS request includes an entity-body (as indicated by the
+ * presence of Content-Length or Transfer-Encoding), then the media type
+ * MUST be indicated by a Content-Type field."
+ *
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2
+ */
+ @Test
+ public void testOPTIONSRequestsWithBodiesAndNoContentTypeHaveOneSupplied() throws Exception {
+ BasicHttpEntityEnclosingRequest options = new BasicHttpEntityEnclosingRequest("OPTIONS",
+ "/", HTTP_1_1);
+ options.setEntity(body);
+ options.setHeader("Content-Length", "1");
+
+ Capture<HttpRequest> reqCap = new Capture<HttpRequest>();
+ org.easymock.EasyMock.expect(
+ mockBackend.execute(org.easymock.EasyMock.eq(host), org.easymock.EasyMock
+ .capture(reqCap), (HttpContext) org.easymock.EasyMock.isNull())).andReturn(
+ originResponse);
+ replayMocks();
+
+ impl.execute(host, options);
+
+ verifyMocks();
+
+ HttpRequest forwarded = reqCap.getValue();
+ Assert.assertTrue(forwarded instanceof HttpEntityEnclosingRequest);
+ HttpEntityEnclosingRequest reqWithBody = (HttpEntityEnclosingRequest) forwarded;
+ HttpEntity reqBody = reqWithBody.getEntity();
+ Assert.assertNotNull(reqBody);
+ Assert.assertNotNull(reqBody.getContentType());
+ }
+
+ /*
+ * "10.2.7 206 Partial Content ... The request MUST have included a Range
+ * header field (section 14.35) indicating the desired range, and MAY have
+ * included an If-Range header field (section 14.27) to make the request
+ * conditional."
+ *
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.7
+ */
+ @Test
+ public void testPartialContentIsNotReturnedToAClientThatDidNotAskForIt() throws Exception {
+
+ // tester's note: I don't know what the cache will *do* in
+ // this situation, but it better not just pass the response
+ // on.
+ request.removeHeaders("Range");
+ originResponse = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_PARTIAL_CONTENT,
+ "Partial Content");
+ originResponse.setHeader("Content-Range", "bytes 0-499/1234");
+ originResponse.setEntity(makeBody(500));
+
+ org.easymock.EasyMock.expect(
+ mockBackend.execute(org.easymock.EasyMock.isA(HttpHost.class),
+ org.easymock.EasyMock.isA(HttpRequest.class),
+ (HttpContext) org.easymock.EasyMock.isNull())).andReturn(originResponse);
+
+ replayMocks();
+ try {
+ HttpResponse result = impl.execute(host, request);
+ Assert.assertTrue(HttpStatus.SC_PARTIAL_CONTENT != result.getStatusLine()
+ .getStatusCode());
+ } catch (ClientProtocolException acceptableBehavior) {
+ // this is probably ok
+ }
+ }
+
+ /*
+ * "10.4.2 401 Unauthorized ... The response MUST include a WWW-Authenticate
+ * header field (section 14.47) containing a challenge applicable to the
+ * requested resource."
+ *
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
+ */
+ @Test(expected = ClientProtocolException.class)
+ public void testCantReturnOrigin401ResponseWithoutWWWAuthenticateHeader() throws Exception {
+
+ originResponse = new BasicHttpResponse(HTTP_1_1, 401, "Unauthorized");
+
+ org.easymock.EasyMock.expect(
+ mockBackend.execute(org.easymock.EasyMock.isA(HttpHost.class),
+ org.easymock.EasyMock.isA(HttpRequest.class),
+ (HttpContext) org.easymock.EasyMock.isNull())).andReturn(originResponse);
+ replayMocks();
+
+ // this is another case where we are caught in a sticky
+ // situation, where the origin was not 1.1-compliant.
+ try {
+ impl.execute(host, request);
+ } catch (ClientProtocolException possiblyAcceptableBehavior) {
+ verifyMocks();
+ throw possiblyAcceptableBehavior;
+ }
+ }
+
+ /*
+ * "10.4.6 405 Method Not Allowed ... The response MUST include an Allow
+ * header containing a list of valid methods for the requested resource.
+ *
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
+ */
+ @Test(expected = ClientProtocolException.class)
+ public void testCantReturnAnOrigin405WithoutAllowHeader() throws Exception {
+ originResponse = new BasicHttpResponse(HTTP_1_1, 405, "Method Not Allowed");
+
+ org.easymock.EasyMock.expect(
+ mockBackend.execute(org.easymock.EasyMock.isA(HttpHost.class),
+ org.easymock.EasyMock.isA(HttpRequest.class),
+ (HttpContext) org.easymock.EasyMock.isNull())).andReturn(originResponse);
+ replayMocks();
+
+ // this is another case where we are caught in a sticky
+ // situation, where the origin was not 1.1-compliant.
+ try {
+ impl.execute(host, request);
+ } catch (ClientProtocolException possiblyAcceptableBehavior) {
+ verifyMocks();
+ throw possiblyAcceptableBehavior;
+ }
+ }
+
+ /*
+ * "10.4.8 407 Proxy Authentication Required ... The proxy MUST return a
+ * Proxy-Authenticate header field (section 14.33) containing a challenge
+ * applicable to the proxy for the requested resource."
+ *
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8
+ */
+ @Test
+ public void testCantReturnA407WithoutAProxyAuthenticateHeader() throws Exception {
+ originResponse = new BasicHttpResponse(HTTP_1_1, 407, "Proxy Authentication Required");
+
+ org.easymock.EasyMock.expect(
+ mockBackend.execute(org.easymock.EasyMock.isA(HttpHost.class),
+ org.easymock.EasyMock.isA(HttpRequest.class),
+ (HttpContext) org.easymock.EasyMock.isNull())).andReturn(originResponse);
+ replayMocks();
+
+ boolean gotException = false;
+ // this is another case where we are caught in a sticky
+ // situation, where the origin was not 1.1-compliant.
+ try {
+ HttpResponse result = impl.execute(host, request);
+ Assert.fail("should have gotten ClientProtocolException");
+
+ if (result.getStatusLine().getStatusCode() == 407) {
+ Assert.assertNotNull(result.getFirstHeader("Proxy-Authentication"));
+ }
+ } catch (ClientProtocolException possiblyAcceptableBehavior) {
+ gotException = true;
+ }
+
+ verifyMocks();
+ Assert.assertTrue(gotException);
+ }
+
+}
\ No newline at end of file