1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.client5.http.impl.cache;
28
29 import java.time.Instant;
30
31 import org.apache.hc.client5.http.cache.HttpCacheEntry;
32 import org.apache.hc.client5.http.utils.DateUtils;
33 import org.apache.hc.core5.http.Header;
34 import org.apache.hc.core5.http.HttpHost;
35 import org.apache.hc.core5.http.HttpRequest;
36 import org.apache.hc.core5.http.message.BasicHeader;
37 import org.apache.hc.core5.http.message.BasicHttpRequest;
38 import org.apache.hc.core5.util.TimeValue;
39 import org.junit.jupiter.api.Assertions;
40 import org.junit.jupiter.api.BeforeEach;
41 import org.junit.jupiter.api.Test;
42
43 public class TestCachedResponseSuitabilityChecker {
44
45 private Instant now;
46 private Instant elevenSecondsAgo;
47 private Instant tenSecondsAgo;
48 private Instant nineSecondsAgo;
49
50 private HttpHost host;
51 private HttpRequest request;
52 private HttpCacheEntry entry;
53 private CachedResponseSuitabilityChecker impl;
54
55 @BeforeEach
56 public void setUp() {
57 now = Instant.now();
58 elevenSecondsAgo = now.minusSeconds(11);
59 tenSecondsAgo = now.minusSeconds(10);
60 nineSecondsAgo = now.minusSeconds(9);
61
62 host = new HttpHost("foo.example.com");
63 request = new BasicHttpRequest("GET", "/foo");
64 entry = HttpTestUtils.makeCacheEntry();
65
66 impl = new CachedResponseSuitabilityChecker(CacheConfig.DEFAULT);
67 }
68
69 private HttpCacheEntry getEntry(final Header[] headers) {
70 return HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, headers);
71 }
72
73 @Test
74 public void testNotSuitableIfContentLengthHeaderIsWrong() {
75 final Header[] headers = {
76 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
77 new BasicHeader("Cache-Control", "max-age=3600"),
78 new BasicHeader("Content-Length","1")
79 };
80 entry = getEntry(headers);
81 Assertions.assertFalse(impl.canCachedResponseBeUsed(host, request, entry, now));
82 }
83
84 @Test
85 public void testSuitableIfCacheEntryIsFresh() {
86 final Header[] headers = {
87 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
88 new BasicHeader("Cache-Control", "max-age=3600"),
89 new BasicHeader("Content-Length","128")
90 };
91 entry = getEntry(headers);
92 Assertions.assertTrue(impl.canCachedResponseBeUsed(host, request, entry, now));
93 }
94
95 @Test
96 public void testNotSuitableIfCacheEntryIsNotFresh() {
97 final Header[] headers = {
98 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
99 new BasicHeader("Cache-Control", "max-age=5"),
100 new BasicHeader("Content-Length","128")
101 };
102 entry = getEntry(headers);
103 Assertions.assertFalse(impl.canCachedResponseBeUsed(host, request, entry, now));
104 }
105
106 @Test
107 public void testNotSuitableIfRequestHasNoCache() {
108 request.addHeader("Cache-Control", "no-cache");
109 final Header[] headers = {
110 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
111 new BasicHeader("Cache-Control", "max-age=3600"),
112 new BasicHeader("Content-Length","128")
113 };
114 entry = getEntry(headers);
115 Assertions.assertFalse(impl.canCachedResponseBeUsed(host, request, entry, now));
116 }
117
118 @Test
119 public void testNotSuitableIfAgeExceedsRequestMaxAge() {
120 request.addHeader("Cache-Control", "max-age=10");
121 final Header[] headers = {
122 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
123 new BasicHeader("Cache-Control", "max-age=3600"),
124 new BasicHeader("Content-Length","128")
125 };
126 entry = getEntry(headers);
127 Assertions.assertFalse(impl.canCachedResponseBeUsed(host, request, entry, now));
128 }
129
130 @Test
131 public void testSuitableIfFreshAndAgeIsUnderRequestMaxAge() {
132 request.addHeader("Cache-Control", "max-age=15");
133 final Header[] headers = {
134 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
135 new BasicHeader("Cache-Control", "max-age=3600"),
136 new BasicHeader("Content-Length","128")
137 };
138 entry = getEntry(headers);
139 Assertions.assertTrue(impl.canCachedResponseBeUsed(host, request, entry, now));
140 }
141
142 @Test
143 public void testSuitableIfFreshAndFreshnessLifetimeGreaterThanRequestMinFresh() {
144 request.addHeader("Cache-Control", "min-fresh=10");
145 final Header[] headers = {
146 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
147 new BasicHeader("Cache-Control", "max-age=3600"),
148 new BasicHeader("Content-Length","128")
149 };
150 entry = getEntry(headers);
151 Assertions.assertTrue(impl.canCachedResponseBeUsed(host, request, entry, now));
152 }
153
154 @Test
155 public void testNotSuitableIfFreshnessLifetimeLessThanRequestMinFresh() {
156 request.addHeader("Cache-Control", "min-fresh=10");
157 final Header[] headers = {
158 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
159 new BasicHeader("Cache-Control", "max-age=15"),
160 new BasicHeader("Content-Length","128")
161 };
162 entry = getEntry(headers);
163 Assertions.assertFalse(impl.canCachedResponseBeUsed(host, request, entry, now));
164 }
165
166 @Test
167 public void testSuitableEvenIfStaleButPermittedByRequestMaxStale() {
168 request.addHeader("Cache-Control", "max-stale=10");
169 final Header[] headers = {
170 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
171 new BasicHeader("Cache-Control", "max-age=5"),
172 new BasicHeader("Content-Length","128")
173 };
174 entry = getEntry(headers);
175 Assertions.assertTrue(impl.canCachedResponseBeUsed(host, request, entry, now));
176 }
177
178 @Test
179 public void testNotSuitableIfStaleButTooStaleForRequestMaxStale() {
180 request.addHeader("Cache-Control", "max-stale=2");
181 final Header[] headers = {
182 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
183 new BasicHeader("Cache-Control", "max-age=5"),
184 new BasicHeader("Content-Length","128")
185 };
186 entry = getEntry(headers);
187 Assertions.assertFalse(impl.canCachedResponseBeUsed(host, request, entry, now));
188 }
189
190
191 @Test
192 public void testMalformedCacheControlMaxAgeRequestHeaderCausesUnsuitableEntry() {
193 request.addHeader(new BasicHeader("Cache-Control", "max-age=foo"));
194 final Header[] headers = {
195 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
196 new BasicHeader("Cache-Control", "max-age=3600"),
197 new BasicHeader("Content-Length","128")
198 };
199 entry = getEntry(headers);
200 Assertions.assertFalse(impl.canCachedResponseBeUsed(host, request, entry, now));
201 }
202
203 @Test
204 public void testMalformedCacheControlMinFreshRequestHeaderCausesUnsuitableEntry() {
205 request.addHeader(new BasicHeader("Cache-Control", "min-fresh=foo"));
206 final Header[] headers = {
207 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
208 new BasicHeader("Cache-Control", "max-age=3600"),
209 new BasicHeader("Content-Length","128")
210 };
211 entry = getEntry(headers);
212 Assertions.assertFalse(impl.canCachedResponseBeUsed(host, request, entry, now));
213 }
214
215 @Test
216 public void testSuitableIfCacheEntryIsHeuristicallyFreshEnough() {
217 final Instant oneSecondAgo = now.minusSeconds(1);
218 final Instant twentyOneSecondsAgo = now.minusSeconds(21);
219
220 final Header[] headers = {
221 new BasicHeader("Date", DateUtils.formatStandardDate(oneSecondAgo)),
222 new BasicHeader("Last-Modified", DateUtils.formatStandardDate(twentyOneSecondsAgo)),
223 new BasicHeader("Content-Length", "128")
224 };
225
226 entry = HttpTestUtils.makeCacheEntry(oneSecondAgo, oneSecondAgo, headers);
227
228 final CacheConfig config = CacheConfig.custom()
229 .setHeuristicCachingEnabled(true)
230 .setHeuristicCoefficient(0.1f).build();
231 impl = new CachedResponseSuitabilityChecker(config);
232
233 Assertions.assertTrue(impl.canCachedResponseBeUsed(host, request, entry, now));
234 }
235
236 @Test
237 public void testSuitableIfCacheEntryIsHeuristicallyFreshEnoughByDefault() {
238 final Header[] headers = {
239 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
240 new BasicHeader("Content-Length", "128")
241 };
242
243 entry = getEntry(headers);
244
245 final CacheConfig config = CacheConfig.custom()
246 .setHeuristicCachingEnabled(true)
247 .setHeuristicDefaultLifetime(TimeValue.ofSeconds(20L))
248 .build();
249 impl = new CachedResponseSuitabilityChecker(config);
250
251 Assertions.assertTrue(impl.canCachedResponseBeUsed(host, request, entry, now));
252 }
253
254 @Test
255 public void testSuitableIfRequestMethodisHEAD() {
256 final HttpRequest headRequest = new BasicHttpRequest("HEAD", "/foo");
257 final Header[] headers = {
258 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
259 new BasicHeader("Cache-Control", "max-age=3600"),
260 new BasicHeader("Content-Length","128")
261 };
262 entry = getEntry(headers);
263
264 Assertions.assertTrue(impl.canCachedResponseBeUsed(host, headRequest, entry, now));
265 }
266
267 @Test
268 public void testNotSuitableIfRequestMethodIsGETAndEntryResourceIsNull() {
269 final Header[] headers = {
270 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
271 new BasicHeader("Cache-Control", "max-age=3600"),
272 new BasicHeader("Content-Length","128")
273 };
274 entry = HttpTestUtils.makeHeadCacheEntry(headers);
275
276 Assertions.assertFalse(impl.canCachedResponseBeUsed(host, request, entry, now));
277 }
278
279 @Test
280 public void testNotSuitableForGETIfEntryDoesNotSpecifyARequestMethodOrEntity() {
281 impl = new CachedResponseSuitabilityChecker(CacheConfig.custom().build());
282 final Header[] headers = {
283 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
284 new BasicHeader("Cache-Control", "max-age=3600"),
285 new BasicHeader("Content-Length","128")
286 };
287 entry = HttpTestUtils.makeCacheEntryWithNoRequestMethodOrEntity(headers);
288
289 Assertions.assertFalse(impl.canCachedResponseBeUsed(host, request, entry, now));
290 }
291
292 @Test
293 public void testSuitableForGETIfEntryDoesNotSpecifyARequestMethodButContainsEntity() {
294 impl = new CachedResponseSuitabilityChecker(CacheConfig.custom().build());
295 final Header[] headers = {
296 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
297 new BasicHeader("Cache-Control", "max-age=3600"),
298 new BasicHeader("Content-Length","128")
299 };
300 entry = HttpTestUtils.makeCacheEntryWithNoRequestMethod(headers);
301
302 Assertions.assertTrue(impl.canCachedResponseBeUsed(host, request, entry, now));
303 }
304
305 @Test
306 public void testSuitableForGETIfHeadResponseCachingEnabledAndEntryDoesNotSpecifyARequestMethodButContains204Response() {
307 impl = new CachedResponseSuitabilityChecker(CacheConfig.custom().build());
308 final Header[] headers = {
309 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
310 new BasicHeader("Cache-Control", "max-age=3600")
311 };
312 entry = HttpTestUtils.make204CacheEntryWithNoRequestMethod(headers);
313
314 Assertions.assertTrue(impl.canCachedResponseBeUsed(host, request, entry, now));
315 }
316
317 @Test
318 public void testSuitableForHEADIfHeadResponseCachingEnabledAndEntryDoesNotSpecifyARequestMethod() {
319 final HttpRequest headRequest = new BasicHttpRequest("HEAD", "/foo");
320 impl = new CachedResponseSuitabilityChecker(CacheConfig.custom().build());
321 final Header[] headers = {
322 new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
323 new BasicHeader("Cache-Control", "max-age=3600"),
324 new BasicHeader("Content-Length","128")
325 };
326 entry = HttpTestUtils.makeHeadCacheEntryWithNoRequestMethod(headers);
327
328 Assertions.assertTrue(impl.canCachedResponseBeUsed(host, headRequest, entry, now));
329 }
330 }