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
30 import static org.junit.jupiter.api.Assertions.assertEquals;
31 import static org.junit.jupiter.api.Assertions.assertFalse;
32 import static org.junit.jupiter.api.Assertions.assertTrue;
33
34 import java.io.ByteArrayInputStream;
35 import java.io.IOException;
36 import java.time.Instant;
37 import java.util.concurrent.ScheduledExecutorService;
38 import java.util.concurrent.ScheduledThreadPoolExecutor;
39
40 import org.apache.hc.client5.http.HttpRoute;
41 import org.apache.hc.client5.http.classic.ExecChain;
42 import org.apache.hc.client5.http.classic.ExecRuntime;
43 import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy;
44 import org.apache.hc.client5.http.protocol.HttpClientContext;
45 import org.apache.hc.client5.http.utils.DateUtils;
46 import org.apache.hc.core5.http.ClassicHttpRequest;
47 import org.apache.hc.core5.http.ClassicHttpResponse;
48 import org.apache.hc.core5.http.Header;
49 import org.apache.hc.core5.http.HttpEntity;
50 import org.apache.hc.core5.http.HttpException;
51 import org.apache.hc.core5.http.HttpHost;
52 import org.apache.hc.core5.http.HttpStatus;
53 import org.apache.hc.core5.http.io.entity.InputStreamEntity;
54 import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
55 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
56 import org.junit.jupiter.api.AfterEach;
57 import org.junit.jupiter.api.BeforeEach;
58 import org.junit.jupiter.api.Test;
59 import org.mockito.Mock;
60 import org.mockito.Mockito;
61 import org.mockito.MockitoAnnotations;
62
63
64
65
66
67
68 public class TestRFC5861Compliance {
69
70 static final int MAX_BYTES = 1024;
71 static final int MAX_ENTRIES = 100;
72 static final int ENTITY_LENGTH = 128;
73
74 HttpHost host;
75 HttpRoute route;
76 HttpEntity body;
77 HttpClientContext context;
78 @Mock
79 ExecChain mockExecChain;
80 @Mock
81 ExecRuntime mockExecRuntime;
82 ClassicHttpRequest request;
83 ClassicHttpResponse originResponse;
84 CacheConfig config;
85 CachingExec impl;
86 HttpCache cache;
87 ScheduledExecutorService executorService;
88
89 @BeforeEach
90 public void setUp() throws Exception {
91 MockitoAnnotations.openMocks(this);
92
93 host = new HttpHost("foo.example.com", 80);
94
95 route = new HttpRoute(host);
96
97 body = HttpTestUtils.makeBody(ENTITY_LENGTH);
98
99 request = new BasicClassicHttpRequest("GET", "/foo");
100
101 context = HttpClientContext.create();
102
103 originResponse = HttpTestUtils.make200Response();
104
105 config = CacheConfig.custom()
106 .setMaxCacheEntries(MAX_ENTRIES)
107 .setMaxObjectSize(MAX_BYTES)
108 .build();
109
110 cache = new BasicHttpCache(config);
111 impl = new CachingExec(cache, null, config);
112
113 executorService = new ScheduledThreadPoolExecutor(1);
114
115 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
116 Mockito.when(mockExecRuntime.fork(null)).thenReturn(mockExecRuntime);
117 }
118
119 @AfterEach
120 public void cleanup() {
121 executorService.shutdownNow();
122 }
123
124 public ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
125 return impl.execute(
126 ClassicRequestBuilder.copy(request).build(),
127 new ExecChain.Scope("test", route, request, mockExecRuntime, context),
128 mockExecChain);
129 }
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 @Test
147 public void testStaleIfErrorInResponseIsTrueReturnsStaleEntryWithWarning()
148 throws Exception{
149 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
150 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
151 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
152 "public, max-age=5, stale-if-error=60");
153
154 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
155 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
156
157 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
158
159 execute(req1);
160
161 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
162
163 final ClassicHttpResponse result = execute(req2);
164
165 HttpTestUtils.assert110WarningFound(result);
166 }
167
168 @Test
169 public void testConsumesErrorResponseWhenServingStale()
170 throws Exception{
171 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
172 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
173 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
174 "public, max-age=5, stale-if-error=60");
175
176 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
177 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
178 final byte[] body101 = HttpTestUtils.getRandomBytes(101);
179 final ByteArrayInputStream buf = new ByteArrayInputStream(body101);
180 final ConsumableInputStream cis = new ConsumableInputStream(buf);
181 final HttpEntity entity = new InputStreamEntity(cis, 101, null);
182 resp2.setEntity(entity);
183
184 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
185
186 execute(req1);
187
188 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
189
190 execute(req2);
191
192 assertTrue(cis.wasClosed());
193 }
194
195 @Test
196 public void testStaleIfErrorInResponseYieldsToMustRevalidate()
197 throws Exception{
198 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
199 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
200 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
201 "public, max-age=5, stale-if-error=60, must-revalidate");
202
203 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
204 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
205
206 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
207
208 execute(req1);
209
210 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
211
212 final ClassicHttpResponse result = execute(req2);
213
214 assertTrue(HttpStatus.SC_OK != result.getCode());
215 }
216
217 @Test
218 public void testStaleIfErrorInResponseYieldsToProxyRevalidateForSharedCache()
219 throws Exception{
220 assertTrue(config.isSharedCache());
221 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
222 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
223 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
224 "public, max-age=5, stale-if-error=60, proxy-revalidate");
225
226 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
227 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
228
229 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
230
231 execute(req1);
232
233 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
234
235 final ClassicHttpResponse result = execute(req2);
236
237 assertTrue(HttpStatus.SC_OK != result.getCode());
238 }
239
240 @Test
241 public void testStaleIfErrorInResponseNeedNotYieldToProxyRevalidateForPrivateCache()
242 throws Exception{
243 final CacheConfig configUnshared = CacheConfig.custom()
244 .setSharedCache(false).build();
245 impl = new CachingExec(new BasicHttpCache(configUnshared), null, configUnshared);
246
247 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
248 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
249 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
250 "public, max-age=5, stale-if-error=60, proxy-revalidate");
251
252 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
253 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
254
255 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
256
257 execute(req1);
258
259 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
260
261 final ClassicHttpResponse result = execute(req2);
262
263 HttpTestUtils.assert110WarningFound(result);
264 }
265
266 @Test
267 public void testStaleIfErrorInResponseYieldsToExplicitFreshnessRequest()
268 throws Exception{
269 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
270 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
271 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
272 "public, max-age=5, stale-if-error=60");
273
274 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
275 req2.setHeader("Cache-Control","min-fresh=2");
276 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
277
278 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
279
280 execute(req1);
281
282 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
283
284 final ClassicHttpResponse result = execute(req2);
285
286 assertTrue(HttpStatus.SC_OK != result.getCode());
287 }
288
289 @Test
290 public void testStaleIfErrorInRequestIsTrueReturnsStaleEntryWithWarning()
291 throws Exception{
292 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
293 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
294 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
295 "public, max-age=5");
296
297 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
298 req2.setHeader("Cache-Control","public, stale-if-error=60");
299 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
300
301 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
302
303 execute(req1);
304
305 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
306
307 final ClassicHttpResponse result = execute(req2);
308
309 HttpTestUtils.assert110WarningFound(result);
310 }
311
312 @Test
313 public void testStaleIfErrorInRequestIsTrueReturnsStaleNonRevalidatableEntryWithWarning()
314 throws Exception {
315 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
316 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
317 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
318 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
319 resp1.setHeader("Cache-Control", "public, max-age=5");
320
321 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
322 req2.setHeader("Cache-Control", "public, stale-if-error=60");
323 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
324
325 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
326
327 execute(req1);
328
329 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
330
331 final ClassicHttpResponse result = execute(req2);
332
333 HttpTestUtils.assert110WarningFound(result);
334 }
335
336 @Test
337 public void testStaleIfErrorInResponseIsFalseReturnsError()
338 throws Exception{
339 final Instant now = Instant.now();
340 final Instant tenSecondsAgo = now.minusSeconds(10);
341 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
342 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
343 "public, max-age=5, stale-if-error=2");
344
345 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
346 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
347
348 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
349
350 execute(req1);
351
352 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
353
354 final ClassicHttpResponse result = execute(req2);
355
356 assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
357 result.getCode());
358 }
359
360 @Test
361 public void testStaleIfErrorInRequestIsFalseReturnsError()
362 throws Exception{
363 final Instant now = Instant.now();
364 final Instant tenSecondsAgo = now.minusSeconds(10);
365 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
366 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
367 "public, max-age=5");
368
369 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
370 req2.setHeader("Cache-Control","stale-if-error=2");
371 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
372
373 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
374
375 execute(req1);
376
377 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
378
379 final ClassicHttpResponse result = execute(req2);
380
381 assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, result.getCode());
382 }
383
384
385
386
387
388
389
390
391
392 @Test
393 public void testStaleWhileRevalidateReturnsStaleEntryWithWarning()
394 throws Exception {
395 config = CacheConfig.custom()
396 .setMaxCacheEntries(MAX_ENTRIES)
397 .setMaxObjectSize(MAX_BYTES)
398 .setAsynchronousWorkers(1)
399 .build();
400
401 impl = new CachingExec(cache, executorService, ImmediateSchedulingStrategy.INSTANCE, config);
402
403 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
404 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
405 final Instant now = Instant.now();
406 final Instant tenSecondsAgo = now.minusSeconds(10);
407 resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
408 resp1.setHeader("ETag","\"etag\"");
409 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
410
411 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
412
413 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
414
415 execute(req1);
416 final ClassicHttpResponse result = execute(req2);
417
418 assertEquals(HttpStatus.SC_OK, result.getCode());
419 boolean warning110Found = false;
420 for(final Header h : result.getHeaders("Warning")) {
421 for(final WarningValue wv : WarningValue.getWarningValues(h)) {
422 if (wv.getWarnCode() == 110) {
423 warning110Found = true;
424 break;
425 }
426 }
427 }
428 assertTrue(warning110Found);
429
430 Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
431 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
432 }
433
434 @Test
435 public void testStaleWhileRevalidateReturnsStaleNonRevalidatableEntryWithWarning()
436 throws Exception {
437 config = CacheConfig.custom().setMaxCacheEntries(MAX_ENTRIES).setMaxObjectSize(MAX_BYTES)
438 .setAsynchronousWorkers(1).build();
439
440 impl = new CachingExec(cache, executorService, ImmediateSchedulingStrategy.INSTANCE, config);
441
442 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
443 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
444 final Instant now = Instant.now();
445 final Instant tenSecondsAgo = now.minusSeconds(10);
446 resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
447 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
448
449 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
450
451 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
452
453 execute(req1);
454 final ClassicHttpResponse result = execute(req2);
455
456 assertEquals(HttpStatus.SC_OK, result.getCode());
457 boolean warning110Found = false;
458 for (final Header h : result.getHeaders("Warning")) {
459 for (final WarningValue wv : WarningValue.getWarningValues(h)) {
460 if (wv.getWarnCode() == 110) {
461 warning110Found = true;
462 break;
463 }
464 }
465 }
466 assertTrue(warning110Found);
467
468 Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
469 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
470 }
471
472 @Test
473 public void testCanAlsoServeStale304sWhileRevalidating()
474 throws Exception {
475
476 config = CacheConfig.custom()
477 .setMaxCacheEntries(MAX_ENTRIES)
478 .setMaxObjectSize(MAX_BYTES)
479 .setAsynchronousWorkers(1)
480 .setSharedCache(false)
481 .build();
482 impl = new CachingExec(cache, executorService, ImmediateSchedulingStrategy.INSTANCE, config);
483
484 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
485 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
486 final Instant now = Instant.now();
487 final Instant tenSecondsAgo = now.minusSeconds(10);
488 resp1.setHeader("Cache-Control", "private, stale-while-revalidate=15");
489 resp1.setHeader("ETag","\"etag\"");
490 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
491
492 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
493 req2.setHeader("If-None-Match","\"etag\"");
494
495 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
496
497 execute(req1);
498 final ClassicHttpResponse result = execute(req2);
499
500 assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
501 boolean warning110Found = false;
502 for(final Header h : result.getHeaders("Warning")) {
503 for(final WarningValue wv : WarningValue.getWarningValues(h)) {
504 if (wv.getWarnCode() == 110) {
505 warning110Found = true;
506 break;
507 }
508 }
509 }
510 assertTrue(warning110Found);
511
512 Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
513 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
514 }
515
516 @Test
517 public void testStaleWhileRevalidateYieldsToMustRevalidate()
518 throws Exception {
519
520 final Instant now = Instant.now();
521 final Instant tenSecondsAgo = now.minusSeconds(10);
522
523 config = CacheConfig.custom()
524 .setMaxCacheEntries(MAX_ENTRIES)
525 .setMaxObjectSize(MAX_BYTES)
526 .setAsynchronousWorkers(1)
527 .build();
528 impl = new CachingExec(cache, null, config);
529
530 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
531 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
532 resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate");
533 resp1.setHeader("ETag","\"etag\"");
534 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
535
536 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
537 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
538 resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate");
539 resp2.setHeader("ETag","\"etag\"");
540 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
541
542 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
543
544 execute(req1);
545
546 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
547
548 final ClassicHttpResponse result = execute(req2);
549
550 assertEquals(HttpStatus.SC_OK, result.getCode());
551 boolean warning110Found = false;
552 for(final Header h : result.getHeaders("Warning")) {
553 for(final WarningValue wv : WarningValue.getWarningValues(h)) {
554 if (wv.getWarnCode() == 110) {
555 warning110Found = true;
556 break;
557 }
558 }
559 }
560 assertFalse(warning110Found);
561 }
562
563 @Test
564 public void testStaleWhileRevalidateYieldsToProxyRevalidateForSharedCache()
565 throws Exception {
566
567 final Instant now = Instant.now();
568 final Instant tenSecondsAgo = now.minusSeconds(10);
569
570 config = CacheConfig.custom()
571 .setMaxCacheEntries(MAX_ENTRIES)
572 .setMaxObjectSize(MAX_BYTES)
573 .setAsynchronousWorkers(1)
574 .setSharedCache(true)
575 .build();
576 impl = new CachingExec(cache, null, config);
577
578 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
579 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
580 resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate");
581 resp1.setHeader("ETag","\"etag\"");
582 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
583
584 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
585 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
586 resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate");
587 resp2.setHeader("ETag","\"etag\"");
588 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
589
590 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
591
592 execute(req1);
593
594 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
595
596 final ClassicHttpResponse result = execute(req2);
597
598 assertEquals(HttpStatus.SC_OK, result.getCode());
599 boolean warning110Found = false;
600 for(final Header h : result.getHeaders("Warning")) {
601 for(final WarningValue wv : WarningValue.getWarningValues(h)) {
602 if (wv.getWarnCode() == 110) {
603 warning110Found = true;
604 break;
605 }
606 }
607 }
608 assertFalse(warning110Found);
609 }
610
611 @Test
612 public void testStaleWhileRevalidateYieldsToExplicitFreshnessRequest()
613 throws Exception {
614
615 final Instant now = Instant.now();
616 final Instant tenSecondsAgo = now.minusSeconds(10);
617
618 config = CacheConfig.custom()
619 .setMaxCacheEntries(MAX_ENTRIES)
620 .setMaxObjectSize(MAX_BYTES)
621 .setAsynchronousWorkers(1)
622 .setSharedCache(true)
623 .build();
624 impl = new CachingExec(cache, null, config);
625
626 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
627 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
628 resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
629 resp1.setHeader("ETag","\"etag\"");
630 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
631
632 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
633 req2.setHeader("Cache-Control","min-fresh=2");
634 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
635 resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
636 resp2.setHeader("ETag","\"etag\"");
637 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
638
639 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
640
641 execute(req1);
642
643 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
644
645 final ClassicHttpResponse result = execute(req2);
646
647 assertEquals(HttpStatus.SC_OK, result.getCode());
648 boolean warning110Found = false;
649 for(final Header h : result.getHeaders("Warning")) {
650 for(final WarningValue wv : WarningValue.getWarningValues(h)) {
651 if (wv.getWarnCode() == 110) {
652 warning110Found = true;
653 break;
654 }
655 }
656 }
657 assertFalse(warning110Found);
658 }
659
660 }