1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit;
16
17 import static org.apache.http.client.utils.DateUtils.formatDate;
18 import static org.htmlunit.HttpHeader.CACHE_CONTROL;
19 import static org.htmlunit.HttpHeader.ETAG;
20 import static org.htmlunit.HttpHeader.EXPIRES;
21 import static org.htmlunit.HttpHeader.IF_MODIFIED_SINCE;
22 import static org.htmlunit.HttpHeader.IF_NONE_MATCH;
23 import static org.htmlunit.HttpHeader.LAST_MODIFIED;
24
25 import java.net.URL;
26 import java.text.SimpleDateFormat;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.Date;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33
34 import org.apache.commons.lang3.time.DateUtils;
35 import org.htmlunit.html.HtmlPage;
36 import org.htmlunit.http.HttpStatus;
37 import org.htmlunit.junit.annotation.Alerts;
38 import org.htmlunit.util.MimeType;
39 import org.htmlunit.util.NameValuePair;
40 import org.htmlunit.util.mocks.WebResponseMock;
41 import org.junit.jupiter.api.Test;
42
43
44
45
46
47
48
49
50
51
52
53
54 public class CacheTest extends SimpleWebTestCase {
55
56 private static final long ONE_MINUTE = 60_000L;
57 private static final long ONE_HOUR = ONE_MINUTE * 60;
58
59 private final long now_ = new Date().getTime();
60 private final String tomorrow_ = formatDate(DateUtils.addDays(new Date(), 1));
61
62
63
64
65 @Test
66 public void isCacheableContent() {
67 final Cache cache = new Cache();
68 final Map<String, String> headers = new HashMap<>();
69 final WebResponse response = new WebResponseMock(null, headers);
70
71 assertFalse(cache.isCacheableContent(response));
72
73 headers.put(LAST_MODIFIED, "Sun, 15 Jul 2007 20:46:27 GMT");
74 assertTrue(cache.isCacheableContent(response));
75
76 headers.put(LAST_MODIFIED, formatDate(DateUtils.addMinutes(new Date(), -5)));
77 assertTrue(cache.isCacheableContent(response));
78
79 headers.put(LAST_MODIFIED, formatDate(new Date()));
80 assertFalse(cache.isCacheableContent(response));
81
82 headers.put(LAST_MODIFIED, formatDate(DateUtils.addMinutes(new Date(), 10)));
83 assertFalse(cache.isCacheableContent(response));
84
85 headers.put(EXPIRES, formatDate(DateUtils.addMinutes(new Date(), 5)));
86 assertFalse(cache.isCacheableContent(response));
87
88 headers.put(EXPIRES, formatDate(DateUtils.addHours(new Date(), 1)));
89 assertTrue(cache.isCacheableContent(response));
90
91 headers.remove(LAST_MODIFIED);
92 assertTrue(cache.isCacheableContent(response));
93
94 headers.put(EXPIRES, "0");
95 assertFalse(cache.isCacheableContent(response));
96
97 headers.put(EXPIRES, "-1");
98 assertFalse(cache.isCacheableContent(response));
99
100 headers.put(CACHE_CONTROL, "no-store");
101 assertFalse(cache.isCacheableContent(response));
102 }
103
104
105
106
107 @Test
108 public void contentWithNoHeadersIsNotCached() {
109 assertFalse(Cache.isWithinCacheWindow(new WebResponseMock(null, null), now_, now_));
110 }
111
112
113
114
115 @Test
116 public void contentWithExpiryDateIsCached() {
117 final Map<String, String> headers = new HashMap<>();
118 headers.put(EXPIRES, tomorrow_);
119
120 assertTrue(Cache.isWithinCacheWindow(new WebResponseMock(null, headers), now_, now_));
121 }
122
123
124
125
126 @Test
127 public void contentWithExpiryDateInFutureButShortMaxAgeIsNotInCacheWindow() {
128 final Map<String, String> headers = new HashMap<>();
129 headers.put(EXPIRES, tomorrow_);
130
131 headers.put(CACHE_CONTROL, "some-other-value, max-age=1");
132
133 assertFalse(Cache.isWithinCacheWindow(new WebResponseMock(null, headers), now_ + ONE_MINUTE, now_));
134 }
135
136
137
138
139 @Test
140 public void contentWithExpiryDateInFutureButShortSMaxAgeIsNotInCacheWindow() {
141 final Map<String, String> headers = new HashMap<>();
142 headers.put(EXPIRES, tomorrow_);
143
144 headers.put(CACHE_CONTROL, "some-other-value, s-maxage=1");
145
146 assertFalse(Cache.isWithinCacheWindow(new WebResponseMock(null, headers), now_ + ONE_MINUTE, now_));
147 }
148
149
150
151
152 @Test
153 public void contentWithBothMaxAgeAndSMaxUsesSMaxAsPriority() {
154 final Map<String, String> headers = new HashMap<>();
155 headers.put(CACHE_CONTROL, "some-other-value, max-age=1200, s-maxage=1");
156
157 assertFalse(Cache.isWithinCacheWindow(new WebResponseMock(null, headers), now_ + ONE_MINUTE, now_));
158 }
159
160
161
162
163 @Test
164 public void contentWithMaxAgeInFutureWillBeCached() {
165 final Map<String, String> headers = new HashMap<>();
166 headers.put(CACHE_CONTROL, "some-other-value, max-age=1200");
167
168 assertTrue(Cache.isWithinCacheWindow(new WebResponseMock(null, headers), now_, now_));
169
170 headers.clear();
171 headers.put(CACHE_CONTROL, "some-other-value, max-age=1200");
172
173 assertTrue(Cache.isWithinCacheWindow(new WebResponseMock(null, headers), now_ + ONE_MINUTE, now_));
174 }
175
176
177
178
179 @Test
180 public void contentWithLongLastModifiedTimeComparedToNowIsCachedOnDownload() {
181 final Map<String, String> headers = new HashMap<>();
182 headers.put(LAST_MODIFIED, formatDate(DateUtils.addDays(new Date(), -1)));
183
184 assertTrue(Cache.isWithinCacheWindow(new WebResponseMock(null, headers), now_, now_));
185 }
186
187
188
189
190 @Test
191 public void contentWithLastModifiedTimeIsCachedAfterAFewPercentOfCreationAge() {
192 final Map<String, String> headers = new HashMap<>();
193 headers.put(LAST_MODIFIED, formatDate(DateUtils.addDays(new Date(), -1)));
194
195 assertTrue(Cache.isWithinCacheWindow(new WebResponseMock(null, headers), now_ + ONE_HOUR, now_));
196 }
197
198
199
200
201 @Test
202 public void contentWithLastModifiedTimeIsNotCachedAfterALongerPeriod() {
203 final Map<String, String> headers = new HashMap<>();
204 headers.put(LAST_MODIFIED, formatDate(DateUtils.addDays(new Date(), -1)));
205
206 assertFalse(Cache.isWithinCacheWindow(new WebResponseMock(null, headers), now_ + (ONE_HOUR * 5), now_));
207 }
208
209
210
211
212 @Test
213 public void usage() throws Exception {
214 final String content = DOCTYPE_HTML
215 + "<html><head><title>page 1</title>\n"
216 + "<script src='foo1.js'></script>\n"
217 + "<script src='foo2.js'></script>\n"
218 + "</head><body>\n"
219 + "<a href='page2.html'>to page 2</a>\n"
220 + "</body></html>";
221
222 final String content2 = DOCTYPE_HTML
223 + "<html><head><title>page 2</title>\n"
224 + "<script src='foo2.js'></script>\n"
225 + "</head><body>\n"
226 + "<a href='page1.html'>to page 1</a>\n"
227 + "</body></html>";
228
229 final String script1 = "alert('in foo1');";
230 final String script2 = "alert('in foo2');";
231
232 final WebClient webClient = getWebClient();
233 final MockWebConnection connection = new MockWebConnection();
234 webClient.setWebConnection(connection);
235
236 final URL urlPage1 = new URL(URL_FIRST, "page1.html");
237 connection.setResponse(urlPage1, content);
238 final URL urlPage2 = new URL(URL_FIRST, "page2.html");
239 connection.setResponse(urlPage2, content2);
240
241 final List<NameValuePair> headers = new ArrayList<>();
242 headers.add(new NameValuePair(LAST_MODIFIED, "Sun, 15 Jul 2007 20:46:27 GMT"));
243 connection.setResponse(new URL(URL_FIRST, "foo1.js"), script1, 200, "ok",
244 MimeType.TEXT_JAVASCRIPT, headers);
245 connection.setResponse(new URL(URL_FIRST, "foo2.js"), script2, 200, "ok",
246 MimeType.TEXT_JAVASCRIPT, headers);
247
248 final List<String> collectedAlerts = new ArrayList<>();
249 webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
250
251 final HtmlPage page1 = webClient.getPage(urlPage1);
252 final String[] expectedAlerts = {"in foo1", "in foo2"};
253 assertEquals(expectedAlerts, collectedAlerts);
254
255 collectedAlerts.clear();
256 page1.getAnchors().get(0).click();
257
258 assertEquals(new String[] {"in foo2"}, collectedAlerts);
259 assertEquals("no request for scripts should have been performed",
260 urlPage2, connection.getLastWebRequest().getUrl());
261 }
262
263
264
265
266 @Test
267 public void jsUrlEncoded() throws Exception {
268 final String content = DOCTYPE_HTML
269 + "<html>\n"
270 + "<head>\n"
271 + " <title>page 1</title>\n"
272 + " <script src='foo1.js'></script>\n"
273 + " <script src='foo2.js?foo[1]=bar/baz'></script>\n"
274 + "</head>\n"
275 + "<body>\n"
276 + " <a href='page2.html'>to page 2</a>\n"
277 + "</body>\n"
278 + "</html>";
279
280 final String content2 = DOCTYPE_HTML
281 + "<html>\n"
282 + "<head>\n"
283 + " <title>page 2</title>\n"
284 + " <script src='foo2.js?foo[1]=bar/baz'></script>\n"
285 + "</head>\n"
286 + "<body>\n"
287 + " <a href='page1.html'>to page 1</a>\n"
288 + "</body>\n"
289 + "</html>";
290
291 final String script1 = "alert('in foo1');";
292 final String script2 = "alert('in foo2');";
293
294 final URL urlPage1 = new URL(URL_FIRST, "page1.html");
295 getMockWebConnection().setResponse(urlPage1, content);
296 final URL urlPage2 = new URL(URL_FIRST, "page2.html");
297 getMockWebConnection().setResponse(urlPage2, content2);
298
299 final List<NameValuePair> headers = new ArrayList<>();
300 headers.add(new NameValuePair(LAST_MODIFIED, "Sun, 15 Jul 2007 20:46:27 GMT"));
301 getMockWebConnection().setResponse(new URL(URL_FIRST, "foo1.js"), script1,
302 200, "ok", MimeType.TEXT_JAVASCRIPT, headers);
303 getMockWebConnection().setDefaultResponse(script2, 200, "ok", MimeType.TEXT_JAVASCRIPT, headers);
304
305 final WebClient webClient = getWebClientWithMockWebConnection();
306
307 final List<String> collectedAlerts = new ArrayList<>();
308 webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
309
310 final HtmlPage page1 = webClient.getPage(urlPage1);
311 final String[] expectedAlerts = {"in foo1", "in foo2"};
312 assertEquals(expectedAlerts, collectedAlerts);
313
314 collectedAlerts.clear();
315 page1.getAnchors().get(0).click();
316
317 assertEquals(new String[] {"in foo2"}, collectedAlerts);
318 assertEquals("no request for scripts should have been performed",
319 urlPage2, getMockWebConnection().getLastWebRequest().getUrl());
320 }
321
322
323
324
325 @Test
326 public void cssUrlEncoded() throws Exception {
327 final String content = DOCTYPE_HTML
328 + "<html>\n"
329 + "<head>\n"
330 + " <title>page 1</title>\n"
331 + " <link href='foo1.css' type='text/css' rel='stylesheet'>\n"
332 + " <link href='foo2.js?foo[1]=bar/baz' type='text/css' rel='stylesheet'>\n"
333 + "</head>\n"
334 + "<body>\n"
335 + " <a href='page2.html'>to page 2</a>\n"
336 + " <script>\n"
337 + " var sheets = document.styleSheets;\n"
338 + " alert(sheets.length);\n"
339 + " var rules = sheets[0].cssRules || sheets[0].rules;\n"
340 + " alert(rules.length);\n"
341 + " rules = sheets[1].cssRules || sheets[1].rules;\n"
342 + " alert(rules.length);\n"
343 + " </script>\n"
344 + "</body>\n"
345 + "</html>";
346
347 final String content2 = DOCTYPE_HTML
348 + "<html>\n"
349 + "<head>\n"
350 + " <title>page 2</title>\n"
351 + " <link href='foo2.js?foo[1]=bar/baz' type='text/css' rel='stylesheet'>\n"
352 + "</head>\n"
353 + "<body>\n"
354 + " <a href='page1.html'>to page 1</a>\n"
355 + " <script>\n"
356 + " var sheets = document.styleSheets;\n"
357 + " alert(sheets.length);\n"
358 + " var rules = sheets[0].cssRules || sheets[0].rules;\n"
359 + " alert(rules.length);\n"
360 + " </script>\n"
361 + "</body>\n"
362 + "</html>";
363
364 final URL urlPage1 = new URL(URL_FIRST, "page1.html");
365 getMockWebConnection().setResponse(urlPage1, content);
366 final URL urlPage2 = new URL(URL_FIRST, "page2.html");
367 getMockWebConnection().setResponse(urlPage2, content2);
368
369 final List<NameValuePair> headers = new ArrayList<>();
370 headers.add(new NameValuePair(LAST_MODIFIED, "Sun, 15 Jul 2007 20:46:27 GMT"));
371 getMockWebConnection().setResponse(new URL(URL_FIRST, "foo1.js"), "",
372 200, "ok", MimeType.TEXT_CSS, headers);
373 getMockWebConnection().setDefaultResponse("", 200, "ok", MimeType.TEXT_CSS, headers);
374
375 final WebClient webClient = getWebClientWithMockWebConnection();
376
377 final List<String> collectedAlerts = new ArrayList<>();
378 webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
379
380 final HtmlPage page1 = webClient.getPage(urlPage1);
381 final String[] expectedAlerts = {"2", "0", "0"};
382 assertEquals(expectedAlerts, collectedAlerts);
383 assertEquals(3, getMockWebConnection().getRequestCount());
384
385 collectedAlerts.clear();
386 page1.getAnchors().get(0).click();
387
388 assertEquals(new String[] {"1", "0"}, collectedAlerts);
389 assertEquals(4, getMockWebConnection().getRequestCount());
390 assertEquals("no request for scripts should have been performed",
391 urlPage2, getMockWebConnection().getLastWebRequest().getUrl());
392 }
393
394
395
396
397 @Test
398 public void maxSizeMaintained() throws Exception {
399 final String html = DOCTYPE_HTML
400 + "<html><head><title>page 1</title>\n"
401 + "<script src='foo1.js' type='text/javascript'/>\n"
402 + "<script src='foo2.js' type='text/javascript'/>\n"
403 + "</head><body>abc</body></html>";
404
405 final WebClient client = getWebClient();
406 client.getCache().setMaxSize(1);
407
408 final MockWebConnection connection = new MockWebConnection();
409 client.setWebConnection(connection);
410
411 final URL pageUrl = new URL(URL_FIRST, "page1.html");
412 connection.setResponse(pageUrl, html);
413
414 final List<NameValuePair> headers =
415 Collections.singletonList(new NameValuePair(LAST_MODIFIED, "Sun, 15 Jul 2007 20:46:27 GMT"));
416 connection.setResponse(new URL(URL_FIRST, "foo1.js"), ";", 200, "ok", MimeType.TEXT_JAVASCRIPT, headers);
417 connection.setResponse(new URL(URL_FIRST, "foo2.js"), ";", 200, "ok", MimeType.TEXT_JAVASCRIPT, headers);
418
419 client.getPage(pageUrl);
420 assertEquals(1, client.getCache().getSize());
421
422 client.getCache().clear();
423 assertEquals(0, client.getCache().getSize());
424 }
425
426
427
428
429
430 @Test
431 public void cssIsCached() throws Exception {
432 final String html = DOCTYPE_HTML
433 + "<html><head><title>page 1</title>\n"
434 + "<style>.x { color: red; }</style>\n"
435 + "<link rel='stylesheet' type='text/css' href='foo.css' />\n"
436 + "</head>\n"
437 + "<body onload='document.styleSheets.item(0); document.styleSheets.item(1);'>x</body>\n"
438 + "</html>";
439
440 final WebClient client = getWebClient();
441
442 final MockWebConnection connection = new MockWebConnection();
443 client.setWebConnection(connection);
444
445 final URL pageUrl = new URL(URL_FIRST, "page1.html");
446 connection.setResponse(pageUrl, html);
447
448 final List<NameValuePair> headers =
449 Collections.singletonList(new NameValuePair(LAST_MODIFIED, "Sun, 15 Jul 2007 20:46:27 GMT"));
450 connection.setResponse(new URL(URL_FIRST, "foo.css"), "", 200, "OK", MimeType.TEXT_CSS, headers);
451
452 client.getPage(pageUrl);
453 assertEquals(2, client.getCache().getSize());
454 }
455
456
457
458
459
460
461 @Test
462 public void cssIsCachedIfUrlWasRedirected() throws Exception {
463 final String html = DOCTYPE_HTML
464 + "<html><head><title>page 1</title>\n"
465 + "<link rel='stylesheet' type='text/css' href='foo.css' />\n"
466 + "</head>\n"
467 + "<body onload='document.styleSheets.item(0); document.styleSheets.item(1);'>x</body>\n"
468 + "</html>";
469
470 final String css = ".x { color: red; }";
471
472 final WebClient client = getWebClient();
473
474 final MockWebConnection connection = new MockWebConnection();
475 client.setWebConnection(connection);
476
477 final URL pageUrl = new URL(URL_FIRST, "page1.html");
478 connection.setResponse(pageUrl, html);
479
480 final URL cssUrl = new URL(URL_FIRST, "foo.css");
481 final URL redirectUrl = new URL(URL_FIRST, "fooContent.css");
482
483 List<NameValuePair> headers = new ArrayList<>();
484 headers.add(new NameValuePair("Location", redirectUrl.toExternalForm()));
485 connection.setResponse(cssUrl, "", 301, "Redirect", null, headers);
486
487 headers = Collections.singletonList(new NameValuePair(LAST_MODIFIED, "Sun, 15 Jul 2007 20:46:27 GMT"));
488 connection.setResponse(redirectUrl, css, 200, "OK", MimeType.TEXT_CSS, headers);
489
490 client.getPage(pageUrl);
491 client.getPage(pageUrl);
492
493
494 assertEquals(4, connection.getRequestCount());
495
496 assertEquals(2, client.getCache().getSize());
497 }
498
499
500
501
502 @Test
503 public void cssFromCacheIsUsed() throws Exception {
504 final String html = DOCTYPE_HTML
505 + "<html><head><title>page 1</title>\n"
506 + "<link rel='stylesheet' type='text/css' href='foo.css' />\n"
507 + "<link rel='stylesheet' type='text/css' href='foo.css' />\n"
508 + "</head>\n"
509 + "<body>x</body>\n"
510 + "</html>";
511
512 final String css = ".x { color: red; }";
513
514 final WebClient client = getWebClient();
515
516 final MockWebConnection connection = new MockWebConnection();
517 client.setWebConnection(connection);
518
519 final URL pageUrl = new URL(URL_FIRST, "page1.html");
520 connection.setResponse(pageUrl, html);
521
522 final URL cssUrl = new URL(URL_FIRST, "foo.css");
523 final List<NameValuePair> headers = new ArrayList<>();
524 headers.add(new NameValuePair(LAST_MODIFIED, "Sun, 15 Jul 2007 20:46:27 GMT"));
525 connection.setResponse(cssUrl, css, 200, "OK", MimeType.TEXT_CSS, headers);
526
527 client.getPage(pageUrl);
528
529 assertEquals(2, connection.getRequestCount());
530 assertEquals(1, client.getCache().getSize());
531 }
532
533
534
535
536 @Test
537 public void cssManuallyAddeToCache() throws Exception {
538 final String html = DOCTYPE_HTML
539 + "<html><head>\n"
540 + "<link rel='stylesheet' type='text/css' href='foo.css' />\n"
541 + "</head>\n"
542 + "<body>\n"
543 + "abc <div class='test'>def</div>\n"
544 + "</body>\n"
545 + "</html>";
546
547 final String css = ".test { visibility: hidden; }";
548
549 final WebClient client = getWebClient();
550
551 final MockWebConnection connection = new MockWebConnection();
552 client.setWebConnection(connection);
553
554 final URL pageUrl = new URL(URL_FIRST, "page1.html");
555 connection.setResponse(pageUrl, html);
556
557 final URL cssUrl = new URL(URL_FIRST, "foo.css");
558 final List<NameValuePair> headers = new ArrayList<>();
559 headers.add(new NameValuePair(LAST_MODIFIED, "Sun, 15 Jul 2007 20:46:27 GMT"));
560 final WebRequest request = new WebRequest(cssUrl);
561 final WebResponseData data = new WebResponseData(css.getBytes("UTF-8"),
562 HttpStatus.OK_200, HttpStatus.OK_200_MSG, headers);
563 final WebResponse response = new WebResponse(data, request, 100);
564 client.getCache().cacheIfPossible(new WebRequest(cssUrl), response, headers);
565
566 final HtmlPage page = client.getPage(pageUrl);
567 assertEquals("abc", page.asNormalizedText());
568
569 assertEquals(1, connection.getRequestCount());
570 assertEquals(1, client.getCache().getSize());
571 }
572
573
574
575
576
577 @Test
578 @Alerts({"hello", "hello"})
579 public void xhrContentCached() throws Exception {
580 final String html = DOCTYPE_HTML
581 + "<html><head><title>page 1</title>\n"
582 + "<script>\n"
583 + " function doTest() {\n"
584 + " var xhr = new XMLHttpRequest();\n"
585 + " xhr.open('GET', 'foo.txt', false);\n"
586 + " xhr.send('');\n"
587 + " alert(xhr.responseText);\n"
588 + " xhr.send('');\n"
589 + " alert(xhr.responseText);\n"
590 + " }\n"
591 + "</script>\n"
592 + "</head>\n"
593 + "<body onload='doTest()'>x</body>\n"
594 + "</html>";
595
596 final MockWebConnection connection = getMockWebConnection();
597
598 final List<NameValuePair> headers =
599 Collections.singletonList(new NameValuePair(LAST_MODIFIED, "Sun, 15 Jul 2007 20:46:27 GMT"));
600 connection.setResponse(new URL(URL_FIRST, "foo.txt"), "hello", 200, "OK", MimeType.TEXT_PLAIN, headers);
601
602 loadPageWithAlerts(html);
603
604 assertEquals(2, connection.getRequestCount());
605 }
606
607
608
609
610 @Test
611 public void testNoStoreCacheControl() throws Exception {
612 final String html = DOCTYPE_HTML
613 + "<html><head><title>page 1</title>\n"
614 + "<link rel='stylesheet' type='text/css' href='foo.css' />\n"
615 + "</head>\n"
616 + "<body>x</body>\n"
617 + "</html>";
618
619 final WebClient client = getWebClient();
620
621 final MockWebConnection connection = new MockWebConnection();
622 client.setWebConnection(connection);
623
624 final List<NameValuePair> headers = new ArrayList<>();
625 headers.add(new NameValuePair(CACHE_CONTROL, "some-other-value, no-store"));
626
627 final URL pageUrl = new URL(URL_FIRST, "page1.html");
628 connection.setResponse(pageUrl, html, 200, "OK", "text/html;charset=ISO-8859-1", headers);
629 connection.setResponse(new URL(URL_FIRST, "foo.css"), "", 200, "OK", MimeType.TEXT_JAVASCRIPT, headers);
630
631 client.getPage(pageUrl);
632 assertEquals(0, client.getCache().getSize());
633 assertEquals(2, connection.getRequestCount());
634
635 client.getPage(pageUrl);
636 assertEquals(0, client.getCache().getSize());
637 assertEquals(4, connection.getRequestCount());
638 }
639
640
641
642
643 @Test
644 public void testNoCacheCacheControl() throws Exception {
645 final String html = DOCTYPE_HTML
646 + "<html><head><title>page 1</title>\n"
647 + "</head>\n"
648 + "<body>x</body>\n"
649 + "</html>";
650
651 final WebClient client = getWebClient();
652
653 final MockWebConnection connection = new MockWebConnection();
654 client.setWebConnection(connection);
655
656 final String date = "Thu, 02 Mar 2023 02:00:00 GMT";
657 final String etag = "foo";
658 final String lastModified = "Wed, 01 Mar 2023 01:00:00 GMT";
659
660 final List<NameValuePair> headers = new ArrayList<>();
661 headers.add(new NameValuePair("Date", date));
662 headers.add(new NameValuePair(CACHE_CONTROL, "some-other-value, no-cache"));
663 headers.add(new NameValuePair(ETAG, etag));
664 headers.add(new NameValuePair(LAST_MODIFIED, lastModified));
665
666 final URL pageUrl = new URL(URL_FIRST, "page1.html");
667 connection.setResponse(pageUrl, html, 200, "OK", "text/html;charset=ISO-8859-1", headers);
668
669 client.getPage(pageUrl);
670 assertEquals(1, client.getCache().getSize());
671
672 final String updatedDate = "Thu, 02 Mar 2023 02:00:10 GMT";
673 final List<NameValuePair> headers2 = new ArrayList<>();
674 headers2.add(new NameValuePair("Date", updatedDate));
675 headers2.add(new NameValuePair("Proxy-Authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l"));
676 headers2.add(new NameValuePair("X-Content-Type-Options", "nosniff"));
677 connection.setResponse(pageUrl, html, 304, "Not Modified", "text/html;charset=ISO-8859-1", headers2);
678
679 client.getPage(pageUrl);
680 assertEquals(2, connection.getRequestCount());
681
682 final WebRequest lastRequest = connection.getLastWebRequest();
683 assertEquals(etag, lastRequest.getAdditionalHeader(IF_NONE_MATCH));
684 assertEquals(lastModified, lastRequest.getAdditionalHeader(IF_MODIFIED_SINCE));
685 assertEquals(1, client.getCache().getSize());
686
687 WebResponse cached = client.getCache().getCachedResponse(connection.getLastWebRequest());
688 assertEquals(updatedDate, cached.getResponseHeaderValue("Date"));
689 assertEquals(null, cached.getResponseHeaderValue("Proxy-Authorization"));
690 assertEquals(null, cached.getResponseHeaderValue("X-Content-Type-Options"));
691
692 final String updatedEtag = "bar";
693 final String updatedLastModified = "Wed, 01 Mar 2023 02:00:00 GMT";
694
695 final List<NameValuePair> headers3 = new ArrayList<>();
696 headers3.add(new NameValuePair(CACHE_CONTROL, "some-other-value, no-cache"));
697 headers3.add(new NameValuePair(ETAG, updatedEtag));
698 headers3.add(new NameValuePair(LAST_MODIFIED, updatedLastModified));
699 connection.setResponse(pageUrl, html, 200, "OK", "text/html;charset=ISO-8859-1", headers3);
700
701 client.getPage(pageUrl);
702 assertEquals(3, connection.getRequestCount());
703 assertEquals(1, client.getCache().getSize());
704
705 cached = client.getCache().getCachedResponse(connection.getLastWebRequest());
706 assertEquals(null, cached.getResponseHeaderValue("Date"));
707 assertEquals(updatedEtag, cached.getResponseHeaderValue(ETAG));
708 assertEquals(updatedLastModified, cached.getResponseHeaderValue(LAST_MODIFIED));
709 }
710
711
712
713
714 @Test
715 public void testMaxAgeCacheControl() throws Exception {
716 final String html = DOCTYPE_HTML
717 + "<html><head><title>page 1</title>\n"
718 + "<link rel='stylesheet' type='text/css' href='foo.css' />\n"
719 + "</head>\n"
720 + "<body>x</body>\n"
721 + "</html>";
722
723 final WebClient client = getWebClient();
724
725 final MockWebConnection connection = new MockWebConnection();
726 client.setWebConnection(connection);
727
728 final List<NameValuePair> headers = new ArrayList<>();
729 headers.add(new NameValuePair(LAST_MODIFIED, "Tue, 20 Feb 2018 10:00:00 GMT"));
730 headers.add(new NameValuePair(CACHE_CONTROL, "some-other-value, max-age=1"));
731
732 final URL pageUrl = new URL(URL_FIRST, "page1.html");
733 connection.setResponse(pageUrl, html, 200, "OK", "text/html;charset=ISO-8859-1", headers);
734 connection.setResponse(new URL(URL_FIRST, "foo.css"), "", 200, "OK", MimeType.TEXT_JAVASCRIPT, headers);
735
736 client.getPage(pageUrl);
737 assertEquals(2, client.getCache().getSize());
738 assertEquals(2, connection.getRequestCount());
739
740 client.getPage(pageUrl);
741 assertEquals(2, client.getCache().getSize());
742 assertEquals(2, connection.getRequestCount());
743
744 Thread.sleep(2 * 1000);
745 client.getPage(pageUrl);
746 assertEquals(2, client.getCache().getSize());
747 assertEquals(4, connection.getRequestCount());
748
749
750 Thread.sleep(2 * 1000);
751 client.getCache().clearOutdated();
752 assertEquals(0, client.getCache().getSize());
753 assertEquals(4, connection.getRequestCount());
754 }
755
756
757
758
759 @Test
760 public void testSMaxageCacheControl() throws Exception {
761 final String html = DOCTYPE_HTML
762 + "<html><head><title>page 1</title>\n"
763 + "<link rel='stylesheet' type='text/css' href='foo.css' />\n"
764 + "</head>\n"
765 + "<body>x</body>\n"
766 + "</html>";
767
768 final WebClient client = getWebClient();
769
770 final MockWebConnection connection = new MockWebConnection();
771 client.setWebConnection(connection);
772
773 final List<NameValuePair> headers = new ArrayList<>();
774 headers.add(new NameValuePair(LAST_MODIFIED, "Tue, 20 Feb 2018 10:00:00 GMT"));
775 headers.add(new NameValuePair(CACHE_CONTROL, "public, s-maxage=1, some-other-value, max-age=10"));
776
777 final URL pageUrl = new URL(URL_FIRST, "page1.html");
778 connection.setResponse(pageUrl, html, 200, "OK", "text/html;charset=ISO-8859-1", headers);
779 connection.setResponse(new URL(URL_FIRST, "foo.css"), "", 200, "OK", MimeType.TEXT_JAVASCRIPT, headers);
780
781 client.getPage(pageUrl);
782 assertEquals(2, client.getCache().getSize());
783 assertEquals(2, connection.getRequestCount());
784
785 client.getPage(pageUrl);
786 assertEquals(2, client.getCache().getSize());
787 assertEquals(2, connection.getRequestCount());
788
789 Thread.sleep(2 * 1000);
790 client.getPage(pageUrl);
791 assertEquals(2, client.getCache().getSize());
792 assertEquals(4, connection.getRequestCount());
793
794
795 Thread.sleep(2 * 1000);
796 client.getCache().clearOutdated();
797 assertEquals(0, client.getCache().getSize());
798 assertEquals(4, connection.getRequestCount());
799 }
800
801
802
803
804 @Test
805 public void testExpiresCacheControl() throws Exception {
806 final String html = DOCTYPE_HTML
807 + "<html><head><title>page 1</title>\n"
808 + "<link rel='stylesheet' type='text/css' href='foo.css' />\n"
809 + "</head>\n"
810 + "<body>x</body>\n"
811 + "</html>";
812
813 final WebClient client = getWebClient();
814
815 final MockWebConnection connection = new MockWebConnection();
816 client.setWebConnection(connection);
817
818 final List<NameValuePair> headers = new ArrayList<>();
819 headers.add(new NameValuePair(LAST_MODIFIED, "Tue, 20 Feb 2018 10:00:00 GMT"));
820 final Date expi = new Date(System.currentTimeMillis() + 2 * 1000 + 10 * DateUtils.MILLIS_PER_MINUTE);
821 headers.add(new NameValuePair(EXPIRES, new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").format(expi)));
822 headers.add(new NameValuePair(CACHE_CONTROL, "public, some-other-value"));
823
824 final URL pageUrl = new URL(URL_FIRST, "page1.html");
825 connection.setResponse(pageUrl, html, 200, "OK", "text/html;charset=ISO-8859-1", headers);
826 connection.setResponse(new URL(URL_FIRST, "foo.css"), "", 200, "OK", MimeType.TEXT_JAVASCRIPT, headers);
827
828 client.getPage(pageUrl);
829 assertEquals(2, client.getCache().getSize());
830 assertEquals(2, connection.getRequestCount());
831
832 client.getPage(pageUrl);
833 assertEquals(2, client.getCache().getSize());
834 assertEquals(2, connection.getRequestCount());
835
836 Thread.sleep(2 * 1000);
837 client.getPage(pageUrl);
838 assertEquals(0, client.getCache().getSize());
839 assertEquals(4, connection.getRequestCount());
840 }
841
842
843
844
845 @Test
846 public void testMaxAgeOverrulesExpiresCacheControl() throws Exception {
847 final String html = DOCTYPE_HTML
848 + "<html><head><title>page 1</title>\n"
849 + "<link rel='stylesheet' type='text/css' href='foo.css' />\n"
850 + "</head>\n"
851 + "<body>x</body>\n"
852 + "</html>";
853
854 final WebClient client = getWebClient();
855
856 final MockWebConnection connection = new MockWebConnection();
857 client.setWebConnection(connection);
858
859 final List<NameValuePair> headers = new ArrayList<>();
860 headers.add(new NameValuePair(LAST_MODIFIED, "Tue, 20 Feb 2018 10:00:00 GMT"));
861 headers.add(new NameValuePair(EXPIRES, "0"));
862 headers.add(new NameValuePair(CACHE_CONTROL, "max-age=20"));
863
864 final URL pageUrl = new URL(URL_FIRST, "page1.html");
865 connection.setResponse(pageUrl, html, 200, "OK", "text/html;charset=ISO-8859-1", headers);
866 connection.setResponse(new URL(URL_FIRST, "foo.css"), "", 200, "OK", MimeType.TEXT_JAVASCRIPT, headers);
867
868 client.getPage(pageUrl);
869 assertEquals(2, client.getCache().getSize());
870 assertEquals(2, connection.getRequestCount());
871
872 client.getPage(pageUrl);
873 assertEquals(2, client.getCache().getSize());
874 assertEquals(2, connection.getRequestCount());
875 }
876
877
878
879
880
881 @Test
882 public void cleanUpOverflow() throws Exception {
883 final WebRequest request1 = new WebRequest(URL_FIRST, HttpMethod.GET);
884
885 final Map<String, String> headers = new HashMap<>();
886 headers.put(HttpHeader.EXPIRES, formatDate(DateUtils.addHours(new Date(), 1)));
887
888 final WebResponseMock response1 = new WebResponseMock(request1, headers);
889
890 final WebRequest request2 = new WebRequest(URL_SECOND, HttpMethod.GET);
891 final WebResponseMock response2 = new WebResponseMock(request2, headers);
892
893 final Cache cache = new Cache();
894 cache.setMaxSize(1);
895 cache.cacheIfPossible(request1, response1, null);
896 assertEquals(0, response1.getCallCount("cleanUp"));
897 assertEquals(0, response2.getCallCount("cleanUp"));
898 assertEquals(6, response1.getCallCount("getResponseHeaderValue"));
899 assertEquals(0, response2.getCallCount("getResponseHeaderValue"));
900
901 Thread.sleep(10);
902 cache.cacheIfPossible(request2, response2, null);
903 assertEquals(1, response1.getCallCount("cleanUp"));
904 assertEquals(0, response2.getCallCount("cleanUp"));
905 assertEquals(6, response1.getCallCount("getResponseHeaderValue"));
906 assertEquals(6, response2.getCallCount("getResponseHeaderValue"));
907 }
908
909
910
911
912 @Test
913 public void cleanUpOnClear() {
914 final WebRequest request1 = new WebRequest(URL_FIRST, HttpMethod.GET);
915
916 final Map<String, String> headers = new HashMap<>();
917 headers.put(HttpHeader.EXPIRES, formatDate(DateUtils.addHours(new Date(), 1)));
918
919 final WebResponseMock response1 = new WebResponseMock(request1, headers);
920
921 final Cache cache = new Cache();
922 cache.cacheIfPossible(request1, response1, null);
923 assertEquals(0, response1.getCallCount("cleanUp"));
924 assertEquals(6, response1.getCallCount("getResponseHeaderValue"));
925
926 cache.clear();
927
928 assertEquals(1, response1.getCallCount("cleanUp"));
929 assertEquals(6, response1.getCallCount("getResponseHeaderValue"));
930 }
931 }