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.isA;
33 import static org.easymock.EasyMock.isNull;
34 import static org.easymock.EasyMock.same;
35 import static org.easymock.classextension.EasyMock.createMockBuilder;
36 import static org.easymock.classextension.EasyMock.createNiceMock;
37 import static org.easymock.classextension.EasyMock.replay;
38 import static org.easymock.classextension.EasyMock.verify;
39 import static org.junit.Assert.assertTrue;
40
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.util.Date;
44 import java.util.HashMap;
45 import java.util.Map;
46 import java.util.concurrent.atomic.AtomicInteger;
47
48 import org.apache.http.HttpHost;
49 import org.apache.http.HttpRequest;
50 import org.apache.http.HttpResponse;
51 import org.apache.http.HttpStatus;
52 import org.apache.http.HttpVersion;
53 import org.apache.http.client.cache.HttpCacheEntry;
54 import org.apache.http.client.methods.CloseableHttpResponse;
55 import org.apache.http.client.methods.HttpExecutionAware;
56 import org.apache.http.client.methods.HttpGet;
57 import org.apache.http.client.methods.HttpRequestWrapper;
58 import org.apache.http.client.protocol.HttpClientContext;
59 import org.apache.http.client.utils.DateUtils;
60 import org.apache.http.conn.routing.HttpRoute;
61 import org.apache.http.entity.InputStreamEntity;
62 import org.apache.http.impl.execchain.ClientExecChain;
63 import org.apache.http.message.BasicHttpRequest;
64 import org.apache.http.message.BasicHttpResponse;
65 import org.apache.http.message.BasicStatusLine;
66 import org.apache.http.protocol.HTTP;
67 import org.easymock.IExpectationSetters;
68 import org.easymock.EasyMock;
69 import org.junit.Assert;
70 import org.junit.Before;
71 import org.junit.Test;
72
73 @SuppressWarnings("boxing")
74 public class TestCachingExec extends TestCachingExecChain {
75
76 private static final String GET_CURRENT_DATE = "getCurrentDate";
77
78 private static final String HANDLE_BACKEND_RESPONSE = "handleBackendResponse";
79
80 private static final String CALL_BACKEND = "callBackend";
81
82 private static final String REVALIDATE_CACHE_ENTRY = "revalidateCacheEntry";
83
84 private CachingExec impl;
85 private boolean mockedImpl;
86
87 private CloseableHttpResponse mockBackendResponse;
88
89 private Date requestDate;
90 private Date responseDate;
91
92 @Override
93 @Before
94 public void setUp() {
95 super.setUp();
96
97 mockBackendResponse = createNiceMock(CloseableHttpResponse.class);
98
99 requestDate = new Date(System.currentTimeMillis() - 1000);
100 responseDate = new Date();
101 }
102
103 @Override
104 public CachingExec createCachingExecChain(final ClientExecChain mockBackend,
105 final HttpCache mockCache, final CacheValidityPolicy mockValidityPolicy,
106 final ResponseCachingPolicy mockResponsePolicy,
107 final CachedHttpResponseGenerator mockResponseGenerator,
108 final CacheableRequestPolicy mockRequestPolicy,
109 final CachedResponseSuitabilityChecker mockSuitabilityChecker,
110 final ConditionalRequestBuilder mockConditionalRequestBuilder,
111 final ResponseProtocolCompliance mockResponseProtocolCompliance,
112 final RequestProtocolCompliance mockRequestProtocolCompliance,
113 final CacheConfig config, final AsynchronousValidator asyncValidator) {
114 return impl = new CachingExec(
115 mockBackend,
116 mockCache,
117 mockValidityPolicy,
118 mockResponsePolicy,
119 mockResponseGenerator,
120 mockRequestPolicy,
121 mockSuitabilityChecker,
122 mockConditionalRequestBuilder,
123 mockResponseProtocolCompliance,
124 mockRequestProtocolCompliance,
125 config,
126 asyncValidator);
127 }
128
129 @Override
130 public CachingExec createCachingExecChain(final ClientExecChain backend,
131 final HttpCache cache, final CacheConfig config) {
132 return impl = new CachingExec(backend, cache, config);
133 }
134
135 @Override
136 protected void replayMocks() {
137 super.replayMocks();
138 replay(mockBackendResponse);
139 if (mockedImpl) {
140 replay(impl);
141 }
142 }
143
144 @Override
145 protected void verifyMocks() {
146 super.verifyMocks();
147 verify(mockBackendResponse);
148 if (mockedImpl) {
149 verify(impl);
150 }
151 }
152
153
154 @Test
155 public void testRequestThatCannotBeServedFromCacheCausesBackendRequest() throws Exception {
156 cacheInvalidatorWasCalled();
157 requestPolicyAllowsCaching(false);
158 mockImplMethods(CALL_BACKEND);
159
160 implExpectsAnyRequestAndReturn(mockBackendResponse);
161 requestIsFatallyNonCompliant(null);
162
163 replayMocks();
164 final HttpResponse result = impl.execute(route, request, context);
165 verifyMocks();
166
167 Assert.assertSame(mockBackendResponse, result);
168 }
169
170 @Test
171 public void testCacheMissCausesBackendRequest() throws Exception {
172 mockImplMethods(CALL_BACKEND);
173 requestPolicyAllowsCaching(true);
174 getCacheEntryReturns(null);
175 getVariantCacheEntriesReturns(new HashMap<String,Variant>());
176
177 requestIsFatallyNonCompliant(null);
178
179 implExpectsAnyRequestAndReturn(mockBackendResponse);
180
181 replayMocks();
182 final HttpResponse result = impl.execute(route, request, context);
183 verifyMocks();
184
185 Assert.assertSame(mockBackendResponse, result);
186 Assert.assertEquals(1, impl.getCacheMisses());
187 Assert.assertEquals(0, impl.getCacheHits());
188 Assert.assertEquals(0, impl.getCacheUpdates());
189 }
190
191 @Test
192 public void testUnsuitableUnvalidatableCacheEntryCausesBackendRequest() throws Exception {
193 mockImplMethods(CALL_BACKEND);
194 requestPolicyAllowsCaching(true);
195 requestIsFatallyNonCompliant(null);
196
197 getCacheEntryReturns(mockCacheEntry);
198 cacheEntrySuitable(false);
199 cacheEntryValidatable(false);
200 expect(mockConditionalRequestBuilder.buildConditionalRequest(request, mockCacheEntry))
201 .andReturn(request);
202 backendExpectsRequestAndReturn(request, mockBackendResponse);
203 expect(mockBackendResponse.getProtocolVersion()).andReturn(HttpVersion.HTTP_1_1);
204 expect(mockBackendResponse.getStatusLine()).andReturn(
205 new BasicStatusLine(HttpVersion.HTTP_1_1, 200, "Ok"));
206
207 replayMocks();
208 final HttpResponse result = impl.execute(route, request, context);
209 verifyMocks();
210
211 Assert.assertSame(mockBackendResponse, result);
212 Assert.assertEquals(0, impl.getCacheMisses());
213 Assert.assertEquals(1, impl.getCacheHits());
214 Assert.assertEquals(1, impl.getCacheUpdates());
215 }
216
217 @Test
218 public void testUnsuitableValidatableCacheEntryCausesRevalidation() throws Exception {
219 mockImplMethods(REVALIDATE_CACHE_ENTRY);
220 requestPolicyAllowsCaching(true);
221 requestIsFatallyNonCompliant(null);
222
223 getCacheEntryReturns(mockCacheEntry);
224 cacheEntrySuitable(false);
225 cacheEntryValidatable(true);
226 cacheEntryMustRevalidate(false);
227 cacheEntryProxyRevalidate(false);
228 mayReturnStaleWhileRevalidating(false);
229
230 expect(impl.revalidateCacheEntry(
231 isA(HttpRoute.class),
232 isA(HttpRequestWrapper.class),
233 isA(HttpClientContext.class),
234 (HttpExecutionAware) isNull(),
235 isA(HttpCacheEntry.class))).andReturn(mockBackendResponse);
236
237 replayMocks();
238 final HttpResponse result = impl.execute(route, request, context);
239 verifyMocks();
240
241 Assert.assertSame(mockBackendResponse, result);
242 Assert.assertEquals(0, impl.getCacheMisses());
243 Assert.assertEquals(1, impl.getCacheHits());
244 Assert.assertEquals(0, impl.getCacheUpdates());
245 }
246
247 @Test
248 public void testRevalidationCallsHandleBackEndResponseWhenNot200Or304() throws Exception {
249 mockImplMethods(GET_CURRENT_DATE, HANDLE_BACKEND_RESPONSE);
250
251 final HttpRequestWrapper validate = HttpRequestWrapper.wrap(
252 new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1));
253 final CloseableHttpResponse originResponse = Proxies.enhanceResponse(
254 new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_NOT_FOUND, "Not Found"));
255 final CloseableHttpResponse finalResponse = Proxies.enhanceResponse(
256 HttpTestUtils.make200Response());
257
258 conditionalRequestBuilderReturns(validate);
259 getCurrentDateReturns(requestDate);
260 backendExpectsRequestAndReturn(validate, originResponse);
261 getCurrentDateReturns(responseDate);
262 expect(impl.handleBackendResponse(
263 same(validate),
264 same(context),
265 eq(requestDate),
266 eq(responseDate),
267 same(originResponse))).andReturn(finalResponse);
268
269 replayMocks();
270 final HttpResponse result =
271 impl.revalidateCacheEntry(route, request, context, null, entry);
272 verifyMocks();
273
274 Assert.assertSame(finalResponse, result);
275 }
276
277 @Test
278 public void testRevalidationUpdatesCacheEntryAndPutsItToCacheWhen304ReturningCachedResponse()
279 throws Exception {
280
281 mockImplMethods(GET_CURRENT_DATE);
282
283 final HttpRequestWrapper validate = HttpRequestWrapper.wrap(
284 new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1));
285 final HttpResponse originResponse = Proxies.enhanceResponse(
286 new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_NOT_MODIFIED, "Not Modified"));
287 final HttpCacheEntry updatedEntry = HttpTestUtils.makeCacheEntry();
288
289 conditionalRequestBuilderReturns(validate);
290 getCurrentDateReturns(requestDate);
291 backendExpectsRequestAndReturn(validate, originResponse);
292 getCurrentDateReturns(responseDate);
293 expect(mockCache.updateCacheEntry(
294 eq(host),
295 same(request),
296 same(entry),
297 same(originResponse),
298 eq(requestDate),
299 eq(responseDate)))
300 .andReturn(updatedEntry);
301 expect(mockSuitabilityChecker.isConditional(request)).andReturn(false);
302 responseIsGeneratedFromCache();
303
304 replayMocks();
305 impl.revalidateCacheEntry(route, request, context, null, entry);
306 verifyMocks();
307 }
308
309 @Test
310 public void testRevalidationRewritesAbsoluteUri() throws Exception {
311
312 mockImplMethods(GET_CURRENT_DATE);
313
314
315 EasyMock.resetToStrict(mockBackend);
316
317 final HttpRequestWrapper validate = HttpRequestWrapper.wrap(
318 new HttpGet("http://foo.example.com/resource"));
319 final HttpRequestWrapper relativeValidate = HttpRequestWrapper.wrap(
320 new BasicHttpRequest("GET", "/resource", HttpVersion.HTTP_1_1));
321 final CloseableHttpResponse originResponse = Proxies.enhanceResponse(
322 new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "Okay"));
323
324 conditionalRequestBuilderReturns(validate);
325 getCurrentDateReturns(requestDate);
326
327 final CloseableHttpResponse resp = mockBackend.execute(EasyMock.isA(HttpRoute.class),
328 eqRequest(relativeValidate), EasyMock.isA(HttpClientContext.class),
329 EasyMock.<HttpExecutionAware> isNull());
330 expect(resp).andReturn(originResponse);
331
332 getCurrentDateReturns(responseDate);
333
334 replayMocks();
335 impl.revalidateCacheEntry(route, request, context, null, entry);
336 verifyMocks();
337 }
338
339 @Test
340 public void testEndlessResponsesArePassedThrough() throws Exception {
341 impl = createCachingExecChain(mockBackend, new BasicHttpCache(), CacheConfig.DEFAULT);
342
343 final HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
344 resp1.setHeader("Date", DateUtils.formatDate(new Date()));
345 resp1.setHeader("Server", "MockOrigin/1.0");
346 resp1.setHeader(HTTP.TRANSFER_ENCODING, HTTP.CHUNK_CODING);
347
348 final AtomicInteger size = new AtomicInteger();
349 final AtomicInteger maxlength = new AtomicInteger(Integer.MAX_VALUE);
350 resp1.setEntity(new InputStreamEntity(new InputStream() {
351 private Throwable closed;
352
353 @Override
354 public void close() throws IOException {
355 closed = new Throwable();
356 super.close();
357 }
358
359 @Override
360 public int read() throws IOException {
361 Thread.yield();
362 if (closed != null) {
363 throw new IOException("Response has been closed");
364
365 }
366 if (size.incrementAndGet() > maxlength.get()) {
367 return -1;
368 }
369 return 'y';
370 }
371 }, -1));
372
373 final CloseableHttpResponse resp = mockBackend.execute(
374 EasyMock.isA(HttpRoute.class),
375 EasyMock.isA(HttpRequestWrapper.class),
376 EasyMock.isA(HttpClientContext.class),
377 EasyMock.<HttpExecutionAware>isNull());
378 EasyMock.expect(resp).andReturn(Proxies.enhanceResponse(resp1));
379
380 final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
381
382 replayMocks();
383 final CloseableHttpResponse resp2 = impl.execute(route, req1, context, null);
384 maxlength.set(size.get() * 2);
385 verifyMocks();
386 assertTrue(HttpTestUtils.semanticallyTransparent(resp1, resp2));
387 }
388
389 @Test
390 public void testCallBackendMakesBackEndRequestAndHandlesResponse() throws Exception {
391 mockImplMethods(GET_CURRENT_DATE, HANDLE_BACKEND_RESPONSE);
392 final HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
393 getCurrentDateReturns(requestDate);
394 backendExpectsRequestAndReturn(request, resp);
395 getCurrentDateReturns(responseDate);
396 handleBackendResponseReturnsResponse(request, resp);
397
398 replayMocks();
399
400 impl.callBackend(route, request, context, null);
401
402 verifyMocks();
403 }
404
405 @Test
406 public void testDoesNotFlushCachesOnCacheHit() throws Exception {
407 requestPolicyAllowsCaching(true);
408 requestIsFatallyNonCompliant(null);
409
410 getCacheEntryReturns(mockCacheEntry);
411 doesNotFlushCache();
412 cacheEntrySuitable(true);
413 cacheEntryValidatable(true);
414
415 expect(mockResponseGenerator.generateResponse(isA(HttpRequestWrapper.class), isA(HttpCacheEntry.class)))
416 .andReturn(mockBackendResponse);
417
418 replayMocks();
419 final HttpResponse result = impl.execute(route, request, context);
420 verifyMocks();
421
422 Assert.assertSame(mockBackendResponse, result);
423 }
424
425 private IExpectationSetters<CloseableHttpResponse> implExpectsAnyRequestAndReturn(
426 final CloseableHttpResponse response) throws Exception {
427 final CloseableHttpResponse resp = impl.callBackend(
428 EasyMock.isA(HttpRoute.class),
429 EasyMock.isA(HttpRequestWrapper.class),
430 EasyMock.isA(HttpClientContext.class),
431 EasyMock.<HttpExecutionAware>isNull());
432 return EasyMock.expect(resp).andReturn(response);
433 }
434
435 private void getVariantCacheEntriesReturns(final Map<String,Variant> result) throws IOException {
436 expect(mockCache.getVariantCacheEntriesWithEtags(host, request)).andReturn(result);
437 }
438
439 private void cacheInvalidatorWasCalled() throws IOException {
440 mockCache.flushInvalidatedCacheEntriesFor(
441 (HttpHost)anyObject(),
442 (HttpRequest)anyObject());
443 }
444
445 private void getCurrentDateReturns(final Date date) {
446 expect(impl.getCurrentDate()).andReturn(date);
447 }
448
449 private void handleBackendResponseReturnsResponse(final HttpRequestWrapper request, final HttpResponse response)
450 throws IOException {
451 expect(
452 impl.handleBackendResponse(
453 same(request),
454 isA(HttpClientContext.class),
455 isA(Date.class),
456 isA(Date.class),
457 isA(CloseableHttpResponse.class))).andReturn(
458 Proxies.enhanceResponse(response));
459 }
460
461 private void mockImplMethods(final String... methods) {
462 mockedImpl = true;
463 impl = createMockBuilder(CachingExec.class).withConstructor(
464 mockBackend,
465 mockCache,
466 mockValidityPolicy,
467 mockResponsePolicy,
468 mockResponseGenerator,
469 mockRequestPolicy,
470 mockSuitabilityChecker,
471 mockConditionalRequestBuilder,
472 mockResponseProtocolCompliance,
473 mockRequestProtocolCompliance,
474 config,
475 asyncValidator).addMockedMethods(methods).createNiceMock();
476 }
477
478 }