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