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.junit.jupiter.api.Assertions.assertEquals;
30 import static org.junit.jupiter.api.Assertions.assertFalse;
31 import static org.junit.jupiter.api.Assertions.assertNotNull;
32 import static org.junit.jupiter.api.Assertions.assertNull;
33 import static org.junit.jupiter.api.Assertions.assertSame;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.verifyNoMoreInteractions;
36
37 import java.net.URI;
38 import java.time.Instant;
39 import java.util.Collection;
40 import java.util.HashSet;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.concurrent.CountDownLatch;
44 import java.util.concurrent.atomic.AtomicReference;
45 import java.util.stream.Collectors;
46
47 import org.apache.hc.client5.http.HeadersMatcher;
48 import org.apache.hc.client5.http.cache.HttpCacheEntry;
49 import org.apache.hc.client5.http.classic.methods.HttpGet;
50 import org.apache.hc.client5.http.utils.DateUtils;
51 import org.apache.hc.core5.http.HttpHeaders;
52 import org.apache.hc.core5.http.HttpHost;
53 import org.apache.hc.core5.http.HttpRequest;
54 import org.apache.hc.core5.http.HttpResponse;
55 import org.apache.hc.core5.http.HttpStatus;
56 import org.apache.hc.core5.http.message.BasicHeader;
57 import org.apache.hc.core5.http.message.BasicHttpRequest;
58 import org.apache.hc.core5.http.message.BasicHttpResponse;
59 import org.apache.hc.core5.net.URIBuilder;
60 import org.apache.hc.core5.util.ByteArrayBuffer;
61 import org.hamcrest.MatcherAssert;
62 import org.junit.jupiter.api.Assertions;
63 import org.junit.jupiter.api.BeforeEach;
64 import org.junit.jupiter.api.Test;
65 import org.mockito.Mockito;
66
67 class TestBasicHttpAsyncCache {
68
69 private HttpHost host;
70 private Instant now;
71 private Instant tenSecondsAgo;
72 private SimpleHttpAsyncCacheStorage backing;
73 private BasicHttpAsyncCache impl;
74
75 @BeforeEach
76 void setUp() {
77 host = new HttpHost("foo.example.com");
78 now = Instant.now();
79 tenSecondsAgo = now.minusSeconds(10);
80 backing = Mockito.spy(new SimpleHttpAsyncCacheStorage());
81 impl = new BasicHttpAsyncCache(HeapResourceFactory.INSTANCE, backing);
82 }
83
84 @Test
85 void testGetCacheEntryReturnsNullOnCacheMiss() throws Exception {
86 final HttpHost host = new HttpHost("foo.example.com");
87 final HttpRequest request = new HttpGet("http://foo.example.com/bar");
88
89 final CountDownLatch latch1 = new CountDownLatch(1);
90 final AtomicReference<CacheMatch> resultRef = new AtomicReference<>();
91
92 impl.match(host, request, HttpTestUtils.countDown(latch1, resultRef::set));
93
94 latch1.await();
95
96 assertNull(resultRef.get());
97 }
98
99 @Test
100 void testGetCacheEntryFetchesFromCacheOnCacheHitIfNoVariants() throws Exception {
101 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
102 assertFalse(entry.hasVariants());
103 final HttpHost host = new HttpHost("foo.example.com");
104 final HttpRequest request = new HttpGet("http://foo.example.com/bar");
105
106 final String key = CacheKeyGenerator.INSTANCE.generateKey(host, request);
107
108 backing.map.put(key,entry);
109
110 final CountDownLatch latch1 = new CountDownLatch(1);
111 final AtomicReference<CacheMatch> resultRef = new AtomicReference<>();
112
113 impl.match(host, request, HttpTestUtils.countDown(latch1, resultRef::set));
114
115 latch1.await();
116 final CacheMatch result = resultRef.get();
117
118 assertNotNull(result);
119 assertNotNull(result.hit);
120 assertSame(entry, result.hit.entry);
121 }
122
123 @Test
124 void testGetCacheEntryReturnsNullIfNoVariantInCache() throws Exception {
125 final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
126 origRequest.setHeader("Accept-Encoding","gzip");
127
128 final ByteArrayBuffer buf = HttpTestUtils.makeRandomBuffer(128);
129 final HttpResponse origResponse = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
130 origResponse.setHeader("Date", DateUtils.formatStandardDate(now));
131 origResponse.setHeader("Cache-Control", "max-age=3600, public");
132 origResponse.setHeader("ETag", "\"etag\"");
133 origResponse.setHeader("Vary", "Accept-Encoding");
134 origResponse.setHeader("Content-Encoding","gzip");
135
136 final CountDownLatch latch1 = new CountDownLatch(1);
137
138 impl.store(host, origRequest, origResponse, buf, now, now, HttpTestUtils.countDown(latch1));
139
140 latch1.await();
141
142 final HttpRequest request = new HttpGet("http://foo.example.com/bar");
143
144 final CountDownLatch latch2 = new CountDownLatch(1);
145 final AtomicReference<CacheMatch> resultRef = new AtomicReference<>();
146 impl.match(host, request, HttpTestUtils.countDown(latch2, resultRef::set));
147
148 latch2.await();
149 final CacheMatch result = resultRef.get();
150
151 assertNotNull(result);
152 assertNull(result.hit);
153 }
154
155 @Test
156 void testGetCacheEntryReturnsVariantIfPresentInCache() throws Exception {
157 final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
158 origRequest.setHeader("Accept-Encoding","gzip");
159
160 final ByteArrayBuffer buf = HttpTestUtils.makeRandomBuffer(128);
161 final HttpResponse origResponse = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
162 origResponse.setHeader("Date", DateUtils.formatStandardDate(now));
163 origResponse.setHeader("Cache-Control", "max-age=3600, public");
164 origResponse.setHeader("ETag", "\"etag\"");
165 origResponse.setHeader("Vary", "Accept-Encoding");
166 origResponse.setHeader("Content-Encoding","gzip");
167
168 final CountDownLatch latch1 = new CountDownLatch(1);
169
170 impl.store(host, origRequest, origResponse, buf, now, now, HttpTestUtils.countDown(latch1));
171
172 latch1.await();
173
174 final HttpRequest request = new HttpGet("http://foo.example.com/bar");
175 request.setHeader("Accept-Encoding","gzip");
176
177 final CountDownLatch latch2 = new CountDownLatch(1);
178 final AtomicReference<CacheMatch> resultRef = new AtomicReference<>();
179 impl.match(host, request, HttpTestUtils.countDown(latch2, resultRef::set));
180
181 latch2.await();
182 final CacheMatch result = resultRef.get();
183
184 assertNotNull(result);
185 assertNotNull(result.hit);
186 }
187
188 @Test
189 void testGetCacheEntryReturnsVariantWithMostRecentDateHeader() throws Exception {
190 final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
191 origRequest.setHeader("Accept-Encoding", "gzip");
192
193 final ByteArrayBuffer buf = HttpTestUtils.makeRandomBuffer(128);
194
195
196 final HttpResponse origResponse1 = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
197 origResponse1.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(now.minusSeconds(3600)));
198 origResponse1.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=3600, public");
199 origResponse1.setHeader(HttpHeaders.ETAG, "\"etag1\"");
200 origResponse1.setHeader(HttpHeaders.VARY, "Accept-Encoding");
201
202 final HttpResponse origResponse2 = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
203 origResponse2.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(now));
204 origResponse2.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=3600, public");
205 origResponse2.setHeader(HttpHeaders.ETAG, "\"etag2\"");
206 origResponse2.setHeader(HttpHeaders.VARY, "Accept-Encoding");
207
208
209 final CountDownLatch latch1 = new CountDownLatch(2);
210
211 impl.store(host, origRequest, origResponse1, buf, now, now, HttpTestUtils.countDown(latch1));
212 impl.store(host, origRequest, origResponse2, buf, now, now, HttpTestUtils.countDown(latch1));
213
214 latch1.await();
215
216 final HttpRequest request = new HttpGet("http://foo.example.com/bar");
217 request.setHeader("Accept-Encoding", "gzip");
218
219 final CountDownLatch latch2 = new CountDownLatch(1);
220 final AtomicReference<CacheMatch> resultRef = new AtomicReference<>();
221 impl.match(host, request, HttpTestUtils.countDown(latch2, resultRef::set));
222
223 latch2.await();
224 final CacheMatch result = resultRef.get();
225
226 assertNotNull(result);
227 assertNotNull(result.hit);
228 final HttpCacheEntry entry = result.hit.entry;
229 assertNotNull(entry);
230
231
232
233 final String expectedEtag = origResponse2.getFirstHeader(HttpHeaders.ETAG).getValue();
234 final String actualEtag = entry.getFirstHeader(HttpHeaders.ETAG).getValue();
235
236 assertEquals(expectedEtag, actualEtag);
237 }
238
239 @Test
240 void testGetVariantsRootNoVariants() throws Exception {
241 final HttpCacheEntry root = HttpTestUtils.makeCacheEntry();
242
243 final CountDownLatch latch1 = new CountDownLatch(1);
244 final AtomicReference<Collection<CacheHit>> resultRef = new AtomicReference<>();
245 impl.getVariants(new CacheHit("root-key", root), HttpTestUtils.countDown(latch1, resultRef::set));
246
247 latch1.await();
248 final Collection<CacheHit> variants = resultRef.get();
249
250 assertNotNull(variants);
251 assertEquals(0, variants.size());
252 }
253
254 @Test
255 void testGetVariantsRootNonExistentVariants() throws Exception {
256 final Set<String> varinats = new HashSet<>();
257 varinats.add("variant1");
258 varinats.add("variant2");
259 final HttpCacheEntry root = HttpTestUtils.makeCacheEntry(varinats);
260
261 final CountDownLatch latch1 = new CountDownLatch(1);
262 final AtomicReference<Collection<CacheHit>> resultRef = new AtomicReference<>();
263 impl.getVariants(new CacheHit("root-key", root), HttpTestUtils.countDown(latch1, resultRef::set));
264
265 latch1.await();
266 final Collection<CacheHit> variants = resultRef.get();
267
268 assertNotNull(variants);
269 assertEquals(0, variants.size());
270 }
271
272 @Test
273 void testGetVariantCacheEntriesReturnsAllVariants() throws Exception {
274 final HttpHost host = new HttpHost("foo.example.com");
275 final URI uri = new URI("http://foo.example.com/bar");
276 final HttpRequest req1 = new HttpGet(uri);
277 req1.setHeader("Accept-Encoding", "gzip");
278
279 final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(uri);
280
281 final HttpResponse resp1 = HttpTestUtils.make200Response();
282 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
283 resp1.setHeader("Cache-Control", "max-age=3600, public");
284 resp1.setHeader("ETag", "\"etag1\"");
285 resp1.setHeader("Vary", "Accept-Encoding");
286 resp1.setHeader("Content-Encoding","gzip");
287
288 final HttpRequest req2 = new HttpGet(uri);
289 req2.setHeader("Accept-Encoding", "identity");
290
291 final HttpResponse resp2 = HttpTestUtils.make200Response();
292 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
293 resp2.setHeader("Cache-Control", "max-age=3600, public");
294 resp2.setHeader("ETag", "\"etag2\"");
295 resp2.setHeader("Vary", "Accept-Encoding");
296 resp2.setHeader("Content-Encoding","gzip");
297
298 final CountDownLatch latch1 = new CountDownLatch(2);
299
300 final AtomicReference<CacheHit> resultRef1 = new AtomicReference<>();
301 final AtomicReference<CacheHit> resultRef2 = new AtomicReference<>();
302
303 impl.store(host, req1, resp1, null, now, now, HttpTestUtils.countDown(latch1, resultRef1::set));
304 impl.store(host, req2, resp2, null, now, now, HttpTestUtils.countDown(latch1, resultRef2::set));
305
306 latch1.await();
307
308 final CacheHit hit1 = resultRef1.get();
309 final CacheHit hit2 = resultRef2.get();
310
311 final Set<String> variants = new HashSet<>();
312 variants.add("{accept-encoding=gzip}");
313 variants.add("{accept-encoding=identity}");
314
315 final CountDownLatch latch2 = new CountDownLatch(1);
316 final AtomicReference<Collection<CacheHit>> resultRef3 = new AtomicReference<>();
317
318 impl.getVariants(new CacheHit(hit1.rootKey, HttpTestUtils.makeCacheEntry(variants)),
319 HttpTestUtils.countDown(latch2, resultRef3::set));
320
321 latch2.await();
322
323 final Map<String, HttpCacheEntry> variantMap = resultRef3.get().stream()
324 .collect(Collectors.toMap(CacheHit::getEntryKey, e -> e.entry));
325
326 assertNotNull(variantMap);
327 assertEquals(2, variantMap.size());
328 MatcherAssert.assertThat(variantMap.get("{accept-encoding=gzip}" + rootKey),
329 HttpCacheEntryMatcher.equivalent(hit1.entry));
330 MatcherAssert.assertThat(variantMap.get("{accept-encoding=identity}" + rootKey),
331 HttpCacheEntryMatcher.equivalent(hit2.entry));
332 }
333
334 @Test
335 void testUpdateCacheEntry() throws Exception {
336 final HttpHost host = new HttpHost("foo.example.com");
337 final URI uri = new URI("http://foo.example.com/bar");
338 final HttpRequest req1 = new HttpGet(uri);
339
340 final HttpResponse resp1 = HttpTestUtils.make200Response();
341 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
342 resp1.setHeader("Cache-Control", "max-age=3600, public");
343 resp1.setHeader("ETag", "\"etag1\"");
344 resp1.setHeader("Content-Encoding","gzip");
345
346 final HttpRequest revalidate = new HttpGet(uri);
347 revalidate.setHeader("If-None-Match","\"etag1\"");
348
349 final HttpResponse resp2 = HttpTestUtils.make304Response();
350 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
351 resp2.setHeader("Cache-Control", "max-age=3600, public");
352
353 final CountDownLatch latch1 = new CountDownLatch(1);
354
355 final AtomicReference<CacheHit> resultRef1 = new AtomicReference<>();
356 impl.store(host, req1, resp1, null, now, now, HttpTestUtils.countDown(latch1, resultRef1::set));
357
358 latch1.await();
359 final CacheHit hit1 = resultRef1.get();
360
361 Assertions.assertNotNull(hit1);
362 Assertions.assertEquals(1, backing.map.size());
363 Assertions.assertSame(hit1.entry, backing.map.get(hit1.getEntryKey()));
364
365 final CountDownLatch latch2 = new CountDownLatch(1);
366
367 final AtomicReference<CacheHit> resultRef2 = new AtomicReference<>();
368 impl.update(hit1, host, req1, resp2, now, now, HttpTestUtils.countDown(latch2, resultRef2::set));
369
370 latch2.await();
371 final CacheHit updated = resultRef2.get();
372
373 Assertions.assertNotNull(updated);
374 Assertions.assertEquals(1, backing.map.size());
375 Assertions.assertSame(updated.entry, backing.map.get(hit1.getEntryKey()));
376
377 MatcherAssert.assertThat(
378 updated.entry.getHeaders(),
379 HeadersMatcher.same(
380 new BasicHeader("Server", "MockOrigin/1.0"),
381 new BasicHeader("ETag", "\"etag1\""),
382 new BasicHeader("Content-Encoding","gzip"),
383 new BasicHeader("Date", DateUtils.formatStandardDate(now)),
384 new BasicHeader("Cache-Control", "max-age=3600, public")
385 ));
386 }
387
388 @Test
389 void testUpdateVariantCacheEntry() throws Exception {
390 final HttpHost host = new HttpHost("foo.example.com");
391 final URI uri = new URI("http://foo.example.com/bar");
392 final HttpRequest req1 = new HttpGet(uri);
393 req1.setHeader("User-Agent", "agent1");
394
395 final HttpResponse resp1 = HttpTestUtils.make200Response();
396 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
397 resp1.setHeader("Cache-Control", "max-age=3600, public");
398 resp1.setHeader("ETag", "\"etag1\"");
399 resp1.setHeader("Content-Encoding","gzip");
400 resp1.setHeader("Vary", "User-Agent");
401
402 final HttpRequest revalidate = new HttpGet(uri);
403 revalidate.setHeader("If-None-Match","\"etag1\"");
404
405 final HttpResponse resp2 = HttpTestUtils.make304Response();
406 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
407 resp2.setHeader("Cache-Control", "max-age=3600, public");
408
409 final CountDownLatch latch1 = new CountDownLatch(1);
410
411 final AtomicReference<CacheHit> resultRef1 = new AtomicReference<>();
412 impl.store(host, req1, resp1, null, now, now, HttpTestUtils.countDown(latch1, resultRef1::set));
413
414 latch1.await();
415 final CacheHit hit1 = resultRef1.get();
416
417 Assertions.assertNotNull(hit1);
418 Assertions.assertEquals(2, backing.map.size());
419 Assertions.assertSame(hit1.entry, backing.map.get(hit1.getEntryKey()));
420
421 final CountDownLatch latch2 = new CountDownLatch(1);
422
423 final AtomicReference<CacheHit> resultRef2 = new AtomicReference<>();
424 impl.update(hit1, host, req1, resp2, now, now, HttpTestUtils.countDown(latch2, resultRef2::set));
425
426 latch2.await();
427 final CacheHit updated = resultRef2.get();
428
429 Assertions.assertNotNull(updated);
430 Assertions.assertEquals(2, backing.map.size());
431 Assertions.assertSame(updated.entry, backing.map.get(hit1.getEntryKey()));
432
433 MatcherAssert.assertThat(
434 updated.entry.getHeaders(),
435 HeadersMatcher.same(
436 new BasicHeader("Server", "MockOrigin/1.0"),
437 new BasicHeader("ETag", "\"etag1\""),
438 new BasicHeader("Content-Encoding","gzip"),
439 new BasicHeader("Vary","User-Agent"),
440 new BasicHeader("Date", DateUtils.formatStandardDate(now)),
441 new BasicHeader("Cache-Control", "max-age=3600, public")
442 ));
443 }
444
445 @Test
446 void testUpdateCacheEntryTurnsVariant() throws Exception {
447 final HttpHost host = new HttpHost("foo.example.com");
448 final URI uri = new URI("http://foo.example.com/bar");
449 final HttpRequest req1 = new HttpGet(uri);
450 req1.setHeader("User-Agent", "agent1");
451
452 final HttpResponse resp1 = HttpTestUtils.make200Response();
453 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
454 resp1.setHeader("Cache-Control", "max-age=3600, public");
455 resp1.setHeader("ETag", "\"etag1\"");
456 resp1.setHeader("Content-Encoding","gzip");
457
458 final HttpRequest revalidate = new HttpGet(uri);
459 revalidate.setHeader("If-None-Match","\"etag1\"");
460
461 final HttpResponse resp2 = HttpTestUtils.make304Response();
462 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
463 resp2.setHeader("Cache-Control", "max-age=3600, public");
464 resp2.setHeader("Vary", "User-Agent");
465
466 final CountDownLatch latch1 = new CountDownLatch(1);
467
468 final AtomicReference<CacheHit> resultRef1 = new AtomicReference<>();
469 impl.store(host, req1, resp1, null, now, now, HttpTestUtils.countDown(latch1, resultRef1::set));
470
471 latch1.await();
472 final CacheHit hit1 = resultRef1.get();
473
474 Assertions.assertNotNull(hit1);
475 Assertions.assertEquals(1, backing.map.size());
476 Assertions.assertSame(hit1.entry, backing.map.get(hit1.getEntryKey()));
477
478 final CountDownLatch latch2 = new CountDownLatch(1);
479
480 final AtomicReference<CacheHit> resultRef2 = new AtomicReference<>();
481 impl.update(hit1, host, req1, resp2, now, now, HttpTestUtils.countDown(latch2, resultRef2::set));
482
483 latch2.await();
484 final CacheHit updated = resultRef2.get();
485
486 Assertions.assertNotNull(updated);
487 Assertions.assertEquals(2, backing.map.size());
488
489 MatcherAssert.assertThat(
490 updated.entry.getHeaders(),
491 HeadersMatcher.same(
492 new BasicHeader("Server", "MockOrigin/1.0"),
493 new BasicHeader("ETag", "\"etag1\""),
494 new BasicHeader("Content-Encoding","gzip"),
495 new BasicHeader("Date", DateUtils.formatStandardDate(now)),
496 new BasicHeader("Cache-Control", "max-age=3600, public"),
497 new BasicHeader("Vary","User-Agent")));
498 }
499
500 @Test
501 void testStoreFromNegotiatedVariant() throws Exception {
502 final HttpHost host = new HttpHost("foo.example.com");
503 final URI uri = new URI("http://foo.example.com/bar");
504 final HttpRequest req1 = new HttpGet(uri);
505 req1.setHeader("User-Agent", "agent1");
506
507 final HttpResponse resp1 = HttpTestUtils.make200Response();
508 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
509 resp1.setHeader("Cache-Control", "max-age=3600, public");
510 resp1.setHeader("ETag", "\"etag1\"");
511 resp1.setHeader("Content-Encoding","gzip");
512 resp1.setHeader("Vary", "User-Agent");
513
514 final CountDownLatch latch1 = new CountDownLatch(1);
515
516 final AtomicReference<CacheHit> resultRef1 = new AtomicReference<>();
517 impl.store(host, req1, resp1, null, now, now, HttpTestUtils.countDown(latch1, resultRef1::set));
518
519 latch1.await();
520 final CacheHit hit1 = resultRef1.get();
521
522 Assertions.assertNotNull(hit1);
523 Assertions.assertEquals(2, backing.map.size());
524 Assertions.assertSame(hit1.entry, backing.map.get(hit1.getEntryKey()));
525
526 final HttpRequest req2 = new HttpGet(uri);
527 req2.setHeader("User-Agent", "agent2");
528
529 final HttpResponse resp2 = HttpTestUtils.make304Response();
530 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
531 resp2.setHeader("Cache-Control", "max-age=3600, public");
532
533 final CountDownLatch latch2 = new CountDownLatch(1);
534
535 final AtomicReference<CacheHit> resultRef2 = new AtomicReference<>();
536 impl.storeFromNegotiated(hit1, host, req2, resp2, now, now, HttpTestUtils.countDown(latch2, resultRef2::set));
537
538 final CacheHit hit2 = resultRef2.get();
539
540 Assertions.assertNotNull(hit2);
541 Assertions.assertEquals(3, backing.map.size());
542
543 MatcherAssert.assertThat(
544 hit2.entry.getHeaders(),
545 HeadersMatcher.same(
546 new BasicHeader("Server", "MockOrigin/1.0"),
547 new BasicHeader("ETag", "\"etag1\""),
548 new BasicHeader("Content-Encoding","gzip"),
549 new BasicHeader("Vary","User-Agent"),
550 new BasicHeader("Date", DateUtils.formatStandardDate(now)),
551 new BasicHeader("Cache-Control", "max-age=3600, public")));
552 }
553
554 @Test
555 void testInvalidatesUnsafeRequests() throws Exception {
556 final HttpRequest request = new BasicHttpRequest("POST", "/path");
557 final HttpResponse response = HttpTestUtils.make200Response();
558
559 final String key = CacheKeyGenerator.INSTANCE.generateKey(host, request);
560
561 backing.putEntry(key, HttpTestUtils.makeCacheEntry());
562
563 final CountDownLatch latch = new CountDownLatch(1);
564 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
565
566 latch.await();
567
568 verify(backing).getEntry(Mockito.eq(key), Mockito.any());
569 verify(backing).removeEntry(Mockito.eq(key), Mockito.any());
570 Assertions.assertNull(backing.getEntry(key));
571 }
572
573 @Test
574 void testDoesNotInvalidateSafeRequests() throws Exception {
575 final HttpRequest request1 = new BasicHttpRequest("GET", "/");
576 final HttpResponse response1 = HttpTestUtils.make200Response();
577 final CountDownLatch latch1 = new CountDownLatch(1);
578
579 impl.evictInvalidatedEntries(host, request1, response1, HttpTestUtils.countDown(latch1));
580
581 latch1.await();
582
583 verifyNoMoreInteractions(backing);
584
585 final HttpRequest request2 = new BasicHttpRequest("HEAD", "/");
586 final HttpResponse response2 = HttpTestUtils.make200Response();
587 final CountDownLatch latch2 = new CountDownLatch(1);
588
589 impl.evictInvalidatedEntries(host, request2, response2, HttpTestUtils.countDown(latch2));
590
591 latch2.await();
592
593 verifyNoMoreInteractions(backing);
594 }
595
596 @Test
597 void testInvalidatesUnsafeRequestsWithVariants() throws Exception {
598 final HttpRequest request = new BasicHttpRequest("POST", "/path");
599 final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
600 final Set<String> variants = new HashSet<>();
601 variants.add("{var1}");
602 variants.add("{var2}");
603 final String variantKey1 = "{var1}" + rootKey;
604 final String variantKey2 = "{var2}" + rootKey;
605
606 final HttpResponse response = HttpTestUtils.make200Response();
607
608 backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry(variants));
609 backing.putEntry(variantKey1, HttpTestUtils.makeCacheEntry());
610 backing.putEntry(variantKey2, HttpTestUtils.makeCacheEntry());
611
612 final CountDownLatch latch = new CountDownLatch(1);
613 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
614
615 latch.await();
616
617 verify(backing).getEntry(Mockito.eq(rootKey), Mockito.any());
618 verify(backing).removeEntry(Mockito.eq(rootKey), Mockito.any());
619 verify(backing).removeEntry(Mockito.eq(variantKey1), Mockito.any());
620 verify(backing).removeEntry(Mockito.eq(variantKey2), Mockito.any());
621
622 Assertions.assertNull(backing.getEntry(rootKey));
623 Assertions.assertNull(backing.getEntry(variantKey1));
624 Assertions.assertNull(backing.getEntry(variantKey2));
625 }
626
627 @Test
628 void testInvalidateUriSpecifiedByContentLocationAndFresher() throws Exception {
629 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
630 final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
631 final URI contentUri = new URIBuilder()
632 .setHttpHost(host)
633 .setPath("/bar")
634 .build();
635 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
636
637 final HttpResponse response = HttpTestUtils.make200Response();
638 response.setHeader("ETag","\"new-etag\"");
639 response.setHeader("Date", DateUtils.formatStandardDate(now));
640 response.setHeader("Content-Location", contentUri.toASCIIString());
641
642 backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
643 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
644 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
645 new BasicHeader("ETag", "\"old-etag\"")
646 ));
647
648 final CountDownLatch latch = new CountDownLatch(1);
649 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
650
651 latch.await();
652
653 verify(backing).getEntry(Mockito.eq(rootKey), Mockito.any());
654 verify(backing).removeEntry(Mockito.eq(rootKey), Mockito.any());
655 verify(backing).getEntry(Mockito.eq(contentKey), Mockito.any());
656 verify(backing).removeEntry(Mockito.eq(contentKey), Mockito.any());
657 }
658
659 @Test
660 void testInvalidateUriSpecifiedByLocationAndFresher() throws Exception {
661 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
662 final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
663 final URI contentUri = new URIBuilder()
664 .setHttpHost(host)
665 .setPath("/bar")
666 .build();
667 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
668
669 final HttpResponse response = HttpTestUtils.make200Response();
670 response.setHeader("ETag","\"new-etag\"");
671 response.setHeader("Date", DateUtils.formatStandardDate(now));
672 response.setHeader("Location", contentUri.toASCIIString());
673
674 backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
675 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
676 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
677 new BasicHeader("ETag", "\"old-etag\"")
678 ));
679
680 final CountDownLatch latch = new CountDownLatch(1);
681 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
682
683 latch.await();
684
685 verify(backing).getEntry(Mockito.eq(rootKey), Mockito.any());
686 verify(backing).removeEntry(Mockito.eq(rootKey), Mockito.any());
687 verify(backing).getEntry(Mockito.eq(contentKey), Mockito.any());
688 verify(backing).removeEntry(Mockito.eq(contentKey), Mockito.any());
689 }
690
691 @Test
692 void testDoesNotInvalidateForUnsuccessfulResponse() throws Exception {
693 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
694 final URI contentUri = new URIBuilder()
695 .setHttpHost(host)
696 .setPath("/bar")
697 .build();
698 final HttpResponse response = HttpTestUtils.make500Response();
699 response.setHeader("ETag","\"new-etag\"");
700 response.setHeader("Date", DateUtils.formatStandardDate(now));
701 response.setHeader("Content-Location", contentUri.toASCIIString());
702
703 final CountDownLatch latch = new CountDownLatch(1);
704 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
705
706 latch.await();
707
708 verifyNoMoreInteractions(backing);
709 }
710
711 @Test
712 void testInvalidateUriSpecifiedByContentLocationNonCanonical() throws Exception {
713 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
714 final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
715 final URI contentUri = new URIBuilder()
716 .setHttpHost(host)
717 .setPath("/bar")
718 .build();
719 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
720
721 final HttpResponse response = HttpTestUtils.make200Response();
722 response.setHeader("ETag","\"new-etag\"");
723 response.setHeader("Date", DateUtils.formatStandardDate(now));
724
725 response.setHeader("Content-Location", contentUri.toASCIIString());
726
727 backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
728
729 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
730 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
731 new BasicHeader("ETag", "\"old-etag\"")));
732
733 final CountDownLatch latch = new CountDownLatch(1);
734 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
735
736 latch.await();
737
738 verify(backing).getEntry(Mockito.eq(rootKey), Mockito.any());
739 verify(backing).removeEntry(Mockito.eq(rootKey), Mockito.any());
740 verify(backing).getEntry(Mockito.eq(contentKey), Mockito.any());
741 verify(backing).removeEntry(Mockito.eq(contentKey), Mockito.any());
742 Assertions.assertNull(backing.getEntry(rootKey));
743 Assertions.assertNull(backing.getEntry(contentKey));
744 }
745
746 @Test
747 void testInvalidateUriSpecifiedByContentLocationRelative() throws Exception {
748 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
749 final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
750 final URI contentUri = new URIBuilder()
751 .setHttpHost(host)
752 .setPath("/bar")
753 .build();
754 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
755
756 final HttpResponse response = HttpTestUtils.make200Response();
757 response.setHeader("ETag","\"new-etag\"");
758 response.setHeader("Date", DateUtils.formatStandardDate(now));
759
760 response.setHeader("Content-Location", "/bar");
761
762 backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
763
764 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
765 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
766 new BasicHeader("ETag", "\"old-etag\"")));
767
768 final CountDownLatch latch = new CountDownLatch(1);
769 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
770
771 latch.await();
772
773 verify(backing).getEntry(Mockito.eq(rootKey), Mockito.any());
774 verify(backing).removeEntry(Mockito.eq(rootKey), Mockito.any());
775 verify(backing).getEntry(Mockito.eq(contentKey), Mockito.any());
776 verify(backing).removeEntry(Mockito.eq(contentKey), Mockito.any());
777 Assertions.assertNull(backing.getEntry(rootKey));
778 Assertions.assertNull(backing.getEntry(contentKey));
779 }
780
781 @Test
782 void testDoesNotInvalidateUriSpecifiedByContentLocationOtherOrigin() throws Exception {
783 final HttpRequest request = new BasicHttpRequest("PUT", "/");
784 final URI contentUri = new URIBuilder()
785 .setHost("bar.example.com")
786 .setPath("/")
787 .build();
788 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
789
790 final HttpResponse response = HttpTestUtils.make200Response();
791 response.setHeader("ETag","\"new-etag\"");
792 response.setHeader("Date", DateUtils.formatStandardDate(now));
793 response.setHeader("Content-Location", contentUri.toASCIIString());
794
795 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry());
796
797 final CountDownLatch latch = new CountDownLatch(1);
798 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
799
800 latch.await();
801
802 verify(backing, Mockito.never()).getEntry(contentKey);
803 verify(backing, Mockito.never()).removeEntry(Mockito.eq(contentKey), Mockito.any());
804 }
805
806 @Test
807 void testDoesNotInvalidateUriSpecifiedByContentLocationIfEtagsMatch() throws Exception {
808 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
809 final URI contentUri = new URIBuilder()
810 .setHttpHost(host)
811 .setPath("/bar")
812 .build();
813 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
814
815 final HttpResponse response = HttpTestUtils.make200Response();
816 response.setHeader("ETag","\"same-etag\"");
817 response.setHeader("Date", DateUtils.formatStandardDate(now));
818 response.setHeader("Content-Location", contentUri.toASCIIString());
819
820 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
821 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
822 new BasicHeader("ETag", "\"same-etag\"")));
823
824 final CountDownLatch latch = new CountDownLatch(1);
825 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
826
827 latch.await();
828
829 verify(backing).getEntry(Mockito.eq(contentKey), Mockito.any());
830 verify(backing, Mockito.never()).removeEntry(Mockito.eq(contentKey), Mockito.any());
831 }
832
833 @Test
834 void testDoesNotInvalidateUriSpecifiedByContentLocationIfOlder() throws Exception {
835 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
836 final URI contentUri = new URIBuilder()
837 .setHttpHost(host)
838 .setPath("/bar")
839 .build();
840 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
841
842 final HttpResponse response = HttpTestUtils.make200Response();
843 response.setHeader("ETag","\"new-etag\"");
844 response.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
845 response.setHeader("Content-Location", contentUri.toASCIIString());
846
847 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
848 new BasicHeader("Date", DateUtils.formatStandardDate(now)),
849 new BasicHeader("ETag", "\"old-etag\"")));
850
851 final CountDownLatch latch = new CountDownLatch(1);
852 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
853
854 latch.await();
855
856 verify(backing).getEntry(Mockito.eq(contentKey), Mockito.any());
857 verify(backing, Mockito.never()).removeEntry(Mockito.eq(contentKey), Mockito.any());
858 }
859
860 @Test
861 void testDoesNotInvalidateUriSpecifiedByContentLocationIfResponseHasNoEtag() throws Exception {
862 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
863 final URI contentUri = new URIBuilder()
864 .setHttpHost(host)
865 .setPath("/bar")
866 .build();
867 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
868
869 final HttpResponse response = HttpTestUtils.make200Response();
870 response.removeHeaders("ETag");
871 response.setHeader("Date", DateUtils.formatStandardDate(now));
872 response.setHeader("Content-Location", contentUri.toASCIIString());
873
874 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
875 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
876 new BasicHeader("ETag", "\"old-etag\"")));
877
878 final CountDownLatch latch = new CountDownLatch(1);
879 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
880
881 latch.await();
882
883 verify(backing).getEntry(Mockito.eq(contentKey), Mockito.any());
884 verify(backing, Mockito.never()).removeEntry(Mockito.eq(contentKey), Mockito.any());
885 }
886
887 @Test
888 void testDoesNotInvalidateUriSpecifiedByContentLocationIfEntryHasNoEtag() throws Exception {
889 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
890 final URI contentUri = new URIBuilder()
891 .setHttpHost(host)
892 .setPath("/bar")
893 .build();
894 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
895
896 final HttpResponse response = HttpTestUtils.make200Response();
897 response.setHeader("ETag", "\"some-etag\"");
898 response.setHeader("Date", DateUtils.formatStandardDate(now));
899 response.setHeader("Content-Location", contentUri.toASCIIString());
900
901 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
902 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))));
903
904 final CountDownLatch latch = new CountDownLatch(1);
905 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
906
907 latch.await();
908
909 verify(backing).getEntry(Mockito.eq(contentKey), Mockito.any());
910 verify(backing, Mockito.never()).removeEntry(Mockito.eq(contentKey), Mockito.any());
911 }
912
913 @Test
914 void testInvalidatesUriSpecifiedByContentLocationIfResponseHasNoDate() throws Exception {
915 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
916 final URI contentUri = new URIBuilder()
917 .setHttpHost(host)
918 .setPath("/bar")
919 .build();
920 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
921
922 final HttpResponse response = HttpTestUtils.make200Response();
923 response.setHeader("ETag", "\"new-etag\"");
924 response.removeHeaders("Date");
925 response.setHeader("Content-Location", contentUri.toASCIIString());
926
927 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
928 new BasicHeader("ETag", "\"old-etag\""),
929 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))));
930
931 final CountDownLatch latch = new CountDownLatch(1);
932 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
933
934 latch.await();
935
936 verify(backing).getEntry(Mockito.eq(contentKey), Mockito.any());
937 verify(backing).removeEntry(Mockito.eq(contentKey), Mockito.any());
938 }
939
940 @Test
941 void testInvalidatesUriSpecifiedByContentLocationIfEntryHasNoDate() throws Exception {
942 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
943 final URI contentUri = new URIBuilder()
944 .setHttpHost(host)
945 .setPath("/bar")
946 .build();
947 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
948
949 final HttpResponse response = HttpTestUtils.make200Response();
950 response.setHeader("ETag","\"new-etag\"");
951 response.setHeader("Date", DateUtils.formatStandardDate(now));
952 response.setHeader("Content-Location", contentUri.toASCIIString());
953
954 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
955 new BasicHeader("ETag", "\"old-etag\"")));
956
957 final CountDownLatch latch = new CountDownLatch(1);
958 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
959
960 latch.await();
961
962 verify(backing).getEntry(Mockito.eq(contentKey), Mockito.any());
963 verify(backing).removeEntry(Mockito.eq(contentKey), Mockito.any());
964 }
965
966 @Test
967 void testInvalidatesUriSpecifiedByContentLocationIfResponseHasMalformedDate() throws Exception {
968 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
969 final URI contentUri = new URIBuilder()
970 .setHttpHost(host)
971 .setPath("/bar")
972 .build();
973 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
974
975 final HttpResponse response = HttpTestUtils.make200Response();
976 response.setHeader("ETag","\"new-etag\"");
977 response.setHeader("Date", "huh?");
978 response.setHeader("Content-Location", contentUri.toASCIIString());
979
980 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
981 new BasicHeader("ETag", "\"old-etag\""),
982 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))));
983
984 final CountDownLatch latch = new CountDownLatch(1);
985 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
986
987 latch.await();
988
989 verify(backing).getEntry(Mockito.eq(contentKey), Mockito.any());
990 verify(backing).removeEntry(Mockito.eq(contentKey), Mockito.any());
991 }
992
993 @Test
994 void testInvalidatesUriSpecifiedByContentLocationIfEntryHasMalformedDate() throws Exception {
995 final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
996 final URI contentUri = new URIBuilder()
997 .setHttpHost(host)
998 .setPath("/bar")
999 .build();
1000 final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
1001
1002 final HttpResponse response = HttpTestUtils.make200Response();
1003 response.setHeader("ETag","\"new-etag\"");
1004 response.setHeader("Date", DateUtils.formatStandardDate(now));
1005 response.setHeader("Content-Location", contentUri.toASCIIString());
1006
1007 backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
1008 new BasicHeader("ETag", "\"old-etag\""),
1009 new BasicHeader("Date", "huh?")));
1010
1011 final CountDownLatch latch = new CountDownLatch(1);
1012 impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
1013
1014 latch.await();
1015
1016 verify(backing).getEntry(Mockito.eq(contentKey), Mockito.any());
1017 verify(backing).removeEntry(Mockito.eq(contentKey), Mockito.any());
1018 }
1019
1020 }