1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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")
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
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
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 }