View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.http.impl.client.cache;
28  
29  import static org.easymock.EasyMock.anyObject;
30  import static org.easymock.EasyMock.eq;
31  import static org.easymock.EasyMock.expect;
32  import static org.easymock.EasyMock.expectLastCall;
33  import static org.easymock.EasyMock.isA;
34  import static org.easymock.EasyMock.isNull;
35  import static org.easymock.classextension.EasyMock.createNiceMock;
36  import static org.easymock.classextension.EasyMock.replay;
37  import static org.easymock.classextension.EasyMock.verify;
38  import static org.junit.Assert.assertEquals;
39  import static org.junit.Assert.assertNull;
40  import static org.junit.Assert.assertSame;
41  import static org.junit.Assert.assertTrue;
42  
43  import java.io.IOException;
44  import java.io.InputStream;
45  import java.net.SocketException;
46  import java.net.SocketTimeoutException;
47  import java.util.ArrayList;
48  import java.util.Date;
49  import java.util.List;
50  
51  import junit.framework.AssertionFailedError;
52  
53  import org.apache.http.Header;
54  import org.apache.http.HttpHost;
55  import org.apache.http.HttpRequest;
56  import org.apache.http.HttpResponse;
57  import org.apache.http.HttpStatus;
58  import org.apache.http.HttpVersion;
59  import org.apache.http.StatusLine;
60  import org.apache.http.client.ClientProtocolException;
61  import org.apache.http.client.ResponseHandler;
62  import org.apache.http.client.cache.CacheResponseStatus;
63  import org.apache.http.client.cache.HttpCacheContext;
64  import org.apache.http.client.cache.HttpCacheEntry;
65  import org.apache.http.client.cache.HttpCacheStorage;
66  import org.apache.http.client.methods.CloseableHttpResponse;
67  import org.apache.http.client.methods.HttpExecutionAware;
68  import org.apache.http.client.methods.HttpGet;
69  import org.apache.http.client.methods.HttpOptions;
70  import org.apache.http.client.methods.HttpRequestWrapper;
71  import org.apache.http.client.methods.HttpUriRequest;
72  import org.apache.http.client.protocol.HttpClientContext;
73  import org.apache.http.client.utils.DateUtils;
74  import org.apache.http.conn.routing.HttpRoute;
75  import org.apache.http.entity.InputStreamEntity;
76  import org.apache.http.impl.execchain.ClientExecChain;
77  import org.apache.http.message.BasicHeader;
78  import org.apache.http.message.BasicHttpRequest;
79  import org.apache.http.message.BasicHttpResponse;
80  import org.apache.http.util.EntityUtils;
81  import org.easymock.Capture;
82  import org.easymock.IExpectationSetters;
83  import org.easymock.EasyMock;
84  import org.junit.Assert;
85  import org.junit.Before;
86  import org.junit.Test;
87  
88  @SuppressWarnings("boxing") // test code
89  public abstract class TestCachingExecChain {
90  
91      private ClientExecChain impl;
92  
93      protected CacheValidityPolicy mockValidityPolicy;
94      protected CacheableRequestPolicy mockRequestPolicy;
95      protected ClientExecChain mockBackend;
96      protected HttpCache mockCache;
97      private HttpCacheStorage mockStorage;
98      protected CachedResponseSuitabilityChecker mockSuitabilityChecker;
99      protected ResponseCachingPolicy mockResponsePolicy;
100     protected HttpCacheEntry mockCacheEntry;
101     protected CachedHttpResponseGenerator mockResponseGenerator;
102     private ResponseHandler<Object> mockHandler;
103     private HttpUriRequest mockUriRequest;
104     private CloseableHttpResponse mockCachedResponse;
105     protected ConditionalRequestBuilder mockConditionalRequestBuilder;
106     private HttpRequest mockConditionalRequest;
107     private StatusLine mockStatusLine;
108     protected ResponseProtocolCompliance mockResponseProtocolCompliance;
109     protected RequestProtocolCompliance mockRequestProtocolCompliance;
110     protected CacheConfig config;
111     protected AsynchronousValidator asyncValidator;
112 
113     protected HttpRoute route;
114     protected HttpHost host;
115     protected HttpRequestWrapper request;
116     protected HttpCacheContext context;
117     protected HttpCacheEntry entry;
118 
119     @SuppressWarnings("unchecked")
120     @Before
121     public void setUp() {
122         mockRequestPolicy = createNiceMock(CacheableRequestPolicy.class);
123         mockValidityPolicy = createNiceMock(CacheValidityPolicy.class);
124         mockBackend = createNiceMock(ClientExecChain.class);
125         mockCache = createNiceMock(HttpCache.class);
126         mockSuitabilityChecker = createNiceMock(CachedResponseSuitabilityChecker.class);
127         mockResponsePolicy = createNiceMock(ResponseCachingPolicy.class);
128         mockHandler = createNiceMock(ResponseHandler.class);
129         mockUriRequest = createNiceMock(HttpUriRequest.class);
130         mockCacheEntry = createNiceMock(HttpCacheEntry.class);
131         mockResponseGenerator = createNiceMock(CachedHttpResponseGenerator.class);
132         mockCachedResponse = createNiceMock(CloseableHttpResponse.class);
133         mockConditionalRequestBuilder = createNiceMock(ConditionalRequestBuilder.class);
134         mockConditionalRequest = createNiceMock(HttpRequest.class);
135         mockStatusLine = createNiceMock(StatusLine.class);
136         mockResponseProtocolCompliance = createNiceMock(ResponseProtocolCompliance.class);
137         mockRequestProtocolCompliance = createNiceMock(RequestProtocolCompliance.class);
138         mockStorage = createNiceMock(HttpCacheStorage.class);
139         config = CacheConfig.DEFAULT;
140         asyncValidator = new AsynchronousValidator(config);
141 
142         host = new HttpHost("foo.example.com", 80);
143         route = new HttpRoute(host);
144         request = HttpRequestWrapper.wrap(new BasicHttpRequest("GET", "/stuff",
145             HttpVersion.HTTP_1_1));
146         context = HttpCacheContext.create();
147         context.setTargetHost(host);
148         entry = HttpTestUtils.makeCacheEntry();
149         impl = createCachingExecChain(mockBackend, mockCache, mockValidityPolicy,
150             mockResponsePolicy, mockResponseGenerator, mockRequestPolicy, mockSuitabilityChecker,
151             mockConditionalRequestBuilder, mockResponseProtocolCompliance,
152             mockRequestProtocolCompliance, config, asyncValidator);
153     }
154 
155     public abstract ClientExecChain createCachingExecChain(ClientExecChain backend,
156         HttpCache responseCache, CacheValidityPolicy validityPolicy,
157         ResponseCachingPolicy responseCachingPolicy, CachedHttpResponseGenerator responseGenerator,
158         CacheableRequestPolicy cacheableRequestPolicy,
159         CachedResponseSuitabilityChecker suitabilityChecker,
160         ConditionalRequestBuilder conditionalRequestBuilder,
161         ResponseProtocolCompliance responseCompliance, RequestProtocolCompliance requestCompliance,
162         CacheConfig config, AsynchronousValidator asynchRevalidator);
163 
164     public abstract ClientExecChain createCachingExecChain(ClientExecChain backend,
165         HttpCache cache, CacheConfig config);
166 
167     public static HttpRequestWrapper eqRequest(final HttpRequestWrapper in) {
168         EasyMock.reportMatcher(new RequestEquivalent(in));
169         return null;
170     }
171 
172     public static <R extends HttpResponse> R eqResponse(final R in) {
173         EasyMock.reportMatcher(new ResponseEquivalent(in));
174         return null;
175     }
176 
177     protected void replayMocks() {
178         replay(mockRequestPolicy);
179         replay(mockValidityPolicy);
180         replay(mockSuitabilityChecker);
181         replay(mockResponsePolicy);
182         replay(mockCacheEntry);
183         replay(mockResponseGenerator);
184         replay(mockBackend);
185         replay(mockCache);
186         replay(mockHandler);
187         replay(mockUriRequest);
188         replay(mockCachedResponse);
189         replay(mockConditionalRequestBuilder);
190         replay(mockConditionalRequest);
191         replay(mockStatusLine);
192         replay(mockResponseProtocolCompliance);
193         replay(mockRequestProtocolCompliance);
194         replay(mockStorage);
195     }
196 
197     protected void verifyMocks() {
198         verify(mockRequestPolicy);
199         verify(mockValidityPolicy);
200         verify(mockSuitabilityChecker);
201         verify(mockResponsePolicy);
202         verify(mockCacheEntry);
203         verify(mockResponseGenerator);
204         verify(mockBackend);
205         verify(mockCache);
206         verify(mockHandler);
207         verify(mockUriRequest);
208         verify(mockCachedResponse);
209         verify(mockConditionalRequestBuilder);
210         verify(mockConditionalRequest);
211         verify(mockStatusLine);
212         verify(mockResponseProtocolCompliance);
213         verify(mockRequestProtocolCompliance);
214         verify(mockStorage);
215     }
216 
217     @Test
218     public void testCacheableResponsesGoIntoCache() throws Exception {
219         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
220 
221         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
222         final HttpResponse resp1 = HttpTestUtils.make200Response();
223         resp1.setHeader("Cache-Control", "max-age=3600");
224 
225         backendExpectsAnyRequestAndReturn(resp1);
226 
227         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
228 
229         replayMocks();
230         impl.execute(route, req1, context, null);
231         impl.execute(route, req2, context, null);
232         verifyMocks();
233     }
234 
235     @Test
236     public void testOlderCacheableResponsesDoNotGoIntoCache() throws Exception {
237         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
238         final Date now = new Date();
239         final Date fiveSecondsAgo = new Date(now.getTime() - 5 * 1000L);
240 
241         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
242         final HttpResponse resp1 = HttpTestUtils.make200Response();
243         resp1.setHeader("Date", DateUtils.formatDate(now));
244         resp1.setHeader("Cache-Control", "max-age=3600");
245         resp1.setHeader("Etag", "\"new-etag\"");
246 
247         backendExpectsAnyRequestAndReturn(resp1);
248 
249         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
250         req2.setHeader("Cache-Control", "no-cache");
251         final HttpResponse resp2 = HttpTestUtils.make200Response();
252         resp2.setHeader("ETag", "\"old-etag\"");
253         resp2.setHeader("Date", DateUtils.formatDate(fiveSecondsAgo));
254         resp2.setHeader("Cache-Control", "max-age=3600");
255 
256         backendExpectsAnyRequestAndReturn(resp2);
257 
258         final HttpRequestWrapper req3 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
259 
260         replayMocks();
261         impl.execute(route, req1, context, null);
262         impl.execute(route, req2, context, null);
263         final HttpResponse result = impl.execute(route, req3, context, null);
264         verifyMocks();
265 
266         assertEquals("\"new-etag\"", result.getFirstHeader("ETag").getValue());
267     }
268 
269     @Test
270     public void testNewerCacheableResponsesReplaceExistingCacheEntry() throws Exception {
271         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
272         final Date now = new Date();
273         final Date fiveSecondsAgo = new Date(now.getTime() - 5 * 1000L);
274 
275         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
276         final HttpResponse resp1 = HttpTestUtils.make200Response();
277         resp1.setHeader("Date", DateUtils.formatDate(fiveSecondsAgo));
278         resp1.setHeader("Cache-Control", "max-age=3600");
279         resp1.setHeader("Etag", "\"old-etag\"");
280 
281         backendExpectsAnyRequestAndReturn(resp1);
282 
283         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
284         req2.setHeader("Cache-Control", "max-age=0");
285         final HttpResponse resp2 = HttpTestUtils.make200Response();
286         resp2.setHeader("ETag", "\"new-etag\"");
287         resp2.setHeader("Date", DateUtils.formatDate(now));
288         resp2.setHeader("Cache-Control", "max-age=3600");
289 
290         backendExpectsAnyRequestAndReturn(resp2);
291 
292         final HttpRequestWrapper req3 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
293 
294         replayMocks();
295         impl.execute(route, req1, context, null);
296         impl.execute(route, req2, context, null);
297         final HttpResponse result = impl.execute(route, req3, context, null);
298         verifyMocks();
299 
300         assertEquals("\"new-etag\"", result.getFirstHeader("ETag").getValue());
301     }
302 
303     protected void requestIsFatallyNonCompliant(final RequestProtocolError error) {
304         final List<RequestProtocolError> errors = new ArrayList<RequestProtocolError>();
305         if (error != null) {
306             errors.add(error);
307         }
308         expect(mockRequestProtocolCompliance.requestIsFatallyNonCompliant(eqRequest(request)))
309             .andReturn(errors);
310     }
311 
312     @Test
313     public void testSuitableCacheEntryDoesNotCauseBackendRequest() throws Exception {
314         requestPolicyAllowsCaching(true);
315         getCacheEntryReturns(mockCacheEntry);
316         cacheEntrySuitable(true);
317         responseIsGeneratedFromCache();
318         requestIsFatallyNonCompliant(null);
319         entryHasStaleness(0L);
320 
321         replayMocks();
322         final HttpResponse result = impl.execute(route, request, context, null);
323         verifyMocks();
324 
325         Assert.assertSame(mockCachedResponse, result);
326     }
327 
328     @Test
329     public void testNonCacheableResponseIsNotCachedAndIsReturnedAsIs() throws Exception {
330         final CacheConfig configDefault = CacheConfig.DEFAULT;
331         impl = createCachingExecChain(mockBackend, new BasicHttpCache(new HeapResourceFactory(),
332             mockStorage, configDefault), configDefault);
333 
334         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
335         final HttpResponse resp1 = HttpTestUtils.make200Response();
336         resp1.setHeader("Cache-Control", "no-cache");
337 
338         expect(mockStorage.getEntry(isA(String.class))).andReturn(null).anyTimes();
339         mockStorage.removeEntry(isA(String.class));
340         expectLastCall().anyTimes();
341         backendExpectsAnyRequestAndReturn(resp1);
342 
343         replayMocks();
344         final HttpResponse result = impl.execute(route, req1, context, null);
345         verifyMocks();
346 
347         assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result));
348     }
349 
350     @Test
351     public void testResponseIsGeneratedWhenCacheEntryIsUsable() throws Exception {
352 
353         requestIsFatallyNonCompliant(null);
354         requestPolicyAllowsCaching(true);
355         cacheEntrySuitable(true);
356         getCacheEntryReturns(mockCacheEntry);
357         responseIsGeneratedFromCache();
358         entryHasStaleness(0L);
359 
360         replayMocks();
361         impl.execute(route, request, context, null);
362         verifyMocks();
363     }
364 
365     @Test
366     public void testNonCompliantRequestWrapsAndReThrowsProtocolException() throws Exception {
367 
368         final ClientProtocolException expected = new ClientProtocolException("ouch");
369 
370         requestIsFatallyNonCompliant(null);
371         mockRequestProtocolCompliance.makeRequestCompliant((HttpRequestWrapper) anyObject());
372         expectLastCall().andThrow(expected);
373 
374         boolean gotException = false;
375         replayMocks();
376         try {
377             impl.execute(route, request, context, null);
378         } catch (final ClientProtocolException ex) {
379             Assert.assertSame(expected, ex);
380             gotException = true;
381         }
382         verifyMocks();
383         Assert.assertTrue(gotException);
384     }
385 
386     @Test
387     public void testSetsModuleGeneratedResponseContextForCacheOptionsResponse() throws Exception {
388         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
389         final HttpRequestWrapper req = HttpRequestWrapper.wrap(new BasicHttpRequest("OPTIONS", "*",
390             HttpVersion.HTTP_1_1));
391         req.setHeader("Max-Forwards", "0");
392 
393         impl.execute(route, req, context, null);
394         Assert.assertEquals(CacheResponseStatus.CACHE_MODULE_RESPONSE,
395             context.getCacheResponseStatus());
396     }
397 
398     @Test
399     public void testSetsModuleGeneratedResponseContextForFatallyNoncompliantRequest()
400         throws Exception {
401         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
402         final HttpRequestWrapper req = HttpRequestWrapper.wrap(new HttpGet(
403             "http://foo.example.com/"));
404         req.setHeader("Range", "bytes=0-50");
405         req.setHeader("If-Range", "W/\"weak-etag\"");
406 
407         impl.execute(route, req, context, null);
408         Assert.assertEquals(CacheResponseStatus.CACHE_MODULE_RESPONSE,
409             context.getCacheResponseStatus());
410     }
411 
412     @Test
413     public void testRecordsClientProtocolInViaHeaderIfRequestNotServableFromCache()
414         throws Exception {
415         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
416         final HttpRequestWrapper req = HttpRequestWrapper.wrap(new BasicHttpRequest("GET", "/",
417             HttpVersion.HTTP_1_0));
418         req.setHeader("Cache-Control", "no-cache");
419         final HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1,
420             HttpStatus.SC_NO_CONTENT, "No Content");
421         final Capture<HttpRequestWrapper> cap = new Capture<HttpRequestWrapper>();
422 
423         backendCaptureRequestAndReturn(cap, resp);
424 
425         replayMocks();
426         impl.execute(route, req, context, null);
427         verifyMocks();
428 
429         final HttpRequest captured = cap.getValue();
430         final String via = captured.getFirstHeader("Via").getValue();
431         final String proto = via.split("\\s+")[0];
432         Assert.assertTrue("http/1.0".equalsIgnoreCase(proto) || "1.0".equalsIgnoreCase(proto));
433     }
434 
435     @Test
436     public void testSetsCacheMissContextIfRequestNotServableFromCache() throws Exception {
437         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
438         final HttpRequestWrapper req = HttpRequestWrapper.wrap(new HttpGet(
439             "http://foo.example.com/"));
440         req.setHeader("Cache-Control", "no-cache");
441         final HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1,
442             HttpStatus.SC_NO_CONTENT, "No Content");
443 
444         backendExpectsAnyRequestAndReturn(resp);
445 
446         replayMocks();
447         impl.execute(route, req, context, null);
448         verifyMocks();
449         Assert.assertEquals(CacheResponseStatus.CACHE_MISS, context.getCacheResponseStatus());
450     }
451 
452     @Test
453     public void testSetsViaHeaderOnResponseIfRequestNotServableFromCache() throws Exception {
454         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
455         final HttpRequestWrapper req = HttpRequestWrapper.wrap(new HttpGet(
456             "http://foo.example.com/"));
457         req.setHeader("Cache-Control", "no-cache");
458         final HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1,
459             HttpStatus.SC_NO_CONTENT, "No Content");
460 
461         backendExpectsAnyRequestAndReturn(resp);
462 
463         replayMocks();
464         final HttpResponse result = impl.execute(route, req, context, null);
465         verifyMocks();
466         Assert.assertNotNull(result.getFirstHeader("Via"));
467     }
468 
469     @Test
470     public void testSetsViaHeaderOnResponseForCacheMiss() throws Exception {
471         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
472         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
473             "http://foo.example.com/"));
474         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
475             "OK");
476         resp1.setEntity(HttpTestUtils.makeBody(128));
477         resp1.setHeader("Content-Length", "128");
478         resp1.setHeader("ETag", "\"etag\"");
479         resp1.setHeader("Date", DateUtils.formatDate(new Date()));
480         resp1.setHeader("Cache-Control", "public, max-age=3600");
481 
482         backendExpectsAnyRequestAndReturn(resp1);
483 
484         replayMocks();
485         final HttpResponse result = impl.execute(route, req1, context, null);
486         verifyMocks();
487         Assert.assertNotNull(result.getFirstHeader("Via"));
488     }
489 
490     @Test
491     public void testSetsCacheHitContextIfRequestServedFromCache() throws Exception {
492         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
493         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
494             "http://foo.example.com/"));
495         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
496             "http://foo.example.com/"));
497         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
498             "OK");
499         resp1.setEntity(HttpTestUtils.makeBody(128));
500         resp1.setHeader("Content-Length", "128");
501         resp1.setHeader("ETag", "\"etag\"");
502         resp1.setHeader("Date", DateUtils.formatDate(new Date()));
503         resp1.setHeader("Cache-Control", "public, max-age=3600");
504 
505         backendExpectsAnyRequestAndReturn(resp1);
506 
507         replayMocks();
508         impl.execute(route, req1, context, null);
509         impl.execute(route, req2, context, null);
510         verifyMocks();
511         Assert.assertEquals(CacheResponseStatus.CACHE_HIT, context.getCacheResponseStatus());
512     }
513 
514     @Test
515     public void testSetsViaHeaderOnResponseIfRequestServedFromCache() throws Exception {
516         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
517         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
518             "http://foo.example.com/"));
519         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
520             "http://foo.example.com/"));
521         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
522             "OK");
523         resp1.setEntity(HttpTestUtils.makeBody(128));
524         resp1.setHeader("Content-Length", "128");
525         resp1.setHeader("ETag", "\"etag\"");
526         resp1.setHeader("Date", DateUtils.formatDate(new Date()));
527         resp1.setHeader("Cache-Control", "public, max-age=3600");
528 
529         backendExpectsAnyRequestAndReturn(resp1);
530 
531         replayMocks();
532         impl.execute(route, req1, context, null);
533         final HttpResponse result = impl.execute(route, req2, context, null);
534         verifyMocks();
535         Assert.assertNotNull(result.getFirstHeader("Via"));
536     }
537 
538     @Test
539     public void testReturns304ForIfModifiedSinceHeaderIfRequestServedFromCache() throws Exception {
540         final Date now = new Date();
541         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
542         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
543         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
544             "http://foo.example.com/"));
545         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
546             "http://foo.example.com/"));
547         req2.addHeader("If-Modified-Since", DateUtils.formatDate(now));
548         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
549             "OK");
550         resp1.setEntity(HttpTestUtils.makeBody(128));
551         resp1.setHeader("Content-Length", "128");
552         resp1.setHeader("ETag", "\"etag\"");
553         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
554         resp1.setHeader("Cache-Control", "public, max-age=3600");
555         resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
556 
557         backendExpectsAnyRequestAndReturn(resp1);
558 
559         replayMocks();
560         impl.execute(route, req1, context, null);
561         final HttpResponse result = impl.execute(route, req2, context, null);
562         verifyMocks();
563         Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getStatusLine().getStatusCode());
564 
565     }
566 
567     @Test
568     public void testReturns304ForIfModifiedSinceHeaderIf304ResponseInCache() throws Exception {
569         final Date now = new Date();
570         final Date oneHourAgo = new Date(now.getTime() - 3600 * 1000L);
571         final Date inTenMinutes = new Date(now.getTime() + 600 * 1000L);
572         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
573         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
574             "http://foo.example.com/"));
575         req1.addHeader("If-Modified-Since", DateUtils.formatDate(oneHourAgo));
576         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
577             "http://foo.example.com/"));
578         req2.addHeader("If-Modified-Since", DateUtils.formatDate(oneHourAgo));
579 
580         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
581             HttpStatus.SC_NOT_MODIFIED, "Not modified");
582         resp1.setHeader("Date", DateUtils.formatDate(now));
583         resp1.setHeader("Cache-control", "max-age=600");
584         resp1.setHeader("Expires", DateUtils.formatDate(inTenMinutes));
585 
586         expect(
587             mockBackend.execute(eq(route), isA(HttpRequestWrapper.class),
588                 isA(HttpClientContext.class), (HttpExecutionAware) isNull())).andReturn(
589             Proxies.enhanceResponse(resp1)).once();
590 
591         expect(
592             mockBackend.execute(eq(route), isA(HttpRequestWrapper.class),
593                 isA(HttpClientContext.class), (HttpExecutionAware) isNull())).andThrow(
594             new AssertionFailedError("Should have reused cached 304 response")).anyTimes();
595 
596         replayMocks();
597         impl.execute(route, req1, context, null);
598         final HttpResponse result = impl.execute(route, req2, context, null);
599         verifyMocks();
600         Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getStatusLine().getStatusCode());
601         Assert.assertFalse(result.containsHeader("Last-Modified"));
602     }
603 
604     @Test
605     public void testReturns200ForIfModifiedSinceDateIsLess() throws Exception {
606         final Date now = new Date();
607         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
608         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
609         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
610             "http://foo.example.com/"));
611         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
612             "http://foo.example.com/"));
613 
614         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
615             "OK");
616         resp1.setEntity(HttpTestUtils.makeBody(128));
617         resp1.setHeader("Content-Length", "128");
618         resp1.setHeader("ETag", "\"etag\"");
619         resp1.setHeader("Date", DateUtils.formatDate(new Date()));
620         resp1.setHeader("Cache-Control", "public, max-age=3600");
621         resp1.setHeader("Last-Modified", DateUtils.formatDate(new Date()));
622 
623         // The variant has been modified since this date
624         req2.addHeader("If-Modified-Since", DateUtils.formatDate(tenSecondsAgo));
625 
626         final HttpResponse resp2 = HttpTestUtils.make200Response();
627 
628         backendExpectsAnyRequestAndReturn(resp1);
629         backendExpectsAnyRequestAndReturn(resp2);
630 
631         replayMocks();
632         impl.execute(route, req1, context, null);
633         final HttpResponse result = impl.execute(route, req2, context, null);
634         verifyMocks();
635         Assert.assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode());
636 
637     }
638 
639     @Test
640     public void testReturns200ForIfModifiedSinceDateIsInvalid() throws Exception {
641         final Date now = new Date();
642         final Date tenSecondsAfter = new Date(now.getTime() + 10 * 1000L);
643         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
644         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
645             "http://foo.example.com/"));
646         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
647             "http://foo.example.com/"));
648 
649         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
650             "OK");
651         resp1.setEntity(HttpTestUtils.makeBody(128));
652         resp1.setHeader("Content-Length", "128");
653         resp1.setHeader("ETag", "\"etag\"");
654         resp1.setHeader("Date", DateUtils.formatDate(new Date()));
655         resp1.setHeader("Cache-Control", "public, max-age=3600");
656         resp1.setHeader("Last-Modified", DateUtils.formatDate(new Date()));
657 
658         // invalid date (date in the future)
659         req2.addHeader("If-Modified-Since", DateUtils.formatDate(tenSecondsAfter));
660 
661         backendExpectsAnyRequestAndReturn(resp1).times(2);
662 
663         replayMocks();
664         impl.execute(route, req1, context, null);
665         final HttpResponse result = impl.execute(route, req2, context, null);
666         verifyMocks();
667         Assert.assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode());
668 
669     }
670 
671     @Test
672     public void testReturns304ForIfNoneMatchHeaderIfRequestServedFromCache() throws Exception {
673         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
674         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
675             "http://foo.example.com/"));
676         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
677             "http://foo.example.com/"));
678         req2.addHeader("If-None-Match", "*");
679         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
680             "OK");
681         resp1.setEntity(HttpTestUtils.makeBody(128));
682         resp1.setHeader("Content-Length", "128");
683         resp1.setHeader("ETag", "\"etag\"");
684         resp1.setHeader("Date", DateUtils.formatDate(new Date()));
685         resp1.setHeader("Cache-Control", "public, max-age=3600");
686 
687         backendExpectsAnyRequestAndReturn(resp1);
688 
689         replayMocks();
690         impl.execute(route, req1, context, null);
691         final HttpResponse result = impl.execute(route, req2, context, null);
692         verifyMocks();
693         Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getStatusLine().getStatusCode());
694 
695     }
696 
697     @Test
698     public void testReturns200ForIfNoneMatchHeaderFails() throws Exception {
699         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
700         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
701             "http://foo.example.com/"));
702         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
703             "http://foo.example.com/"));
704 
705         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
706             "OK");
707         resp1.setEntity(HttpTestUtils.makeBody(128));
708         resp1.setHeader("Content-Length", "128");
709         resp1.setHeader("ETag", "\"etag\"");
710         resp1.setHeader("Date", DateUtils.formatDate(new Date()));
711         resp1.setHeader("Cache-Control", "public, max-age=3600");
712 
713         req2.addHeader("If-None-Match", "\"abc\"");
714 
715         final HttpResponse resp2 = HttpTestUtils.make200Response();
716 
717         backendExpectsAnyRequestAndReturn(resp1);
718         backendExpectsAnyRequestAndReturn(resp2);
719 
720         replayMocks();
721         impl.execute(route, req1, context, null);
722         final HttpResponse result = impl.execute(route, req2, context, null);
723         verifyMocks();
724         Assert.assertEquals(200, result.getStatusLine().getStatusCode());
725 
726     }
727 
728     @Test
729     public void testReturns304ForIfNoneMatchHeaderAndIfModifiedSinceIfRequestServedFromCache()
730         throws Exception {
731         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
732         final Date now = new Date();
733         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
734         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
735             "http://foo.example.com/"));
736         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
737             "http://foo.example.com/"));
738 
739         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
740             "OK");
741         resp1.setEntity(HttpTestUtils.makeBody(128));
742         resp1.setHeader("Content-Length", "128");
743         resp1.setHeader("ETag", "\"etag\"");
744         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
745         resp1.setHeader("Cache-Control", "public, max-age=3600");
746         resp1.setHeader("Last-Modified", DateUtils.formatDate(new Date()));
747 
748         req2.addHeader("If-None-Match", "*");
749         req2.addHeader("If-Modified-Since", DateUtils.formatDate(now));
750 
751         backendExpectsAnyRequestAndReturn(resp1);
752 
753         replayMocks();
754         impl.execute(route, req1, context, null);
755         final HttpResponse result = impl.execute(route, req2, context, null);
756         verifyMocks();
757         Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getStatusLine().getStatusCode());
758 
759     }
760 
761     @Test
762     public void testReturns200ForIfNoneMatchHeaderFailsIfModifiedSinceIgnored() throws Exception {
763         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
764         final Date now = new Date();
765         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
766         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
767             "http://foo.example.com/"));
768         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
769             "http://foo.example.com/"));
770         req2.addHeader("If-None-Match", "\"abc\"");
771         req2.addHeader("If-Modified-Since", DateUtils.formatDate(now));
772         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
773             "OK");
774         resp1.setEntity(HttpTestUtils.makeBody(128));
775         resp1.setHeader("Content-Length", "128");
776         resp1.setHeader("ETag", "\"etag\"");
777         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
778         resp1.setHeader("Cache-Control", "public, max-age=3600");
779         resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
780 
781         backendExpectsAnyRequestAndReturn(resp1);
782         backendExpectsAnyRequestAndReturn(resp1);
783 
784         replayMocks();
785         impl.execute(route, req1, context, null);
786         final HttpResponse result = impl.execute(route, req2, context, null);
787         verifyMocks();
788         Assert.assertEquals(200, result.getStatusLine().getStatusCode());
789 
790     }
791 
792     @Test
793     public void testReturns200ForOptionsFollowedByGetIfAuthorizationHeaderAndSharedCache()
794         throws Exception {
795         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.custom()
796             .setSharedCache(true).build());
797         final Date now = new Date();
798         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpOptions(
799             "http://foo.example.com/"));
800         req1.setHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
801         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
802             "http://foo.example.com/"));
803         req2.setHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
804         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
805             HttpStatus.SC_NO_CONTENT, "No Content");
806         resp1.setHeader("Content-Length", "0");
807         resp1.setHeader("ETag", "\"options-etag\"");
808         resp1.setHeader("Date", DateUtils.formatDate(now));
809         resp1.setHeader("Cache-Control", "public, max-age=3600");
810         resp1.setHeader("Last-Modified", DateUtils.formatDate(now));
811         final HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
812             "OK");
813         resp1.setEntity(HttpTestUtils.makeBody(128));
814         resp1.setHeader("Content-Length", "128");
815         resp1.setHeader("ETag", "\"get-etag\"");
816         resp1.setHeader("Date", DateUtils.formatDate(now));
817         resp1.setHeader("Cache-Control", "public, max-age=3600");
818         resp1.setHeader("Last-Modified", DateUtils.formatDate(now));
819 
820         backendExpectsAnyRequestAndReturn(resp1);
821         backendExpectsAnyRequestAndReturn(resp2);
822 
823         replayMocks();
824         impl.execute(route, req1, context, null);
825         final HttpResponse result = impl.execute(route, req2, context, null);
826         verifyMocks();
827         Assert.assertEquals(200, result.getStatusLine().getStatusCode());
828     }
829 
830     @Test
831     public void testSetsValidatedContextIfRequestWasSuccessfullyValidated() throws Exception {
832         final Date now = new Date();
833         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
834 
835         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
836         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
837             "http://foo.example.com/"));
838         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
839             "http://foo.example.com/"));
840 
841         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
842             "OK");
843         resp1.setEntity(HttpTestUtils.makeBody(128));
844         resp1.setHeader("Content-Length", "128");
845         resp1.setHeader("ETag", "\"etag\"");
846         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
847         resp1.setHeader("Cache-Control", "public, max-age=5");
848 
849         final HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
850             "OK");
851         resp2.setEntity(HttpTestUtils.makeBody(128));
852         resp2.setHeader("Content-Length", "128");
853         resp2.setHeader("ETag", "\"etag\"");
854         resp2.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
855         resp2.setHeader("Cache-Control", "public, max-age=5");
856 
857         backendExpectsAnyRequestAndReturn(resp1);
858         backendExpectsAnyRequestAndReturn(resp2);
859 
860         replayMocks();
861         impl.execute(route, req1, context, null);
862         impl.execute(route, req2, context, null);
863         verifyMocks();
864         Assert.assertEquals(CacheResponseStatus.VALIDATED, context.getCacheResponseStatus());
865     }
866 
867     @Test
868     public void testSetsViaHeaderIfRequestWasSuccessfullyValidated() throws Exception {
869         final Date now = new Date();
870         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
871 
872         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
873         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
874             "http://foo.example.com/"));
875         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
876             "http://foo.example.com/"));
877 
878         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
879             "OK");
880         resp1.setEntity(HttpTestUtils.makeBody(128));
881         resp1.setHeader("Content-Length", "128");
882         resp1.setHeader("ETag", "\"etag\"");
883         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
884         resp1.setHeader("Cache-Control", "public, max-age=5");
885 
886         final HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
887             "OK");
888         resp2.setEntity(HttpTestUtils.makeBody(128));
889         resp2.setHeader("Content-Length", "128");
890         resp2.setHeader("ETag", "\"etag\"");
891         resp2.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
892         resp2.setHeader("Cache-Control", "public, max-age=5");
893 
894         backendExpectsAnyRequestAndReturn(resp1);
895         backendExpectsAnyRequestAndReturn(resp2);
896 
897         replayMocks();
898         impl.execute(route, req1, context, null);
899         final HttpResponse result = impl.execute(route, req2, context, null);
900         verifyMocks();
901         Assert.assertNotNull(result.getFirstHeader("Via"));
902     }
903 
904     @Test
905     public void testSetsModuleResponseContextIfValidationRequiredButFailed() throws Exception {
906         final Date now = new Date();
907         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
908 
909         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
910         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
911             "http://foo.example.com/"));
912         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
913             "http://foo.example.com/"));
914 
915         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
916             "OK");
917         resp1.setEntity(HttpTestUtils.makeBody(128));
918         resp1.setHeader("Content-Length", "128");
919         resp1.setHeader("ETag", "\"etag\"");
920         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
921         resp1.setHeader("Cache-Control", "public, max-age=5, must-revalidate");
922 
923         backendExpectsAnyRequestAndReturn(resp1);
924         backendExpectsAnyRequestAndThrows(new IOException());
925 
926         replayMocks();
927         impl.execute(route, req1, context, null);
928         impl.execute(route, req2, context, null);
929         verifyMocks();
930         Assert.assertEquals(CacheResponseStatus.CACHE_MODULE_RESPONSE,
931             context.getCacheResponseStatus());
932     }
933 
934     @Test
935     public void testSetsModuleResponseContextIfValidationFailsButNotRequired() throws Exception {
936         final Date now = new Date();
937         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
938 
939         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
940         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
941             "http://foo.example.com/"));
942         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
943             "http://foo.example.com/"));
944 
945         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
946             "OK");
947         resp1.setEntity(HttpTestUtils.makeBody(128));
948         resp1.setHeader("Content-Length", "128");
949         resp1.setHeader("ETag", "\"etag\"");
950         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
951         resp1.setHeader("Cache-Control", "public, max-age=5");
952 
953         backendExpectsAnyRequestAndReturn(resp1);
954         backendExpectsAnyRequestAndThrows(new IOException());
955 
956         replayMocks();
957         impl.execute(route, req1, context, null);
958         impl.execute(route, req2, context, null);
959         verifyMocks();
960         Assert.assertEquals(CacheResponseStatus.CACHE_HIT, context.getCacheResponseStatus());
961     }
962 
963     @Test
964     public void testSetViaHeaderIfValidationFailsButNotRequired() throws Exception {
965         final Date now = new Date();
966         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
967 
968         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
969         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
970             "http://foo.example.com/"));
971         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
972             "http://foo.example.com/"));
973 
974         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
975             "OK");
976         resp1.setEntity(HttpTestUtils.makeBody(128));
977         resp1.setHeader("Content-Length", "128");
978         resp1.setHeader("ETag", "\"etag\"");
979         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
980         resp1.setHeader("Cache-Control", "public, max-age=5");
981 
982         backendExpectsAnyRequestAndReturn(resp1);
983         backendExpectsAnyRequestAndThrows(new IOException());
984 
985         replayMocks();
986         impl.execute(route, req1, context, null);
987         final HttpResponse result = impl.execute(route, req2, context, null);
988         verifyMocks();
989         Assert.assertNotNull(result.getFirstHeader("Via"));
990     }
991 
992     @Test
993     public void testReturns304ForIfNoneMatchPassesIfRequestServedFromOrigin() throws Exception {
994 
995         final Date now = new Date();
996         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
997 
998         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
999         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
1000             "http://foo.example.com/"));
1001         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
1002             "http://foo.example.com/"));
1003 
1004         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1005             "OK");
1006         resp1.setEntity(HttpTestUtils.makeBody(128));
1007         resp1.setHeader("Content-Length", "128");
1008         resp1.setHeader("ETag", "\"etag\"");
1009         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
1010         resp1.setHeader("Cache-Control", "public, max-age=5");
1011 
1012         req2.addHeader("If-None-Match", "\"etag\"");
1013         final HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
1014             HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1015         resp2.setHeader("ETag", "\"etag\"");
1016         resp2.setHeader("Date", DateUtils.formatDate(now));
1017         resp2.setHeader("Cache-Control", "public, max-age=5");
1018 
1019         backendExpectsAnyRequestAndReturn(resp1);
1020         backendExpectsAnyRequestAndReturn(resp2);
1021         replayMocks();
1022         impl.execute(route, req1, context, null);
1023         final HttpResponse result = impl.execute(route, req2, context, null);
1024         verifyMocks();
1025 
1026         Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getStatusLine().getStatusCode());
1027     }
1028 
1029     @Test
1030     public void testReturns200ForIfNoneMatchFailsIfRequestServedFromOrigin() throws Exception {
1031 
1032         final Date now = new Date();
1033         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
1034 
1035         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
1036         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
1037             "http://foo.example.com/"));
1038         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
1039             "http://foo.example.com/"));
1040 
1041         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1042             "OK");
1043         resp1.setEntity(HttpTestUtils.makeBody(128));
1044         resp1.setHeader("Content-Length", "128");
1045         resp1.setHeader("ETag", "\"etag\"");
1046         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
1047         resp1.setHeader("Cache-Control", "public, max-age=5");
1048 
1049         req2.addHeader("If-None-Match", "\"etag\"");
1050         final HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1051             "OK");
1052         resp2.setEntity(HttpTestUtils.makeBody(128));
1053         resp2.setHeader("Content-Length", "128");
1054         resp2.setHeader("ETag", "\"newetag\"");
1055         resp2.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
1056         resp2.setHeader("Cache-Control", "public, max-age=5");
1057 
1058         backendExpectsAnyRequestAndReturn(resp1);
1059         backendExpectsAnyRequestAndReturn(resp2);
1060 
1061         replayMocks();
1062         impl.execute(route, req1, context, null);
1063         final HttpResponse result = impl.execute(route, req2, context, null);
1064         verifyMocks();
1065 
1066         Assert.assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode());
1067     }
1068 
1069     @Test
1070     public void testReturns304ForIfModifiedSincePassesIfRequestServedFromOrigin() throws Exception {
1071         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
1072 
1073         final Date now = new Date();
1074         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
1075 
1076         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
1077             "http://foo.example.com/"));
1078         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
1079             "http://foo.example.com/"));
1080 
1081         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1082             "OK");
1083         resp1.setEntity(HttpTestUtils.makeBody(128));
1084         resp1.setHeader("Content-Length", "128");
1085         resp1.setHeader("ETag", "\"etag\"");
1086         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
1087         resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
1088         resp1.setHeader("Cache-Control", "public, max-age=5");
1089 
1090         req2.addHeader("If-Modified-Since", DateUtils.formatDate(tenSecondsAgo));
1091         final HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
1092             HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1093         resp2.setHeader("ETag", "\"etag\"");
1094         resp2.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
1095         resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
1096         resp2.setHeader("Cache-Control", "public, max-age=5");
1097 
1098         backendExpectsAnyRequestAndReturn(resp1);
1099         backendExpectsAnyRequestAndReturn(resp2);
1100 
1101         replayMocks();
1102         impl.execute(route, req1, context, null);
1103         final HttpResponse result = impl.execute(route, req2, context, null);
1104         verifyMocks();
1105 
1106         Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getStatusLine().getStatusCode());
1107     }
1108 
1109     @Test
1110     public void testReturns200ForIfModifiedSinceFailsIfRequestServedFromOrigin() throws Exception {
1111         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
1112         final Date now = new Date();
1113         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
1114 
1115         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
1116             "http://foo.example.com/"));
1117         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
1118             "http://foo.example.com/"));
1119 
1120         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1121             "OK");
1122         resp1.setEntity(HttpTestUtils.makeBody(128));
1123         resp1.setHeader("Content-Length", "128");
1124         resp1.setHeader("ETag", "\"etag\"");
1125         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
1126         resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
1127         resp1.setHeader("Cache-Control", "public, max-age=5");
1128 
1129         req2.addHeader("If-Modified-Since", DateUtils.formatDate(tenSecondsAgo));
1130         final HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1131             "OK");
1132         resp2.setEntity(HttpTestUtils.makeBody(128));
1133         resp2.setHeader("Content-Length", "128");
1134         resp2.setHeader("ETag", "\"newetag\"");
1135         resp2.setHeader("Date", DateUtils.formatDate(now));
1136         resp1.setHeader("Last-Modified", DateUtils.formatDate(now));
1137         resp2.setHeader("Cache-Control", "public, max-age=5");
1138 
1139         backendExpectsAnyRequestAndReturn(resp1);
1140         backendExpectsAnyRequestAndReturn(resp2);
1141 
1142         replayMocks();
1143         impl.execute(route, req1, context, null);
1144         final HttpResponse result = impl.execute(route, req2, context, null);
1145         verifyMocks();
1146 
1147         Assert.assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode());
1148     }
1149 
1150     @Test
1151     public void testVariantMissServerIfReturns304CacheReturns200() throws Exception {
1152         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
1153         final Date now = new Date();
1154 
1155         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
1156             "http://foo.example.com"));
1157         req1.addHeader("Accept-Encoding", "gzip");
1158 
1159         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1160             "OK");
1161         resp1.setEntity(HttpTestUtils.makeBody(128));
1162         resp1.setHeader("Content-Length", "128");
1163         resp1.setHeader("Etag", "\"gzip_etag\"");
1164         resp1.setHeader("Date", DateUtils.formatDate(now));
1165         resp1.setHeader("Vary", "Accept-Encoding");
1166         resp1.setHeader("Cache-Control", "public, max-age=3600");
1167 
1168         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
1169             "http://foo.example.com"));
1170         req2.addHeader("Accept-Encoding", "deflate");
1171 
1172         final HttpRequestWrapper req2Server = HttpRequestWrapper.wrap(new HttpGet(
1173             "http://foo.example.com"));
1174         req2Server.addHeader("Accept-Encoding", "deflate");
1175         req2Server.addHeader("If-None-Match", "\"gzip_etag\"");
1176 
1177         final HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1178             "OK");
1179         resp2.setEntity(HttpTestUtils.makeBody(128));
1180         resp2.setHeader("Content-Length", "128");
1181         resp2.setHeader("Etag", "\"deflate_etag\"");
1182         resp2.setHeader("Date", DateUtils.formatDate(now));
1183         resp2.setHeader("Vary", "Accept-Encoding");
1184         resp2.setHeader("Cache-Control", "public, max-age=3600");
1185 
1186         final HttpRequestWrapper req3 = HttpRequestWrapper.wrap(new HttpGet(
1187             "http://foo.example.com"));
1188         req3.addHeader("Accept-Encoding", "gzip,deflate");
1189 
1190         final HttpRequestWrapper req3Server = HttpRequestWrapper.wrap(new HttpGet(
1191             "http://foo.example.com"));
1192         req3Server.addHeader("Accept-Encoding", "gzip,deflate");
1193         req3Server.addHeader("If-None-Match", "\"gzip_etag\",\"deflate_etag\"");
1194 
1195         final HttpResponse resp3 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1196             "OK");
1197         resp3.setEntity(HttpTestUtils.makeBody(128));
1198         resp3.setHeader("Content-Length", "128");
1199         resp3.setHeader("Etag", "\"gzip_etag\"");
1200         resp3.setHeader("Date", DateUtils.formatDate(now));
1201         resp3.setHeader("Vary", "Accept-Encoding");
1202         resp3.setHeader("Cache-Control", "public, max-age=3600");
1203 
1204         backendExpectsAnyRequestAndReturn(resp1);
1205         backendExpectsAnyRequestAndReturn(resp2);
1206         backendExpectsAnyRequestAndReturn(resp3);
1207 
1208         replayMocks();
1209         final HttpResponse result1 = impl.execute(route, req1, context, null);
1210 
1211         final HttpResponse result2 = impl.execute(route, req2, context, null);
1212 
1213         final HttpResponse result3 = impl.execute(route, req3, context, null);
1214 
1215         verifyMocks();
1216         Assert.assertEquals(HttpStatus.SC_OK, result1.getStatusLine().getStatusCode());
1217         Assert.assertEquals(HttpStatus.SC_OK, result2.getStatusLine().getStatusCode());
1218         Assert.assertEquals(HttpStatus.SC_OK, result3.getStatusLine().getStatusCode());
1219     }
1220 
1221     @Test
1222     public void testVariantsMissServerReturns304CacheReturns304() throws Exception {
1223         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
1224         final Date now = new Date();
1225 
1226         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
1227             "http://foo.example.com"));
1228         req1.addHeader("Accept-Encoding", "gzip");
1229 
1230         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1231             "OK");
1232         resp1.setEntity(HttpTestUtils.makeBody(128));
1233         resp1.setHeader("Content-Length", "128");
1234         resp1.setHeader("Etag", "\"gzip_etag\"");
1235         resp1.setHeader("Date", DateUtils.formatDate(now));
1236         resp1.setHeader("Vary", "Accept-Encoding");
1237         resp1.setHeader("Cache-Control", "public, max-age=3600");
1238 
1239         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
1240             "http://foo.example.com"));
1241         req2.addHeader("Accept-Encoding", "deflate");
1242 
1243         final HttpRequestWrapper req2Server = HttpRequestWrapper.wrap(new HttpGet(
1244             "http://foo.example.com"));
1245         req2Server.addHeader("Accept-Encoding", "deflate");
1246         req2Server.addHeader("If-None-Match", "\"gzip_etag\"");
1247 
1248         final HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1249             "OK");
1250         resp2.setEntity(HttpTestUtils.makeBody(128));
1251         resp2.setHeader("Content-Length", "128");
1252         resp2.setHeader("Etag", "\"deflate_etag\"");
1253         resp2.setHeader("Date", DateUtils.formatDate(now));
1254         resp2.setHeader("Vary", "Accept-Encoding");
1255         resp2.setHeader("Cache-Control", "public, max-age=3600");
1256 
1257         final HttpRequestWrapper req4 = HttpRequestWrapper.wrap(new HttpGet(
1258             "http://foo.example.com"));
1259         req4.addHeader("Accept-Encoding", "gzip,identity");
1260         req4.addHeader("If-None-Match", "\"gzip_etag\"");
1261 
1262         final HttpRequestWrapper req4Server = HttpRequestWrapper.wrap(new HttpGet(
1263             "http://foo.example.com"));
1264         req4Server.addHeader("Accept-Encoding", "gzip,identity");
1265         req4Server.addHeader("If-None-Match", "\"gzip_etag\"");
1266 
1267         final HttpResponse resp4 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
1268             HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1269         resp4.setHeader("Etag", "\"gzip_etag\"");
1270         resp4.setHeader("Date", DateUtils.formatDate(now));
1271         resp4.setHeader("Vary", "Accept-Encoding");
1272         resp4.setHeader("Cache-Control", "public, max-age=3600");
1273 
1274         backendExpectsAnyRequestAndReturn(resp1);
1275         backendExpectsAnyRequestAndReturn(resp2);
1276         backendExpectsAnyRequestAndReturn(resp4);
1277 
1278         replayMocks();
1279         final HttpResponse result1 = impl.execute(route, req1, context, null);
1280 
1281         final HttpResponse result2 = impl.execute(route, req2, context, null);
1282 
1283         final HttpResponse result4 = impl.execute(route, req4, context, null);
1284         verifyMocks();
1285         Assert.assertEquals(HttpStatus.SC_OK, result1.getStatusLine().getStatusCode());
1286         Assert.assertEquals(HttpStatus.SC_OK, result2.getStatusLine().getStatusCode());
1287         Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, result4.getStatusLine().getStatusCode());
1288 
1289     }
1290 
1291     @Test
1292     public void testSocketTimeoutExceptionIsNotSilentlyCatched() throws Exception {
1293         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
1294         final Date now = new Date();
1295 
1296         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
1297             "http://foo.example.com"));
1298 
1299         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1300             "OK");
1301         resp1.setEntity(new InputStreamEntity(new InputStream() {
1302             private boolean closed = false;
1303 
1304             @Override
1305             public void close() throws IOException {
1306                 closed = true;
1307             }
1308 
1309             @Override
1310             public int read() throws IOException {
1311                 if (closed) {
1312                     throw new SocketException("Socket closed");
1313                 }
1314                 throw new SocketTimeoutException("Read timed out");
1315             }
1316         }, 128));
1317         resp1.setHeader("Date", DateUtils.formatDate(now));
1318 
1319         backendExpectsAnyRequestAndReturn(resp1);
1320 
1321         replayMocks();
1322         try {
1323             final HttpResponse result1 = impl.execute(route, req1, context, null);
1324             EntityUtils.toString(result1.getEntity());
1325             Assert.fail("We should have had a SocketTimeoutException");
1326         } catch (final SocketTimeoutException e) {
1327         }
1328         verifyMocks();
1329 
1330     }
1331 
1332     @Test
1333     public void testIsSharedCache() {
1334         Assert.assertTrue(config.isSharedCache());
1335     }
1336 
1337     @Test
1338     public void testTreatsCacheIOExceptionsAsCacheMiss() throws Exception {
1339 
1340         impl = createCachingExecChain(mockBackend, mockCache, CacheConfig.DEFAULT);
1341         final CloseableHttpResponse resp = Proxies.enhanceResponse(HttpTestUtils.make200Response());
1342 
1343         mockCache.flushInvalidatedCacheEntriesFor(host, request);
1344         expectLastCall().andThrow(new IOException()).anyTimes();
1345         mockCache.flushInvalidatedCacheEntriesFor(isA(HttpHost.class), isA(HttpRequest.class),
1346             isA(HttpResponse.class));
1347         expectLastCall().anyTimes();
1348         expect(mockCache.getCacheEntry(eq(host), isA(HttpRequest.class))).andThrow(
1349             new IOException()).anyTimes();
1350         expect(mockCache.getVariantCacheEntriesWithEtags(eq(host), isA(HttpRequest.class)))
1351             .andThrow(new IOException()).anyTimes();
1352         expect(
1353             mockCache.cacheAndReturnResponse(eq(host), isA(HttpRequest.class),
1354                 isA(CloseableHttpResponse.class), isA(Date.class), isA(Date.class)))
1355             .andReturn(resp).anyTimes();
1356         expect(
1357             mockBackend.execute(eq(route), isA(HttpRequestWrapper.class),
1358                 isA(HttpClientContext.class), (HttpExecutionAware) isNull())).andReturn(resp);
1359 
1360         replayMocks();
1361         final HttpResponse result = impl.execute(route, request, context, null);
1362         verifyMocks();
1363         Assert.assertSame(resp, result);
1364     }
1365 
1366     @Test
1367     public void testIfOnlyIfCachedAndNoCacheEntryBackendNotCalled() throws Exception {
1368         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
1369 
1370         request.addHeader("Cache-Control", "only-if-cached");
1371 
1372         final HttpResponse resp = impl.execute(route, request, context, null);
1373 
1374         Assert.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, resp.getStatusLine().getStatusCode());
1375     }
1376 
1377     @Test
1378     public void testIfOnlyIfCachedAndEntryNotSuitableBackendNotCalled() throws Exception {
1379 
1380         request.setHeader("Cache-Control", "only-if-cached");
1381 
1382         entry = HttpTestUtils.makeCacheEntry(new Header[] { new BasicHeader("Cache-Control",
1383             "must-revalidate") });
1384 
1385         requestIsFatallyNonCompliant(null);
1386         requestPolicyAllowsCaching(true);
1387         getCacheEntryReturns(entry);
1388         cacheEntrySuitable(false);
1389 
1390         replayMocks();
1391         final HttpResponse resp = impl.execute(route, request, context, null);
1392         verifyMocks();
1393 
1394         Assert.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, resp.getStatusLine().getStatusCode());
1395     }
1396 
1397     @Test
1398     public void testIfOnlyIfCachedAndEntryExistsAndIsSuitableReturnsEntry() throws Exception {
1399 
1400         request.setHeader("Cache-Control", "only-if-cached");
1401 
1402         requestIsFatallyNonCompliant(null);
1403         requestPolicyAllowsCaching(true);
1404         getCacheEntryReturns(entry);
1405         cacheEntrySuitable(true);
1406         responseIsGeneratedFromCache();
1407         entryHasStaleness(0);
1408 
1409         replayMocks();
1410         final HttpResponse resp = impl.execute(route, request, context, null);
1411         verifyMocks();
1412 
1413         Assert.assertSame(mockCachedResponse, resp);
1414     }
1415 
1416     @Test
1417     public void testDoesNotSetConnectionInContextOnCacheHit() throws Exception {
1418         final DummyBackend backend = new DummyBackend();
1419         final HttpResponse response = HttpTestUtils.make200Response();
1420         response.setHeader("Cache-Control", "max-age=3600");
1421         backend.setResponse(response);
1422         impl = createCachingExecChain(backend, new BasicHttpCache(), CacheConfig.DEFAULT);
1423         final HttpClientContext ctx = HttpClientContext.create();
1424         ctx.setTargetHost(host);
1425         impl.execute(route, request, context, null);
1426         impl.execute(route, request, ctx, null);
1427         assertNull(ctx.getConnection());
1428     }
1429 
1430     @Test
1431     public void testSetsTargetHostInContextOnCacheHit() throws Exception {
1432         final DummyBackend backend = new DummyBackend();
1433         final HttpResponse response = HttpTestUtils.make200Response();
1434         response.setHeader("Cache-Control", "max-age=3600");
1435         backend.setResponse(response);
1436         impl = createCachingExecChain(backend, new BasicHttpCache(), CacheConfig.DEFAULT);
1437         final HttpClientContext ctx = HttpClientContext.create();
1438         ctx.setTargetHost(host);
1439         impl.execute(route, request, context, null);
1440         impl.execute(route, request, ctx, null);
1441         assertSame(host, ctx.getTargetHost());
1442     }
1443 
1444     @Test
1445     public void testSetsRouteInContextOnCacheHit() throws Exception {
1446         final DummyBackend backend = new DummyBackend();
1447         final HttpResponse response = HttpTestUtils.make200Response();
1448         response.setHeader("Cache-Control", "max-age=3600");
1449         backend.setResponse(response);
1450         impl = createCachingExecChain(backend, new BasicHttpCache(), CacheConfig.DEFAULT);
1451         final HttpClientContext ctx = HttpClientContext.create();
1452         ctx.setTargetHost(host);
1453         impl.execute(route, request, context, null);
1454         impl.execute(route, request, ctx, null);
1455         assertEquals(route, ctx.getHttpRoute());
1456     }
1457 
1458     @Test
1459     public void testSetsRequestInContextOnCacheHit() throws Exception {
1460         final DummyBackend backend = new DummyBackend();
1461         final HttpResponse response = HttpTestUtils.make200Response();
1462         response.setHeader("Cache-Control", "max-age=3600");
1463         backend.setResponse(response);
1464         impl = createCachingExecChain(backend, new BasicHttpCache(), CacheConfig.DEFAULT);
1465         final HttpClientContext ctx = HttpClientContext.create();
1466         ctx.setTargetHost(host);
1467         impl.execute(route, request, context, null);
1468         impl.execute(route, request, ctx, null);
1469         if (!HttpTestUtils.equivalent(request, ctx.getRequest())) {
1470             assertSame(request, ctx.getRequest());
1471         }
1472     }
1473 
1474     @Test
1475     public void testSetsResponseInContextOnCacheHit() throws Exception {
1476         final DummyBackend backend = new DummyBackend();
1477         final HttpResponse response = HttpTestUtils.make200Response();
1478         response.setHeader("Cache-Control", "max-age=3600");
1479         backend.setResponse(response);
1480         impl = createCachingExecChain(backend, new BasicHttpCache(), CacheConfig.DEFAULT);
1481         final HttpClientContext ctx = HttpClientContext.create();
1482         ctx.setTargetHost(host);
1483         impl.execute(route, request, context, null);
1484         final HttpResponse result = impl.execute(route, request, ctx, null);
1485         if (!HttpTestUtils.equivalent(result, ctx.getResponse())) {
1486             assertSame(result, ctx.getResponse());
1487         }
1488     }
1489 
1490     @Test
1491     public void testSetsRequestSentInContextOnCacheHit() throws Exception {
1492         final DummyBackend backend = new DummyBackend();
1493         final HttpResponse response = HttpTestUtils.make200Response();
1494         response.setHeader("Cache-Control", "max-age=3600");
1495         backend.setResponse(response);
1496         impl = createCachingExecChain(backend, new BasicHttpCache(), CacheConfig.DEFAULT);
1497         final HttpClientContext ctx = HttpClientContext.create();
1498         ctx.setTargetHost(host);
1499         impl.execute(route, request, context, null);
1500         impl.execute(route, request, ctx, null);
1501         assertTrue(ctx.isRequestSent());
1502     }
1503 
1504     @Test
1505     public void testCanCacheAResponseWithoutABody() throws Exception {
1506         final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
1507             HttpStatus.SC_NO_CONTENT, "No Content");
1508         response.setHeader("Date", DateUtils.formatDate(new Date()));
1509         response.setHeader("Cache-Control", "max-age=300");
1510         final DummyBackend backend = new DummyBackend();
1511         backend.setResponse(response);
1512         impl = createCachingExecChain(backend, new BasicHttpCache(), CacheConfig.DEFAULT);
1513         impl.execute(route, request, context, null);
1514         impl.execute(route, request, context, null);
1515         assertEquals(1, backend.getExecutions());
1516     }
1517 
1518     @Test
1519     public void testNoEntityForIfNoneMatchRequestNotYetInCache() throws Exception {
1520 
1521         final Date now = new Date();
1522         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
1523 
1524         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
1525         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
1526             "http://foo.example.com/"));
1527         req1.addHeader("If-None-Match", "\"etag\"");
1528 
1529         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
1530             HttpStatus.SC_NOT_MODIFIED, "Not modified");
1531         resp1.setHeader("Content-Length", "128");
1532         resp1.setHeader("ETag", "\"etag\"");
1533         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
1534         resp1.setHeader("Cache-Control", "public, max-age=5");
1535 
1536         backendExpectsAnyRequestAndReturn(resp1);
1537         replayMocks();
1538         final HttpResponse result = impl.execute(route, req1, context, null);
1539         verifyMocks();
1540 
1541         assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getStatusLine().getStatusCode());
1542         assertNull("The 304 response messages MUST NOT contain a message-body", result.getEntity());
1543     }
1544 
1545     @Test
1546     public void testNotModifiedResponseUpdatesCacheEntryWhenNoEntity() throws Exception {
1547 
1548         final Date now = new Date();
1549 
1550         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
1551 
1552         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
1553             "http://foo.example.com/"));
1554         req1.addHeader("If-None-Match", "etag");
1555 
1556         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
1557             "http://foo.example.com/"));
1558         req2.addHeader("If-None-Match", "etag");
1559 
1560         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
1561             HttpStatus.SC_NOT_MODIFIED, "Not modified");
1562         resp1.setHeader("Date", DateUtils.formatDate(now));
1563         resp1.setHeader("Cache-Control", "max-age=0");
1564         resp1.setHeader("Etag", "etag");
1565 
1566         final HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
1567             HttpStatus.SC_NOT_MODIFIED, "Not modified");
1568         resp2.setHeader("Date", DateUtils.formatDate(now));
1569         resp2.setHeader("Cache-Control", "max-age=0");
1570         resp1.setHeader("Etag", "etag");
1571 
1572         backendExpectsAnyRequestAndReturn(resp1);
1573         backendExpectsAnyRequestAndReturn(resp2);
1574         replayMocks();
1575         final HttpResponse result1 = impl.execute(route, req1, context, null);
1576         final HttpResponse result2 = impl.execute(route, req2, context, null);
1577         verifyMocks();
1578 
1579         assertEquals(HttpStatus.SC_NOT_MODIFIED, result1.getStatusLine().getStatusCode());
1580         assertEquals("etag", result1.getFirstHeader("Etag").getValue());
1581         assertEquals(HttpStatus.SC_NOT_MODIFIED, result2.getStatusLine().getStatusCode());
1582         assertEquals("etag", result2.getFirstHeader("Etag").getValue());
1583     }
1584 
1585     @Test
1586     public void testNotModifiedResponseWithVaryUpdatesCacheEntryWhenNoEntity() throws Exception {
1587 
1588         final Date now = new Date();
1589 
1590         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
1591 
1592         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
1593             "http://foo.example.com/"));
1594         req1.addHeader("If-None-Match", "etag");
1595 
1596         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
1597             "http://foo.example.com/"));
1598         req2.addHeader("If-None-Match", "etag");
1599 
1600         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
1601             HttpStatus.SC_NOT_MODIFIED, "Not modified");
1602         resp1.setHeader("Date", DateUtils.formatDate(now));
1603         resp1.setHeader("Cache-Control", "max-age=0");
1604         resp1.setHeader("Etag", "etag");
1605         resp1.setHeader("Vary", "Accept-Encoding");
1606 
1607         final HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
1608             HttpStatus.SC_NOT_MODIFIED, "Not modified");
1609         resp2.setHeader("Date", DateUtils.formatDate(now));
1610         resp2.setHeader("Cache-Control", "max-age=0");
1611         resp1.setHeader("Etag", "etag");
1612         resp1.setHeader("Vary", "Accept-Encoding");
1613 
1614         backendExpectsAnyRequestAndReturn(resp1);
1615         backendExpectsAnyRequestAndReturn(resp2);
1616         replayMocks();
1617         final HttpResponse result1 = impl.execute(route, req1, context, null);
1618         final HttpResponse result2 = impl.execute(route, req2, context, null);
1619         verifyMocks();
1620 
1621         assertEquals(HttpStatus.SC_NOT_MODIFIED, result1.getStatusLine().getStatusCode());
1622         assertEquals("etag", result1.getFirstHeader("Etag").getValue());
1623         assertEquals(HttpStatus.SC_NOT_MODIFIED, result2.getStatusLine().getStatusCode());
1624         assertEquals("etag", result2.getFirstHeader("Etag").getValue());
1625     }
1626 
1627     @Test
1628     public void testDoesNotSend304ForNonConditionalRequest() throws Exception {
1629 
1630         final Date now = new Date();
1631         final Date inOneMinute = new Date(System.currentTimeMillis() + 60000);
1632 
1633         impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
1634 
1635         final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new HttpGet(
1636             "http://foo.example.com/"));
1637         req1.addHeader("If-None-Match", "etag");
1638 
1639         final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new HttpGet(
1640             "http://foo.example.com/"));
1641 
1642         final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
1643             HttpStatus.SC_NOT_MODIFIED, "Not modified");
1644         resp1.setHeader("Date", DateUtils.formatDate(now));
1645         resp1.setHeader("Cache-Control", "public, max-age=60");
1646         resp1.setHeader("Expires", DateUtils.formatDate(inOneMinute));
1647         resp1.setHeader("Etag", "etag");
1648         resp1.setHeader("Vary", "Accept-Encoding");
1649 
1650         final HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
1651             "Ok");
1652         resp2.setHeader("Date", DateUtils.formatDate(now));
1653         resp2.setHeader("Cache-Control", "public, max-age=60");
1654         resp2.setHeader("Expires", DateUtils.formatDate(inOneMinute));
1655         resp2.setHeader("Etag", "etag");
1656         resp2.setHeader("Vary", "Accept-Encoding");
1657         resp2.setEntity(HttpTestUtils.makeBody(128));
1658 
1659         backendExpectsAnyRequestAndReturn(resp1);
1660         backendExpectsAnyRequestAndReturn(resp2).anyTimes();
1661         replayMocks();
1662         final HttpResponse result1 = impl.execute(route, req1, context, null);
1663         final HttpResponse result2 = impl.execute(route, req2, context, null);
1664         verifyMocks();
1665 
1666         assertEquals(HttpStatus.SC_NOT_MODIFIED, result1.getStatusLine().getStatusCode());
1667         assertNull(result1.getEntity());
1668         assertEquals(HttpStatus.SC_OK, result2.getStatusLine().getStatusCode());
1669         Assert.assertNotNull(result2.getEntity());
1670     }
1671 
1672     @Test
1673     public void testUsesVirtualHostForCacheKey() throws Exception {
1674         final DummyBackend backend = new DummyBackend();
1675         final HttpResponse response = HttpTestUtils.make200Response();
1676         response.setHeader("Cache-Control", "max-age=3600");
1677         backend.setResponse(response);
1678         impl = createCachingExecChain(backend, new BasicHttpCache(), CacheConfig.DEFAULT);
1679         impl.execute(route, request, context, null);
1680         assertEquals(1, backend.getExecutions());
1681         context.setTargetHost(new HttpHost("bar.example.com"));
1682         impl.execute(route, request, context, null);
1683         assertEquals(2, backend.getExecutions());
1684         impl.execute(route, request, context, null);
1685         assertEquals(2, backend.getExecutions());
1686     }
1687 
1688     private IExpectationSetters<CloseableHttpResponse> backendExpectsAnyRequestAndReturn(
1689         final HttpResponse response) throws Exception {
1690         final CloseableHttpResponse resp = mockBackend.execute(EasyMock.isA(HttpRoute.class),
1691             EasyMock.isA(HttpRequestWrapper.class), EasyMock.isA(HttpClientContext.class),
1692             EasyMock.<HttpExecutionAware> isNull());
1693         return EasyMock.expect(resp).andReturn(Proxies.enhanceResponse(response));
1694     }
1695 
1696     protected IExpectationSetters<CloseableHttpResponse> backendExpectsRequestAndReturn(
1697         final HttpRequestWrapper request, final HttpResponse response) throws Exception {
1698         final CloseableHttpResponse resp = mockBackend.execute(EasyMock.isA(HttpRoute.class),
1699             EasyMock.eq(request), EasyMock.isA(HttpClientContext.class),
1700             EasyMock.<HttpExecutionAware> isNull());
1701         return EasyMock.expect(resp).andReturn(Proxies.enhanceResponse(response));
1702     }
1703 
1704     protected IExpectationSetters<CloseableHttpResponse> backendExpectsRequestAndReturn(
1705         final HttpRequestWrapper request, final CloseableHttpResponse response) throws Exception {
1706         final CloseableHttpResponse resp = mockBackend.execute(EasyMock.isA(HttpRoute.class),
1707             EasyMock.eq(request), EasyMock.isA(HttpClientContext.class),
1708             EasyMock.<HttpExecutionAware> isNull());
1709         return EasyMock.expect(resp).andReturn(response);
1710     }
1711 
1712     protected IExpectationSetters<CloseableHttpResponse> backendExpectsAnyRequestAndThrows(
1713         final Throwable throwable) throws Exception {
1714         final CloseableHttpResponse resp = mockBackend.execute(EasyMock.isA(HttpRoute.class),
1715             EasyMock.isA(HttpRequestWrapper.class), EasyMock.isA(HttpClientContext.class),
1716             EasyMock.<HttpExecutionAware> isNull());
1717         return EasyMock.expect(resp).andThrow(throwable);
1718     }
1719 
1720     protected IExpectationSetters<CloseableHttpResponse> backendCaptureRequestAndReturn(
1721         final Capture<HttpRequestWrapper> cap, final HttpResponse response) throws Exception {
1722         final CloseableHttpResponse resp = mockBackend.execute(EasyMock.isA(HttpRoute.class),
1723             EasyMock.capture(cap), EasyMock.isA(HttpClientContext.class),
1724             EasyMock.<HttpExecutionAware> isNull());
1725         return EasyMock.expect(resp).andReturn(Proxies.enhanceResponse(response));
1726     }
1727 
1728     protected void getCacheEntryReturns(final HttpCacheEntry result) throws IOException {
1729         expect(mockCache.getCacheEntry(eq(host), eqRequest(request))).andReturn(result);
1730     }
1731 
1732     protected void cacheEntryValidatable(final boolean b) {
1733         expect(mockValidityPolicy.isRevalidatable((HttpCacheEntry) anyObject())).andReturn(b)
1734             .anyTimes();
1735     }
1736 
1737     protected void cacheEntryMustRevalidate(final boolean b) {
1738         expect(mockValidityPolicy.mustRevalidate(mockCacheEntry)).andReturn(b);
1739     }
1740 
1741     protected void cacheEntryProxyRevalidate(final boolean b) {
1742         expect(mockValidityPolicy.proxyRevalidate(mockCacheEntry)).andReturn(b);
1743     }
1744 
1745     protected void mayReturnStaleWhileRevalidating(final boolean b) {
1746         expect(
1747             mockValidityPolicy.mayReturnStaleWhileRevalidating((HttpCacheEntry) anyObject(),
1748                 (Date) anyObject())).andReturn(b);
1749     }
1750 
1751     protected void conditionalRequestBuilderReturns(final HttpRequestWrapper validate)
1752         throws Exception {
1753         expect(mockConditionalRequestBuilder.buildConditionalRequest(request, entry)).andReturn(
1754             validate);
1755     }
1756 
1757     protected void requestPolicyAllowsCaching(final boolean allow) {
1758         expect(mockRequestPolicy.isServableFromCache((HttpRequest) anyObject())).andReturn(allow);
1759     }
1760 
1761     protected void cacheEntrySuitable(final boolean suitable) {
1762         expect(
1763             mockSuitabilityChecker.canCachedResponseBeUsed((HttpHost) anyObject(),
1764                 (HttpRequest) anyObject(), (HttpCacheEntry) anyObject(), (Date) anyObject()))
1765             .andReturn(suitable);
1766     }
1767 
1768     private void entryHasStaleness(final long staleness) {
1769         expect(
1770             mockValidityPolicy.getStalenessSecs((HttpCacheEntry) anyObject(), (Date) anyObject()))
1771             .andReturn(staleness);
1772     }
1773 
1774     protected void responseIsGeneratedFromCache() {
1775         expect(
1776             mockResponseGenerator.generateResponse((HttpRequestWrapper) anyObject(), (HttpCacheEntry) anyObject()))
1777             .andReturn(mockCachedResponse);
1778     }
1779 
1780     protected void doesNotFlushCache() throws IOException {
1781         mockCache.flushInvalidatedCacheEntriesFor(isA(HttpHost.class), isA(HttpRequest.class));
1782         EasyMock.expectLastCall().andThrow(new AssertionError("flushInvalidatedCacheEntriesFor should not have been called")).anyTimes();
1783     }
1784 
1785 }