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