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