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