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.assertSame;
32  import static org.junit.Assert.assertTrue;
33  
34  import java.util.Date;
35  
36  import org.apache.hc.client5.http.cache.HttpCacheEntry;
37  import org.apache.hc.client5.http.utils.DateUtils;
38  import org.apache.hc.core5.http.Header;
39  import org.apache.hc.core5.http.HttpHeaders;
40  import org.apache.hc.core5.http.HttpRequest;
41  import org.apache.hc.core5.http.message.BasicHeader;
42  import org.apache.hc.core5.http.message.BasicHttpRequest;
43  import org.apache.hc.core5.util.TimeValue;
44  import org.junit.Before;
45  import org.junit.Test;
46  
47  public class TestCacheValidityPolicy {
48  
49      private CacheValidityPolicy impl;
50      private Date now;
51      private Date oneSecondAgo;
52      private Date sixSecondsAgo;
53      private Date tenSecondsAgo;
54      private Date elevenSecondsAgo;
55  
56      @Before
57      public void setUp() {
58          impl = new CacheValidityPolicy();
59          now = new Date();
60          oneSecondAgo = new Date(now.getTime() - 1 * 1000L);
61          sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
62          tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
63          elevenSecondsAgo = new Date(now.getTime() - 11 * 1000L);
64      }
65  
66      @Test
67      public void testApparentAgeIsMaxIntIfDateHeaderNotPresent() {
68          final Header[] headers = {
69                  new BasicHeader("Server", "MockServer/1.0")
70          };
71          final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
72          assertEquals(CacheValidityPolicy.MAX_AGE, impl.getApparentAge(entry));
73      }
74  
75      @Test
76      public void testApparentAgeIsResponseReceivedTimeLessDateHeader() {
77          final Header[] headers = new Header[] { new BasicHeader("Date", DateUtils
78                  .formatDate(tenSecondsAgo)) };
79  
80          final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo, headers);
81          assertEquals(TimeValue.ofSeconds(4), impl.getApparentAge(entry));
82      }
83  
84      @Test
85      public void testNegativeApparentAgeIsBroughtUpToZero() {
86          final Header[] headers = new Header[] { new BasicHeader("Date", DateUtils
87                  .formatDate(sixSecondsAgo)) };
88          final HttpCacheEntry entry  = HttpTestUtils.makeCacheEntry(now, tenSecondsAgo, headers);
89          assertEquals(TimeValue.ofSeconds(0), impl.getApparentAge(entry));
90      }
91  
92      @Test
93      public void testCorrectedReceivedAgeIsAgeHeaderIfLarger() {
94          final Header[] headers = new Header[] { new BasicHeader("Age", "10"), };
95          final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
96          impl = new CacheValidityPolicy() {
97              @Override
98              protected TimeValue getApparentAge(final HttpCacheEntry ent) {
99                  return TimeValue.ofSeconds(6);
100             }
101         };
102         assertEquals(TimeValue.ofSeconds(10), impl.getCorrectedReceivedAge(entry));
103     }
104 
105     @Test
106     public void testCorrectedReceivedAgeIsApparentAgeIfLarger() {
107         final Header[] headers = new Header[] { new BasicHeader("Age", "6"), };
108         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
109         impl = new CacheValidityPolicy() {
110             @Override
111             protected TimeValue getApparentAge(final HttpCacheEntry ent) {
112                 return TimeValue.ofSeconds(10);
113             }
114         };
115         assertEquals(TimeValue.ofSeconds(10), impl.getCorrectedReceivedAge(entry));
116     }
117 
118     @Test
119     public void testResponseDelayIsDifferenceBetweenResponseAndRequestTimes() {
120         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, sixSecondsAgo);
121         assertEquals(TimeValue.ofSeconds(4), impl.getResponseDelay(entry));
122     }
123 
124     @Test
125     public void testCorrectedInitialAgeIsCorrectedReceivedAgePlusResponseDelay() {
126         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
127         impl = new CacheValidityPolicy() {
128             @Override
129             protected TimeValue getCorrectedReceivedAge(final HttpCacheEntry ent) {
130                 return TimeValue.ofSeconds(7);
131             }
132 
133             @Override
134             protected TimeValue getResponseDelay(final HttpCacheEntry ent) {
135                 return TimeValue.ofSeconds(13);
136             }
137         };
138         assertEquals(TimeValue.ofSeconds(20), impl.getCorrectedInitialAge(entry));
139     }
140 
141     @Test
142     public void testResidentTimeSecondsIsTimeSinceResponseTime() {
143         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo);
144         assertEquals(TimeValue.ofSeconds(6), impl.getResidentTime(entry, now));
145     }
146 
147     @Test
148     public void testCurrentAgeIsCorrectedInitialAgePlusResidentTime() {
149         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
150         impl = new CacheValidityPolicy() {
151             @Override
152             protected TimeValue getCorrectedInitialAge(final HttpCacheEntry ent) {
153                 return TimeValue.ofSeconds(11);
154             }
155             @Override
156             protected TimeValue getResidentTime(final HttpCacheEntry ent, final Date d) {
157                 return TimeValue.ofSeconds(17);
158             }
159         };
160         assertEquals(TimeValue.ofSeconds(28), impl.getCurrentAge(entry, new Date()));
161     }
162 
163     @Test
164     public void testFreshnessLifetimeIsSMaxAgeIfPresent() {
165         final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10") };
166         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
167         assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
168     }
169 
170     @Test
171     public void testFreshnessLifetimeIsMaxAgeIfPresent() {
172         final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10") };
173         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
174         assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
175     }
176 
177     @Test
178     public void testFreshnessLifetimeIsMostRestrictiveOfMaxAgeAndSMaxAge() {
179         Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"),
180                 new BasicHeader("Cache-Control", "s-maxage=20") };
181         HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
182         assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
183 
184         headers = new Header[] { new BasicHeader("Cache-Control", "max-age=20"),
185                 new BasicHeader("Cache-Control", "s-maxage=10") };
186         entry = HttpTestUtils.makeCacheEntry(headers);
187         assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
188     }
189 
190     @Test
191     public void testFreshnessLifetimeIsMaxAgeEvenIfExpiresIsPresent() {
192         final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"),
193                 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
194                 new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
195 
196         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
197         assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
198     }
199 
200     @Test
201     public void testFreshnessLifetimeIsSMaxAgeEvenIfExpiresIsPresent() {
202         final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10"),
203                 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
204                 new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
205 
206         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
207         assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
208     }
209 
210     @Test
211     public void testFreshnessLifetimeIsFromExpiresHeaderIfNoMaxAge() {
212         final Header[] headers = new Header[] {
213                 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
214                 new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
215 
216         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
217         assertEquals(TimeValue.ofSeconds(4), impl.getFreshnessLifetime(entry));
218     }
219 
220     @Test
221     public void testHeuristicFreshnessLifetime() {
222         final Header[] headers = new Header[] {
223                 new BasicHeader("Date", DateUtils.formatDate(oneSecondAgo)),
224                 new BasicHeader("Last-Modified", DateUtils.formatDate(elevenSecondsAgo))
225         };
226         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
227         assertEquals(TimeValue.ofSeconds(1), impl.getHeuristicFreshnessLifetime(entry, 0.1f, TimeValue.ZERO_MILLISECONDS));
228     }
229 
230     @Test
231     public void testHeuristicFreshnessLifetimeDefaultsProperly() {
232         final TimeValue defaultFreshness = TimeValue.ofSeconds(10);
233         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
234         assertEquals(defaultFreshness, impl.getHeuristicFreshnessLifetime(entry, 0.1f, defaultFreshness));
235     }
236 
237     @Test
238     public void testHeuristicFreshnessLifetimeIsNonNegative() {
239         final Header[] headers = new Header[] {
240                 new BasicHeader("Date", DateUtils.formatDate(elevenSecondsAgo)),
241                 new BasicHeader("Last-Modified", DateUtils.formatDate(oneSecondAgo))
242         };
243 
244         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
245         assertTrue(TimeValue.isNonNegative(impl.getHeuristicFreshnessLifetime(entry, 0.1f, TimeValue.ofSeconds(10))));
246     }
247 
248     @Test
249     public void testResponseIsFreshIfFreshnessLifetimeExceedsCurrentAge() {
250         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
251         impl = new CacheValidityPolicy() {
252             @Override
253             public TimeValue getCurrentAge(final HttpCacheEntry e, final Date d) {
254                 assertSame(entry, e);
255                 assertEquals(now, d);
256                 return TimeValue.ofSeconds(6);
257             }
258             @Override
259             public TimeValue getFreshnessLifetime(final HttpCacheEntry e) {
260                 assertSame(entry, e);
261                 return TimeValue.ofSeconds(10);
262             }
263         };
264         assertTrue(impl.isResponseFresh(entry, now));
265     }
266 
267     @Test
268     public void testResponseIsNotFreshIfFreshnessLifetimeEqualsCurrentAge() {
269         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
270         impl = new CacheValidityPolicy() {
271             @Override
272             public TimeValue getCurrentAge(final HttpCacheEntry e, final Date d) {
273                 assertEquals(now, d);
274                 assertSame(entry, e);
275                 return TimeValue.ofSeconds(6);
276             }
277             @Override
278             public TimeValue getFreshnessLifetime(final HttpCacheEntry e) {
279                 assertSame(entry, e);
280                 return TimeValue.ofSeconds(6);
281             }
282         };
283         assertFalse(impl.isResponseFresh(entry, now));
284     }
285 
286     @Test
287     public void testResponseIsNotFreshIfCurrentAgeExceedsFreshnessLifetime() {
288         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
289         impl = new CacheValidityPolicy() {
290             @Override
291             public TimeValue getCurrentAge(final HttpCacheEntry e, final Date d) {
292                 assertEquals(now, d);
293                 assertSame(entry, e);
294                 return TimeValue.ofSeconds(10);
295             }
296             @Override
297             public TimeValue getFreshnessLifetime(final HttpCacheEntry e) {
298                 assertSame(entry, e);
299                 return TimeValue.ofSeconds(6);
300             }
301         };
302         assertFalse(impl.isResponseFresh(entry, now));
303     }
304 
305     @Test
306     public void testCacheEntryIsRevalidatableIfHeadersIncludeETag() {
307         final Header[] headers = {
308                 new BasicHeader("Expires", DateUtils.formatDate(new Date())),
309                 new BasicHeader("ETag", "somevalue")};
310         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
311         assertTrue(impl.isRevalidatable(entry));
312     }
313 
314     @Test
315     public void testCacheEntryIsRevalidatableIfHeadersIncludeLastModifiedDate() {
316         final Header[] headers = {
317                 new BasicHeader("Expires", DateUtils.formatDate(new Date())),
318                 new BasicHeader("Last-Modified", DateUtils.formatDate(new Date())) };
319         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
320         assertTrue(impl.isRevalidatable(entry));
321     }
322 
323     @Test
324     public void testCacheEntryIsNotRevalidatableIfNoAppropriateHeaders() {
325         final Header[] headers =  {
326                 new BasicHeader("Expires", DateUtils.formatDate(new Date())),
327                 new BasicHeader("Cache-Control", "public") };
328         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
329         assertFalse(impl.isRevalidatable(entry));
330     }
331 
332     @Test
333     public void testMissingContentLengthDoesntInvalidateEntry() {
334         final int contentLength = 128;
335         final Header[] headers = {}; // no Content-Length header
336         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers, HttpTestUtils.getRandomBytes(contentLength));
337         assertTrue(impl.contentLengthHeaderMatchesActualLength(entry));
338     }
339 
340     @Test
341     public void testCorrectContentLengthDoesntInvalidateEntry() {
342         final int contentLength = 128;
343         final Header[] headers = { new BasicHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(contentLength)) };
344         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers, HttpTestUtils.getRandomBytes(contentLength));
345         assertTrue(impl.contentLengthHeaderMatchesActualLength(entry));
346     }
347 
348     @Test
349     public void testWrongContentLengthInvalidatesEntry() {
350         final int contentLength = 128;
351         final Header[] headers = {new BasicHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(contentLength+1))};
352         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers, HttpTestUtils.getRandomBytes(contentLength));
353         assertFalse(impl.contentLengthHeaderMatchesActualLength(entry));
354     }
355 
356     @Test
357     public void testNullResourceInvalidatesEntry() {
358         final int contentLength = 128;
359         final Header[] headers = {new BasicHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(contentLength))};
360         final HttpCacheEntry entry = HttpTestUtils.makeHeadCacheEntry(headers);
361         assertFalse(impl.contentLengthHeaderMatchesActualLength(entry));
362     }
363 
364     @Test
365     public void testNegativeAgeHeaderValueReturnsMaxAge() {
366         final Header[] headers = new Header[] { new BasicHeader("Age", "-100") };
367         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
368         // in seconds
369         assertEquals(CacheValidityPolicy.MAX_AGE.toSeconds(), impl.getAgeValue(entry));
370     }
371 
372     @Test
373     public void testMalformedAgeHeaderValueReturnsMaxAge() {
374         final Header[] headers = new Header[] { new BasicHeader("Age", "asdf") };
375         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
376         // in seconds
377         assertEquals(CacheValidityPolicy.MAX_AGE.toSeconds(), impl.getAgeValue(entry));
378     }
379 
380     @Test
381     public void testMalformedCacheControlMaxAgeHeaderReturnsZero() {
382         final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=asdf") };
383         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
384         // in seconds
385         assertEquals(0L, impl.getMaxAge(entry));
386     }
387 
388     @Test
389     public void testMustRevalidateIsFalseIfDirectiveNotPresent() {
390         final Header[] headers = new Header[] { new BasicHeader("Cache-Control","public") };
391         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
392         assertFalse(impl.mustRevalidate(entry));
393     }
394 
395     @Test
396     public void testMustRevalidateIsTrueWhenDirectiveIsPresent() {
397         final Header[] headers = new Header[] { new BasicHeader("Cache-Control","public, must-revalidate") };
398         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
399         assertTrue(impl.mustRevalidate(entry));
400     }
401 
402     @Test
403     public void testProxyRevalidateIsFalseIfDirectiveNotPresent() {
404         final Header[] headers = new Header[] { new BasicHeader("Cache-Control","public") };
405         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
406         assertFalse(impl.proxyRevalidate(entry));
407     }
408 
409     @Test
410     public void testProxyRevalidateIsTrueWhenDirectiveIsPresent() {
411         final Header[] headers = new Header[] { new BasicHeader("Cache-Control","public, proxy-revalidate") };
412         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
413         assertTrue(impl.proxyRevalidate(entry));
414     }
415 
416     @Test
417     public void testMayReturnStaleIfErrorInResponseIsTrueWithinStaleness(){
418         final Header[] headers = new Header[] {
419                 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
420                 new BasicHeader("Cache-Control", "max-age=5, stale-if-error=15")
421         };
422         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
423         final HttpRequest req = new BasicHttpRequest("GET","/");
424         assertTrue(impl.mayReturnStaleIfError(req, entry, now));
425     }
426 
427     @Test
428     public void testMayReturnStaleIfErrorInRequestIsTrueWithinStaleness(){
429         final Header[] headers = new Header[] {
430                 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
431                 new BasicHeader("Cache-Control", "max-age=5")
432         };
433         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
434         final HttpRequest req = new BasicHttpRequest("GET","/");
435         req.setHeader("Cache-Control","stale-if-error=15");
436         assertTrue(impl.mayReturnStaleIfError(req, entry, now));
437     }
438 
439     @Test
440     public void testMayNotReturnStaleIfErrorInResponseAndAfterResponseWindow(){
441         final Header[] headers = new Header[] {
442                 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
443                 new BasicHeader("Cache-Control", "max-age=5, stale-if-error=1")
444         };
445         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
446         final HttpRequest req = new BasicHttpRequest("GET","/");
447         assertFalse(impl.mayReturnStaleIfError(req, entry, now));
448     }
449 
450     @Test
451     public void testMayNotReturnStaleIfErrorInResponseAndAfterRequestWindow(){
452         final Header[] headers = new Header[] {
453                 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
454                 new BasicHeader("Cache-Control", "max-age=5")
455         };
456         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
457         final HttpRequest req = new BasicHttpRequest("GET","/");
458         req.setHeader("Cache-Control","stale-if-error=1");
459         assertFalse(impl.mayReturnStaleIfError(req, entry, now));
460     }
461 
462     @Test
463     public void testMayReturnStaleWhileRevalidatingIsFalseWhenDirectiveIsAbsent() {
464         final Header[] headers = new Header[] { new BasicHeader("Cache-control", "public") };
465         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
466 
467         assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
468     }
469 
470     @Test
471     public void testMayReturnStaleWhileRevalidatingIsTrueWhenWithinStaleness() {
472         final Header[] headers = new Header[] {
473                 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
474                 new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=15")
475         };
476         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
477 
478         assertTrue(impl.mayReturnStaleWhileRevalidating(entry, now));
479     }
480 
481     @Test
482     public void testMayReturnStaleWhileRevalidatingIsFalseWhenPastStaleness() {
483         final Date twentyFiveSecondsAgo = new Date(now.getTime() - 25 * 1000L);
484         final Header[] headers = new Header[] {
485                 new BasicHeader("Date", DateUtils.formatDate(twentyFiveSecondsAgo)),
486                 new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=15")
487         };
488         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
489 
490         assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
491     }
492 
493     @Test
494     public void testMayReturnStaleWhileRevalidatingIsFalseWhenDirectiveEmpty() {
495         final Header[] headers = new Header[] {
496                 new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
497                 new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=")
498         };
499         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
500 
501         assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
502     }
503 }