View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.http.impl.cache;
28  
29  import static org.junit.Assert.assertEquals;
30  import static org.junit.Assert.assertFalse;
31  import static org.junit.Assert.assertTrue;
32  
33  import java.io.ByteArrayInputStream;
34  import java.util.Date;
35  import java.util.concurrent.ScheduledExecutorService;
36  import java.util.concurrent.ScheduledThreadPoolExecutor;
37  
38  import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy;
39  import org.apache.hc.client5.http.utils.DateUtils;
40  import org.apache.hc.core5.http.ClassicHttpRequest;
41  import org.apache.hc.core5.http.ClassicHttpResponse;
42  import org.apache.hc.core5.http.Header;
43  import org.apache.hc.core5.http.HttpEntity;
44  import org.apache.hc.core5.http.HttpStatus;
45  import org.apache.hc.core5.http.io.entity.InputStreamEntity;
46  import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
47  import org.easymock.EasyMock;
48  import org.junit.After;
49  import org.junit.Before;
50  import org.junit.Test;
51  
52  /**
53   * A suite of acceptance tests for compliance with RFC5861, which
54   * describes the stale-if-error and stale-while-revalidate
55   * Cache-Control extensions.
56   */
57  public class TestRFC5861Compliance extends AbstractProtocolTest {
58  
59      private ScheduledExecutorService executorService;
60  
61      @Before
62      public void setup() {
63          executorService = new ScheduledThreadPoolExecutor(1);
64          EasyMock.expect(mockExecRuntime.fork(null)).andReturn(mockExecRuntime).anyTimes();
65      }
66  
67      @After
68      public void cleanup() {
69          executorService.shutdownNow();
70      }
71  
72      @Override
73      protected void replayMocks() {
74          super.replayMocks();
75          EasyMock.replay(mockExecRuntime);
76      }
77  
78      @Override
79      protected void verifyMocks() {
80          super.verifyMocks();
81          EasyMock.verify(mockExecRuntime);
82      }
83  
84      /*
85       * "The stale-if-error Cache-Control extension indicates that when an
86       * error is encountered, a cached stale response MAY be used to satisfy
87       * the request, regardless of other freshness information.When used as a
88       * request Cache-Control extension, its scope of application is the request
89       * it appears in; when used as a response Cache-Control extension, its
90       * scope is any request applicable to the cached response in which it
91       * occurs.Its value indicates the upper limit to staleness; when the cached
92       * response is more stale than the indicated amount, the cached response
93       * SHOULD NOT be used to satisfy the request, absent other information.
94       * In this context, an error is any situation that would result in a
95       * 500, 502, 503, or 504 HTTP response status code being returned."
96       *
97       * http://tools.ietf.org/html/rfc5861
98       */
99      @Test
100     public void testStaleIfErrorInResponseIsTrueReturnsStaleEntryWithWarning()
101             throws Exception{
102         final Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
103         final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
104         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
105                 "public, max-age=5, stale-if-error=60");
106 
107         backendExpectsAnyRequestAndReturn(resp1);
108 
109         final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
110         final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
111 
112         backendExpectsAnyRequestAndReturn(resp2);
113 
114         replayMocks();
115         execute(req1);
116         final ClassicHttpResponse result = execute(req2);
117         verifyMocks();
118 
119         HttpTestUtils.assert110WarningFound(result);
120     }
121 
122     @Test
123     public void testConsumesErrorResponseWhenServingStale()
124             throws Exception{
125         final Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
126         final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
127         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
128                 "public, max-age=5, stale-if-error=60");
129 
130         backendExpectsAnyRequestAndReturn(resp1);
131 
132         final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
133         final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
134         final byte[] body101 = HttpTestUtils.getRandomBytes(101);
135         final ByteArrayInputStream buf = new ByteArrayInputStream(body101);
136         final ConsumableInputStream/cache/ConsumableInputStream.html#ConsumableInputStream">ConsumableInputStream cis = new ConsumableInputStream(buf);
137         final HttpEntity entity = new InputStreamEntity(cis, 101, null);
138         resp2.setEntity(entity);
139 
140         backendExpectsAnyRequestAndReturn(resp2);
141 
142         replayMocks();
143         execute(req1);
144         execute(req2);
145         verifyMocks();
146 
147         assertTrue(cis.wasClosed());
148     }
149 
150     @Test
151     public void testStaleIfErrorInResponseYieldsToMustRevalidate()
152             throws Exception{
153         final Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
154         final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
155         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
156                 "public, max-age=5, stale-if-error=60, must-revalidate");
157 
158         backendExpectsAnyRequestAndReturn(resp1);
159 
160         final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
161         final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
162 
163         backendExpectsAnyRequestAndReturn(resp2);
164 
165         replayMocks();
166         execute(req1);
167         final ClassicHttpResponse result = execute(req2);
168         verifyMocks();
169 
170         assertTrue(HttpStatus.SC_OK != result.getCode());
171     }
172 
173     @Test
174     public void testStaleIfErrorInResponseYieldsToProxyRevalidateForSharedCache()
175             throws Exception{
176         assertTrue(config.isSharedCache());
177         final Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
178         final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
179         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
180                 "public, max-age=5, stale-if-error=60, proxy-revalidate");
181 
182         backendExpectsAnyRequestAndReturn(resp1);
183 
184         final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
185         final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
186 
187         backendExpectsAnyRequestAndReturn(resp2);
188 
189         replayMocks();
190         execute(req1);
191         final ClassicHttpResponse result = execute(req2);
192         verifyMocks();
193 
194         assertTrue(HttpStatus.SC_OK != result.getCode());
195     }
196 
197     @Test
198     public void testStaleIfErrorInResponseNeedNotYieldToProxyRevalidateForPrivateCache()
199             throws Exception{
200         final CacheConfig configUnshared = CacheConfig.custom()
201                 .setSharedCache(false).build();
202         impl = new CachingExec(new BasicHttpCache(configUnshared), null, configUnshared);
203 
204         final Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
205         final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
206         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
207                 "public, max-age=5, stale-if-error=60, proxy-revalidate");
208 
209         backendExpectsAnyRequestAndReturn(resp1);
210 
211         final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
212         final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
213 
214         backendExpectsAnyRequestAndReturn(resp2);
215 
216         replayMocks();
217         execute(req1);
218         final ClassicHttpResponse result = execute(req2);
219         verifyMocks();
220 
221         HttpTestUtils.assert110WarningFound(result);
222     }
223 
224     @Test
225     public void testStaleIfErrorInResponseYieldsToExplicitFreshnessRequest()
226             throws Exception{
227         final Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
228         final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
229         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
230                 "public, max-age=5, stale-if-error=60");
231 
232         backendExpectsAnyRequestAndReturn(resp1);
233 
234         final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
235         req2.setHeader("Cache-Control","min-fresh=2");
236         final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
237 
238         backendExpectsAnyRequestAndReturn(resp2);
239 
240         replayMocks();
241         execute(req1);
242         final ClassicHttpResponse result = execute(req2);
243         verifyMocks();
244 
245         assertTrue(HttpStatus.SC_OK != result.getCode());
246     }
247 
248     @Test
249     public void testStaleIfErrorInRequestIsTrueReturnsStaleEntryWithWarning()
250             throws Exception{
251         final Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
252         final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
253         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
254                 "public, max-age=5");
255 
256         backendExpectsAnyRequestAndReturn(resp1);
257 
258         final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
259         req2.setHeader("Cache-Control","public, stale-if-error=60");
260         final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
261 
262         backendExpectsAnyRequestAndReturn(resp2);
263 
264         replayMocks();
265         execute(req1);
266         final ClassicHttpResponse result = execute(req2);
267         verifyMocks();
268 
269         HttpTestUtils.assert110WarningFound(result);
270     }
271 
272     @Test
273     public void testStaleIfErrorInRequestIsTrueReturnsStaleNonRevalidatableEntryWithWarning()
274         throws Exception {
275         final Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
276         final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
277         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
278         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
279         resp1.setHeader("Cache-Control", "public, max-age=5");
280 
281         backendExpectsAnyRequestAndReturn(resp1);
282 
283         final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
284         req2.setHeader("Cache-Control", "public, stale-if-error=60");
285         final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
286 
287         backendExpectsAnyRequestAndReturn(resp2);
288 
289         replayMocks();
290         execute(req1);
291         final ClassicHttpResponse result = execute(req2);
292         verifyMocks();
293 
294         HttpTestUtils.assert110WarningFound(result);
295     }
296 
297     @Test
298     public void testStaleIfErrorInResponseIsFalseReturnsError()
299             throws Exception{
300         final Date now = new Date();
301         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
302         final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
303         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
304                 "public, max-age=5, stale-if-error=2");
305 
306         backendExpectsAnyRequestAndReturn(resp1);
307 
308         final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
309         final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
310 
311         backendExpectsAnyRequestAndReturn(resp2);
312 
313         replayMocks();
314         execute(req1);
315         final ClassicHttpResponse result = execute(req2);
316         verifyMocks();
317 
318         assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
319                 result.getCode());
320     }
321 
322     @Test
323     public void testStaleIfErrorInRequestIsFalseReturnsError()
324             throws Exception{
325         final Date now = new Date();
326         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
327         final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
328         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
329                 "public, max-age=5");
330 
331         backendExpectsAnyRequestAndReturn(resp1);
332 
333         final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
334         req2.setHeader("Cache-Control","stale-if-error=2");
335         final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
336 
337         backendExpectsAnyRequestAndReturn(resp2);
338 
339         replayMocks();
340         execute(req1);
341         final ClassicHttpResponse result = execute(req2);
342         verifyMocks();
343 
344         assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
345                 result.getCode());
346     }
347 
348     /*
349      * When present in an HTTP response, the stale-while-revalidate Cache-
350      * Control extension indicates that caches MAY serve the response in
351      * which it appears after it becomes stale, up to the indicated number
352      * of seconds.
353      *
354      * http://tools.ietf.org/html/rfc5861
355      */
356     @Test
357     public void testStaleWhileRevalidateReturnsStaleEntryWithWarning()
358         throws Exception {
359         config = CacheConfig.custom()
360                 .setMaxCacheEntries(MAX_ENTRIES)
361                 .setMaxObjectSize(MAX_BYTES)
362                 .setAsynchronousWorkers(1)
363                 .build();
364 
365         impl = new CachingExec(cache, executorService, ImmediateSchedulingStrategy.INSTANCE, config);
366 
367         final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
368         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
369         final Date now = new Date();
370         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
371         resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
372         resp1.setHeader("ETag","\"etag\"");
373         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
374 
375         backendExpectsAnyRequestAndReturn(resp1).times(1,2);
376 
377         final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
378 
379         replayMocks();
380         execute(req1);
381         final ClassicHttpResponse result = execute(req2);
382         verifyMocks();
383 
384         assertEquals(HttpStatus.SC_OK, result.getCode());
385         boolean warning110Found = false;
386         for(final Header h : result.getHeaders("Warning")) {
387             for(final WarningValue wv : WarningValue.getWarningValues(h)) {
388                 if (wv.getWarnCode() == 110) {
389                     warning110Found = true;
390                     break;
391                 }
392             }
393         }
394         assertTrue(warning110Found);
395     }
396 
397     @Test
398     public void testStaleWhileRevalidateReturnsStaleNonRevalidatableEntryWithWarning()
399         throws Exception {
400         config = CacheConfig.custom().setMaxCacheEntries(MAX_ENTRIES).setMaxObjectSize(MAX_BYTES)
401             .setAsynchronousWorkers(1).build();
402 
403         impl = new CachingExec(cache, executorService, ImmediateSchedulingStrategy.INSTANCE, config);
404 
405         final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
406         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
407         final Date now = new Date();
408         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
409         resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
410         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
411 
412         backendExpectsAnyRequestAndReturn(resp1).times(1, 2);
413 
414         final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
415 
416         replayMocks();
417         execute(req1);
418         final ClassicHttpResponse result = execute(req2);
419         verifyMocks();
420 
421         assertEquals(HttpStatus.SC_OK, result.getCode());
422         boolean warning110Found = false;
423         for (final Header h : result.getHeaders("Warning")) {
424             for (final WarningValue wv : WarningValue.getWarningValues(h)) {
425                 if (wv.getWarnCode() == 110) {
426                     warning110Found = true;
427                     break;
428                 }
429             }
430         }
431         assertTrue(warning110Found);
432     }
433 
434     @Test
435     public void testCanAlsoServeStale304sWhileRevalidating()
436         throws Exception {
437 
438         config = CacheConfig.custom()
439                 .setMaxCacheEntries(MAX_ENTRIES)
440                 .setMaxObjectSize(MAX_BYTES)
441                 .setAsynchronousWorkers(1)
442                 .setSharedCache(false)
443                 .build();
444         impl = new CachingExec(cache, executorService, ImmediateSchedulingStrategy.INSTANCE, config);
445 
446         final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
447         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
448         final Date now = new Date();
449         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
450         resp1.setHeader("Cache-Control", "private, stale-while-revalidate=15");
451         resp1.setHeader("ETag","\"etag\"");
452         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
453 
454         backendExpectsAnyRequestAndReturn(resp1).times(1,2);
455 
456         final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
457         req2.setHeader("If-None-Match","\"etag\"");
458 
459         replayMocks();
460         execute(req1);
461         final ClassicHttpResponse result = execute(req2);
462         verifyMocks();
463 
464         assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
465         boolean warning110Found = false;
466         for(final Header h : result.getHeaders("Warning")) {
467             for(final WarningValue wv : WarningValue.getWarningValues(h)) {
468                 if (wv.getWarnCode() == 110) {
469                     warning110Found = true;
470                     break;
471                 }
472             }
473         }
474         assertTrue(warning110Found);
475     }
476 
477 
478     @Test
479     public void testStaleWhileRevalidateYieldsToMustRevalidate()
480         throws Exception {
481 
482         final Date now = new Date();
483         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
484 
485         config = CacheConfig.custom()
486                 .setMaxCacheEntries(MAX_ENTRIES)
487                 .setMaxObjectSize(MAX_BYTES)
488                 .setAsynchronousWorkers(1)
489                 .build();
490         impl = new CachingExec(cache, null, config);
491 
492         final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
493         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
494         resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate");
495         resp1.setHeader("ETag","\"etag\"");
496         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
497 
498         backendExpectsAnyRequestAndReturn(resp1);
499 
500         final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
501         final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
502         resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate");
503         resp2.setHeader("ETag","\"etag\"");
504         resp2.setHeader("Date", DateUtils.formatDate(now));
505 
506         backendExpectsAnyRequestAndReturn(resp2);
507 
508         replayMocks();
509         execute(req1);
510         final ClassicHttpResponse result = execute(req2);
511         verifyMocks();
512 
513         assertEquals(HttpStatus.SC_OK, result.getCode());
514         boolean warning110Found = false;
515         for(final Header h : result.getHeaders("Warning")) {
516             for(final WarningValue wv : WarningValue.getWarningValues(h)) {
517                 if (wv.getWarnCode() == 110) {
518                     warning110Found = true;
519                     break;
520                 }
521             }
522         }
523         assertFalse(warning110Found);
524     }
525 
526     @Test
527     public void testStaleWhileRevalidateYieldsToProxyRevalidateForSharedCache()
528         throws Exception {
529 
530         final Date now = new Date();
531         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
532 
533         config = CacheConfig.custom()
534                 .setMaxCacheEntries(MAX_ENTRIES)
535                 .setMaxObjectSize(MAX_BYTES)
536                 .setAsynchronousWorkers(1)
537                 .setSharedCache(true)
538                 .build();
539         impl = new CachingExec(cache, null, config);
540 
541         final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
542         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
543         resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate");
544         resp1.setHeader("ETag","\"etag\"");
545         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
546 
547         backendExpectsAnyRequestAndReturn(resp1);
548 
549         final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
550         final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
551         resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate");
552         resp2.setHeader("ETag","\"etag\"");
553         resp2.setHeader("Date", DateUtils.formatDate(now));
554 
555         backendExpectsAnyRequestAndReturn(resp2);
556 
557         replayMocks();
558         execute(req1);
559         final ClassicHttpResponse result = execute(req2);
560         verifyMocks();
561 
562         assertEquals(HttpStatus.SC_OK, result.getCode());
563         boolean warning110Found = false;
564         for(final Header h : result.getHeaders("Warning")) {
565             for(final WarningValue wv : WarningValue.getWarningValues(h)) {
566                 if (wv.getWarnCode() == 110) {
567                     warning110Found = true;
568                     break;
569                 }
570             }
571         }
572         assertFalse(warning110Found);
573     }
574 
575     @Test
576     public void testStaleWhileRevalidateYieldsToExplicitFreshnessRequest()
577         throws Exception {
578 
579         final Date now = new Date();
580         final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
581 
582         config = CacheConfig.custom()
583                 .setMaxCacheEntries(MAX_ENTRIES)
584                 .setMaxObjectSize(MAX_BYTES)
585                 .setAsynchronousWorkers(1)
586                 .setSharedCache(true)
587                 .build();
588         impl = new CachingExec(cache, null, config);
589 
590         final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
591         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
592         resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
593         resp1.setHeader("ETag","\"etag\"");
594         resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
595 
596         backendExpectsAnyRequestAndReturn(resp1);
597 
598         final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
599         req2.setHeader("Cache-Control","min-fresh=2");
600         final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
601         resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
602         resp2.setHeader("ETag","\"etag\"");
603         resp2.setHeader("Date", DateUtils.formatDate(now));
604 
605         backendExpectsAnyRequestAndReturn(resp2);
606 
607         replayMocks();
608         execute(req1);
609         final ClassicHttpResponse result = execute(req2);
610         verifyMocks();
611 
612         assertEquals(HttpStatus.SC_OK, result.getCode());
613         boolean warning110Found = false;
614         for(final Header h : result.getHeaders("Warning")) {
615             for(final WarningValue wv : WarningValue.getWarningValues(h)) {
616                 if (wv.getWarnCode() == 110) {
617                     warning110Found = true;
618                     break;
619                 }
620             }
621         }
622         assertFalse(warning110Found);
623     }
624 
625 }