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