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
30 import static org.junit.jupiter.api.Assertions.assertEquals;
31 import static org.junit.jupiter.api.Assertions.assertFalse;
32 import static org.junit.jupiter.api.Assertions.assertNotEquals;
33 import static org.junit.jupiter.api.Assertions.assertNotNull;
34 import static org.junit.jupiter.api.Assertions.assertNull;
35 import static org.junit.jupiter.api.Assertions.assertTrue;
36
37 import java.io.IOException;
38 import java.time.Instant;
39 import java.time.temporal.ChronoUnit;
40 import java.util.Arrays;
41 import java.util.Iterator;
42 import java.util.List;
43
44 import org.apache.hc.client5.http.HttpRoute;
45 import org.apache.hc.client5.http.auth.StandardAuthScheme;
46 import org.apache.hc.client5.http.classic.ExecChain;
47 import org.apache.hc.client5.http.classic.ExecRuntime;
48 import org.apache.hc.client5.http.classic.methods.HttpGet;
49 import org.apache.hc.client5.http.classic.methods.HttpPost;
50 import org.apache.hc.client5.http.protocol.HttpClientContext;
51 import org.apache.hc.client5.http.utils.DateUtils;
52 import org.apache.hc.core5.http.ClassicHttpRequest;
53 import org.apache.hc.core5.http.ClassicHttpResponse;
54 import org.apache.hc.core5.http.Header;
55 import org.apache.hc.core5.http.HeaderElement;
56 import org.apache.hc.core5.http.HttpEntity;
57 import org.apache.hc.core5.http.HttpException;
58 import org.apache.hc.core5.http.HttpHeaders;
59 import org.apache.hc.core5.http.HttpHost;
60 import org.apache.hc.core5.http.HttpStatus;
61 import org.apache.hc.core5.http.HttpVersion;
62 import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
63 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
64 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
65 import org.apache.hc.core5.http.message.MessageSupport;
66 import org.junit.jupiter.api.BeforeEach;
67 import org.junit.jupiter.api.Test;
68 import org.mockito.ArgumentCaptor;
69 import org.mockito.Mock;
70 import org.mockito.Mockito;
71 import org.mockito.MockitoAnnotations;
72
73
74
75
76
77
78 public class TestProtocolRecommendations {
79
80 static final int MAX_BYTES = 1024;
81 static final int MAX_ENTRIES = 100;
82 static final int ENTITY_LENGTH = 128;
83
84 HttpHost host;
85 HttpRoute route;
86 HttpEntity body;
87 HttpClientContext context;
88 @Mock
89 ExecChain mockExecChain;
90 @Mock
91 ExecRuntime mockExecRuntime;
92 ClassicHttpRequest request;
93 ClassicHttpResponse originResponse;
94 CacheConfig config;
95 CachingExec impl;
96 HttpCache cache;
97 Instant now;
98 Instant tenSecondsAgo;
99 Instant twoMinutesAgo;
100
101 @BeforeEach
102 public void setUp() throws Exception {
103 MockitoAnnotations.openMocks(this);
104 host = new HttpHost("foo.example.com", 80);
105
106 route = new HttpRoute(host);
107
108 body = HttpTestUtils.makeBody(ENTITY_LENGTH);
109
110 request = new BasicClassicHttpRequest("GET", "/foo");
111
112 context = HttpClientContext.create();
113
114 originResponse = HttpTestUtils.make200Response();
115
116 config = CacheConfig.custom()
117 .setMaxCacheEntries(MAX_ENTRIES)
118 .setMaxObjectSize(MAX_BYTES)
119 .build();
120
121 cache = new BasicHttpCache(config);
122 impl = new CachingExec(cache, null, config);
123
124 now = Instant.now();
125 tenSecondsAgo = now.minus(10, ChronoUnit.SECONDS);
126 twoMinutesAgo = now.minus(1, ChronoUnit.MINUTES);
127
128 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
129 }
130
131 public ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
132 return impl.execute(
133 ClassicRequestBuilder.copy(request).build(),
134 new ExecChain.Scope("test", route, request, mockExecRuntime, context),
135 mockExecChain);
136 }
137
138
139
140
141
142
143
144
145 @Test
146 public void testIdentityCodingIsNotUsedInContentEncodingHeader() throws Exception {
147 originResponse.setHeader("Content-Encoding", "identity");
148 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
149
150 final ClassicHttpResponse result = execute(request);
151
152 boolean foundIdentity = false;
153 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.CONTENT_ENCODING);
154 while (it.hasNext()) {
155 final HeaderElement elt = it.next();
156 if ("identity".equalsIgnoreCase(elt.getName())) {
157 foundIdentity = true;
158 }
159 }
160 assertFalse(foundIdentity);
161 }
162
163
164
165
166
167
168
169
170 private void cacheGenerated304ForValidatorShouldNotContainEntityHeader(
171 final String headerName, final String headerValue, final String validatorHeader,
172 final String validator, final String conditionalHeader) throws Exception {
173 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
174 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
175 resp1.setHeader("Cache-Control","max-age=3600");
176 resp1.setHeader(validatorHeader, validator);
177 resp1.setHeader(headerName, headerValue);
178
179 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
180
181 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
182 req2.setHeader(conditionalHeader, validator);
183
184 execute(req1);
185 final ClassicHttpResponse result = execute(req2);
186
187
188 if (HttpStatus.SC_NOT_MODIFIED == result.getCode()) {
189 assertNull(result.getFirstHeader(headerName));
190 }
191 }
192
193 private void cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
194 final String headerName, final String headerValue) throws Exception {
195 cacheGenerated304ForValidatorShouldNotContainEntityHeader(headerName,
196 headerValue, "ETag", "\"etag\"", "If-None-Match");
197 }
198
199 private void cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
200 final String headerName, final String headerValue) throws Exception {
201 cacheGenerated304ForValidatorShouldNotContainEntityHeader(headerName,
202 headerValue, "Last-Modified", DateUtils.formatStandardDate(twoMinutesAgo),
203 "If-Modified-Since");
204 }
205
206 @Test
207 public void cacheGenerated304ForStrongEtagValidatorShouldNotContainAllow() throws Exception {
208 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
209 "Allow", "GET,HEAD");
210 }
211
212 @Test
213 public void cacheGenerated304ForStrongDateValidatorShouldNotContainAllow() throws Exception {
214 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
215 "Allow", "GET,HEAD");
216 }
217
218 @Test
219 public void cacheGenerated304ForStrongEtagValidatorShouldNotContainContentEncoding() throws Exception {
220 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
221 "Content-Encoding", "gzip");
222 }
223
224 @Test
225 public void cacheGenerated304ForStrongDateValidatorShouldNotContainContentEncoding() throws Exception {
226 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
227 "Content-Encoding", "gzip");
228 }
229
230 @Test
231 public void cacheGenerated304ForStrongEtagValidatorShouldNotContainContentLanguage() throws Exception {
232 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
233 "Content-Language", "en");
234 }
235
236 @Test
237 public void cacheGenerated304ForStrongDateValidatorShouldNotContainContentLanguage() throws Exception {
238 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
239 "Content-Language", "en");
240 }
241
242 @Test
243 public void cacheGenerated304ForStrongValidatorShouldNotContainContentLength() throws Exception {
244 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
245 "Content-Length", "128");
246 }
247
248 @Test
249 public void cacheGenerated304ForStrongDateValidatorShouldNotContainContentLength() throws Exception {
250 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
251 "Content-Length", "128");
252 }
253
254 @Test
255 public void cacheGenerated304ForStrongValidatorShouldNotContainContentMD5() throws Exception {
256 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
257 "Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
258 }
259
260 @Test
261 public void cacheGenerated304ForStrongDateValidatorShouldNotContainContentMD5() throws Exception {
262 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
263 "Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
264 }
265
266 private void cacheGenerated304ForStrongValidatorShouldNotContainContentRange(
267 final String validatorHeader, final String validator, final String conditionalHeader) throws Exception {
268 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
269 req1.setHeader("Range","bytes=0-127");
270 final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
271 resp1.setHeader("Cache-Control","max-age=3600");
272 resp1.setHeader(validatorHeader, validator);
273 resp1.setHeader("Content-Range", "bytes 0-127/256");
274
275 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
276
277 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
278 req2.setHeader("If-Range", validator);
279 req2.setHeader("Range","bytes=0-127");
280 req2.setHeader(conditionalHeader, validator);
281
282 try (final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified")) {
283 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
284 resp2.setHeader(validatorHeader, validator);
285 }
286
287
288
289
290 execute(req1);
291 final ClassicHttpResponse result = execute(req2);
292
293 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
294 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(reqCapture.capture(), Mockito.any());
295
296 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
297 if (allRequests.isEmpty() && HttpStatus.SC_NOT_MODIFIED == result.getCode()) {
298
299 assertNull(result.getFirstHeader("Content-Range"));
300 }
301 }
302
303 @Test
304 public void cacheGenerated304ForStrongEtagValidatorShouldNotContainContentRange() throws Exception {
305 cacheGenerated304ForStrongValidatorShouldNotContainContentRange(
306 "ETag", "\"etag\"", "If-None-Match");
307 }
308
309 @Test
310 public void cacheGenerated304ForStrongDateValidatorShouldNotContainContentRange() throws Exception {
311 cacheGenerated304ForStrongValidatorShouldNotContainContentRange(
312 "Last-Modified", DateUtils.formatStandardDate(twoMinutesAgo), "If-Modified-Since");
313 }
314
315 @Test
316 public void cacheGenerated304ForStrongEtagValidatorShouldNotContainContentType() throws Exception {
317 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
318 "Content-Type", "text/html");
319 }
320
321 @Test
322 public void cacheGenerated304ForStrongDateValidatorShouldNotContainContentType() throws Exception {
323 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
324 "Content-Type", "text/html");
325 }
326
327 @Test
328 public void cacheGenerated304ForStrongEtagValidatorShouldNotContainLastModified() throws Exception {
329 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
330 "Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
331 }
332
333 @Test
334 public void cacheGenerated304ForStrongDateValidatorShouldNotContainLastModified() throws Exception {
335 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
336 "Last-Modified", DateUtils.formatStandardDate(twoMinutesAgo));
337 }
338
339 private void shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
340 final String entityHeader, final String entityHeaderValue) throws Exception {
341 final ClassicHttpRequest req = HttpTestUtils.makeDefaultRequest();
342 req.setHeader("If-None-Match", "\"etag\"");
343
344 final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
345 resp.setHeader("Date", DateUtils.formatStandardDate(now));
346 resp.setHeader("Etag", "\"etag\"");
347 resp.setHeader(entityHeader, entityHeaderValue);
348
349 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp);
350
351 final ClassicHttpResponse result = execute(req);
352
353 assertNull(result.getFirstHeader(entityHeader));
354 }
355
356 @Test
357 public void shouldStripAllowFromOrigin304ResponseToStrongValidation() throws Exception {
358 shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
359 "Allow", "GET,HEAD");
360 }
361
362 @Test
363 public void shouldStripContentEncodingFromOrigin304ResponseToStrongValidation() throws Exception {
364 shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
365 "Content-Encoding", "gzip");
366 }
367
368 @Test
369 public void shouldStripContentLanguageFromOrigin304ResponseToStrongValidation() throws Exception {
370 shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
371 "Content-Language", "en");
372 }
373
374 @Test
375 public void shouldStripContentLengthFromOrigin304ResponseToStrongValidation() throws Exception {
376 shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
377 "Content-Length", "128");
378 }
379
380 @Test
381 public void shouldStripContentMD5FromOrigin304ResponseToStrongValidation() throws Exception {
382 shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
383 "Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
384 }
385
386 @Test
387 public void shouldStripContentTypeFromOrigin304ResponseToStrongValidation() throws Exception {
388 shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
389 "Content-Type", "text/html;charset=utf-8");
390 }
391
392 @Test
393 public void shouldStripContentRangeFromOrigin304ResponseToStringValidation() throws Exception {
394 final ClassicHttpRequest req = HttpTestUtils.makeDefaultRequest();
395 req.setHeader("If-Range","\"etag\"");
396 req.setHeader("Range","bytes=0-127");
397
398 final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
399 resp.setHeader("Date", DateUtils.formatStandardDate(now));
400 resp.setHeader("ETag", "\"etag\"");
401 resp.setHeader("Content-Range", "bytes 0-127/256");
402
403 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp);
404
405 final ClassicHttpResponse result = execute(req);
406
407 assertNull(result.getFirstHeader("Content-Range"));
408 }
409
410 @Test
411 public void shouldStripLastModifiedFromOrigin304ResponseToStrongValidation() throws Exception {
412 shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
413 "Last-Modified", DateUtils.formatStandardDate(twoMinutesAgo));
414 }
415
416
417
418
419
420
421 private ClassicHttpRequest requestToPopulateStaleCacheEntry() throws Exception {
422 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
423 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
424 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
425 resp1.setHeader("Cache-Control","public,max-age=5");
426 resp1.setHeader("Etag","\"etag\"");
427
428 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
429 return req1;
430 }
431
432 private void testDoesNotReturnStaleResponseOnError(final ClassicHttpRequest req2) throws Exception {
433 final ClassicHttpRequest req1 = requestToPopulateStaleCacheEntry();
434
435 execute(req1);
436
437 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new IOException());
438
439 ClassicHttpResponse result = null;
440 try {
441 result = execute(req2);
442 } catch (final IOException acceptable) {
443 }
444
445 if (result != null) {
446 assertNotEquals(HttpStatus.SC_OK, result.getCode());
447 }
448 }
449
450 @Test
451 public void testDoesNotReturnStaleResponseIfClientExplicitlyRequestsFirstHandOneWithCacheControl() throws Exception {
452 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
453 req.setHeader("Cache-Control","no-cache");
454 testDoesNotReturnStaleResponseOnError(req);
455 }
456
457 @Test
458 public void testDoesNotReturnStaleResponseIfClientExplicitlyRequestsFirstHandOneWithPragma() throws Exception {
459 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
460 req.setHeader("Pragma","no-cache");
461 testDoesNotReturnStaleResponseOnError(req);
462 }
463
464 @Test
465 public void testDoesNotReturnStaleResponseIfClientExplicitlyRequestsFreshWithMaxAge() throws Exception {
466 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
467 req.setHeader("Cache-Control","max-age=0");
468 testDoesNotReturnStaleResponseOnError(req);
469 }
470
471 @Test
472 public void testDoesNotReturnStaleResponseIfClientExplicitlySpecifiesLargerMaxAge() throws Exception {
473 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
474 req.setHeader("Cache-Control","max-age=20");
475 testDoesNotReturnStaleResponseOnError(req);
476 }
477
478
479 @Test
480 public void testDoesNotReturnStaleResponseIfClientExplicitlyRequestsFreshWithMinFresh() throws Exception {
481 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
482 req.setHeader("Cache-Control","min-fresh=2");
483
484 testDoesNotReturnStaleResponseOnError(req);
485 }
486
487 @Test
488 public void testDoesNotReturnStaleResponseIfClientExplicitlyRequestsFreshWithMaxStale() throws Exception {
489 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
490 req.setHeader("Cache-Control","max-stale=2");
491
492 testDoesNotReturnStaleResponseOnError(req);
493 }
494
495 @Test
496 public void testMayReturnStaleResponseIfClientExplicitlySpecifiesAcceptableMaxStale() throws Exception {
497 final ClassicHttpRequest req1 = requestToPopulateStaleCacheEntry();
498 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
499 req2.setHeader("Cache-Control","max-stale=20");
500
501 execute(req1);
502
503 final ClassicHttpResponse result = execute(req2);
504
505 assertEquals(HttpStatus.SC_OK, result.getCode());
506 assertNotNull(result.getFirstHeader("Warning"));
507
508 Mockito.verify(mockExecChain, Mockito.atMost(1)).proceed(Mockito.any(), Mockito.any());
509 }
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544 @Test
545 public void testReturnsCachedResponsesAppropriatelyWhenNoOriginCommunication() throws Exception {
546 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
547 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
548 resp1.setHeader("Cache-Control", "public, max-age=5");
549 resp1.setHeader("ETag","\"etag\"");
550 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
551
552 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
553
554 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
555
556 execute(req1);
557
558 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new IOException());
559
560 final ClassicHttpResponse result = execute(req2);
561
562 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
563
564 assertEquals(HttpStatus.SC_OK, result.getCode());
565 boolean warning111Found = false;
566 for(final Header h : result.getHeaders("Warning")) {
567 for(final WarningValue wv : WarningValue.getWarningValues(h)) {
568 if (wv.getWarnCode() == 111) {
569 warning111Found = true;
570 break;
571 }
572 }
573 }
574 assertTrue(warning111Found);
575 }
576
577
578
579
580
581
582
583
584
585
586
587
588
589 @Test
590 public void testDoesNotAddNewWarningHeaderIfResponseArrivesStale() throws Exception {
591 originResponse.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
592 originResponse.setHeader("Cache-Control","public, max-age=5");
593 originResponse.setHeader("ETag","\"etag\"");
594
595 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
596
597 final ClassicHttpResponse result = execute(request);
598
599 assertNull(result.getFirstHeader("Warning"));
600 }
601
602 @Test
603 public void testForwardsExistingWarningHeadersOnResponseThatArrivesStale() throws Exception {
604 originResponse.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
605 originResponse.setHeader("Cache-Control","public, max-age=5");
606 originResponse.setHeader("ETag","\"etag\"");
607 originResponse.addHeader("Age","10");
608 final String warning = "110 fred \"Response is stale\"";
609 originResponse.addHeader("Warning",warning);
610
611 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
612
613 final ClassicHttpResponse result = execute(request);
614
615 assertEquals(warning, result.getFirstHeader("Warning").getValue());
616 }
617
618
619
620
621
622
623
624 private void testDoesNotModifyHeaderOnResponses(final String headerName) throws Exception {
625 final String headerValue = HttpTestUtils
626 .getCanonicalHeaderValue(originResponse, headerName);
627 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
628
629 final ClassicHttpResponse result = execute(request);
630
631 assertEquals(headerValue, result.getFirstHeader(headerName).getValue());
632 }
633
634 private void testDoesNotModifyHeaderOnRequests(final String headerName) throws Exception {
635 final String headerValue = HttpTestUtils.getCanonicalHeaderValue(request, headerName);
636 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
637
638 execute(request);
639
640 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
641 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
642
643 assertEquals(headerValue, HttpTestUtils.getCanonicalHeaderValue(reqCapture.getValue(), headerName));
644 }
645
646 @Test
647 public void testDoesNotModifyAcceptRangesOnResponses() throws Exception {
648 final String headerName = "Accept-Ranges";
649 originResponse.setHeader(headerName,"bytes");
650 testDoesNotModifyHeaderOnResponses(headerName);
651 }
652
653 @Test
654 public void testDoesNotModifyAuthorizationOnRequests() throws Exception {
655 request.setHeader("Authorization", StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Q=");
656 testDoesNotModifyHeaderOnRequests("Authorization");
657 }
658
659 @Test
660 public void testDoesNotModifyContentLengthOnRequests() throws Exception {
661 final ClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
662 post.setEntity(HttpTestUtils.makeBody(128));
663 post.setHeader("Content-Length","128");
664 request = post;
665 testDoesNotModifyHeaderOnRequests("Content-Length");
666 }
667
668 @Test
669 public void testDoesNotModifyContentLengthOnResponses() throws Exception {
670 originResponse.setEntity(HttpTestUtils.makeBody(128));
671 originResponse.setHeader("Content-Length","128");
672 testDoesNotModifyHeaderOnResponses("Content-Length");
673 }
674
675 @Test
676 public void testDoesNotModifyContentMD5OnRequests() throws Exception {
677 final ClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
678 post.setEntity(HttpTestUtils.makeBody(128));
679 post.setHeader("Content-Length","128");
680 post.setHeader("Content-MD5","Q2hlY2sgSW50ZWdyaXR5IQ==");
681 request = post;
682 testDoesNotModifyHeaderOnRequests("Content-MD5");
683 }
684
685 @Test
686 public void testDoesNotModifyContentMD5OnResponses() throws Exception {
687 originResponse.setEntity(HttpTestUtils.makeBody(128));
688 originResponse.setHeader("Content-MD5","Q2hlY2sgSW50ZWdyaXR5IQ==");
689 testDoesNotModifyHeaderOnResponses("Content-MD5");
690 }
691
692 @Test
693 public void testDoesNotModifyContentRangeOnRequests() throws Exception {
694 final ClassicHttpRequest put = new BasicClassicHttpRequest("PUT", "/");
695 put.setEntity(HttpTestUtils.makeBody(128));
696 put.setHeader("Content-Length","128");
697 put.setHeader("Content-Range","bytes 0-127/256");
698 request = put;
699 testDoesNotModifyHeaderOnRequests("Content-Range");
700 }
701
702 @Test
703 public void testDoesNotModifyContentRangeOnResponses() throws Exception {
704 request.setHeader("Range","bytes=0-128");
705 originResponse.setCode(HttpStatus.SC_PARTIAL_CONTENT);
706 originResponse.setReasonPhrase("Partial Content");
707 originResponse.setEntity(HttpTestUtils.makeBody(128));
708 originResponse.setHeader("Content-Range","bytes 0-127/256");
709 testDoesNotModifyHeaderOnResponses("Content-Range");
710 }
711
712 @Test
713 public void testDoesNotModifyContentTypeOnRequests() throws Exception {
714 final ClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
715 post.setEntity(HttpTestUtils.makeBody(128));
716 post.setHeader("Content-Length","128");
717 post.setHeader("Content-Type","application/octet-stream");
718 request = post;
719 testDoesNotModifyHeaderOnRequests("Content-Type");
720 }
721
722 @Test
723 public void testDoesNotModifyContentTypeOnResponses() throws Exception {
724 originResponse.setHeader("Content-Type","application/octet-stream");
725 testDoesNotModifyHeaderOnResponses("Content-Type");
726 }
727
728 @Test
729 public void testDoesNotModifyDateOnRequests() throws Exception {
730 request.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
731 testDoesNotModifyHeaderOnRequests("Date");
732 }
733
734 @Test
735 public void testDoesNotModifyDateOnResponses() throws Exception {
736 originResponse.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
737 testDoesNotModifyHeaderOnResponses("Date");
738 }
739
740 @Test
741 public void testDoesNotModifyETagOnResponses() throws Exception {
742 originResponse.setHeader("ETag", "\"random-etag\"");
743 testDoesNotModifyHeaderOnResponses("ETag");
744 }
745
746 @Test
747 public void testDoesNotModifyExpiresOnResponses() throws Exception {
748 originResponse.setHeader("Expires", DateUtils.formatStandardDate(Instant.now()));
749 testDoesNotModifyHeaderOnResponses("Expires");
750 }
751
752 @Test
753 public void testDoesNotModifyFromOnRequests() throws Exception {
754 request.setHeader("From", "foo@example.com");
755 testDoesNotModifyHeaderOnRequests("From");
756 }
757
758 @Test
759 public void testDoesNotModifyIfMatchOnRequests() throws Exception {
760 request = new BasicClassicHttpRequest("DELETE", "/");
761 request.setHeader("If-Match", "\"etag\"");
762 testDoesNotModifyHeaderOnRequests("If-Match");
763 }
764
765 @Test
766 public void testDoesNotModifyIfModifiedSinceOnRequests() throws Exception {
767 request.setHeader("If-Modified-Since", DateUtils.formatStandardDate(Instant.now()));
768 testDoesNotModifyHeaderOnRequests("If-Modified-Since");
769 }
770
771 @Test
772 public void testDoesNotModifyIfNoneMatchOnRequests() throws Exception {
773 request.setHeader("If-None-Match", "\"etag\"");
774 testDoesNotModifyHeaderOnRequests("If-None-Match");
775 }
776
777 @Test
778 public void testDoesNotModifyIfRangeOnRequests() throws Exception {
779 request.setHeader("Range","bytes=0-128");
780 request.setHeader("If-Range", "\"etag\"");
781 testDoesNotModifyHeaderOnRequests("If-Range");
782 }
783
784 @Test
785 public void testDoesNotModifyIfUnmodifiedSinceOnRequests() throws Exception {
786 request = new BasicClassicHttpRequest("DELETE", "/");
787 request.setHeader("If-Unmodified-Since", DateUtils.formatStandardDate(Instant.now()));
788 testDoesNotModifyHeaderOnRequests("If-Unmodified-Since");
789 }
790
791 @Test
792 public void testDoesNotModifyLastModifiedOnResponses() throws Exception {
793 originResponse.setHeader("Last-Modified", DateUtils.formatStandardDate(Instant.now()));
794 testDoesNotModifyHeaderOnResponses("Last-Modified");
795 }
796
797 @Test
798 public void testDoesNotModifyLocationOnResponses() throws Exception {
799 originResponse.setCode(HttpStatus.SC_TEMPORARY_REDIRECT);
800 originResponse.setReasonPhrase("Temporary Redirect");
801 originResponse.setHeader("Location", "http://foo.example.com/bar");
802 testDoesNotModifyHeaderOnResponses("Location");
803 }
804
805 @Test
806 public void testDoesNotModifyRangeOnRequests() throws Exception {
807 request.setHeader("Range", "bytes=0-128");
808 testDoesNotModifyHeaderOnRequests("Range");
809 }
810
811 @Test
812 public void testDoesNotModifyRefererOnRequests() throws Exception {
813 request.setHeader("Referer", "http://foo.example.com/bar");
814 testDoesNotModifyHeaderOnRequests("Referer");
815 }
816
817 @Test
818 public void testDoesNotModifyRetryAfterOnResponses() throws Exception {
819 originResponse.setCode(HttpStatus.SC_SERVICE_UNAVAILABLE);
820 originResponse.setReasonPhrase("Service Unavailable");
821 originResponse.setHeader("Retry-After", "120");
822 testDoesNotModifyHeaderOnResponses("Retry-After");
823 }
824
825 @Test
826 public void testDoesNotModifyServerOnResponses() throws Exception {
827 originResponse.setHeader("Server", "SomeServer/1.0");
828 testDoesNotModifyHeaderOnResponses("Server");
829 }
830
831 @Test
832 public void testDoesNotModifyUserAgentOnRequests() throws Exception {
833 request.setHeader("User-Agent", "MyClient/1.0");
834 testDoesNotModifyHeaderOnRequests("User-Agent");
835 }
836
837 @Test
838 public void testDoesNotModifyVaryOnResponses() throws Exception {
839 request.setHeader("Accept-Encoding","identity");
840 originResponse.setHeader("Vary", "Accept-Encoding");
841 testDoesNotModifyHeaderOnResponses("Vary");
842 }
843
844 @Test
845 public void testDoesNotModifyExtensionHeaderOnRequests() throws Exception {
846 request.setHeader("X-Extension","x-value");
847 testDoesNotModifyHeaderOnRequests("X-Extension");
848 }
849
850 @Test
851 public void testDoesNotModifyExtensionHeaderOnResponses() throws Exception {
852 originResponse.setHeader("X-Extension", "x-value");
853 testDoesNotModifyHeaderOnResponses("X-Extension");
854 }
855
856
857
858
859
860
861
862
863
864 @Test
865 public void testUsesLastModifiedDateForCacheConditionalRequests() throws Exception {
866 final Instant twentySecondsAgo = now.plusSeconds(20);
867 final String lmDate = DateUtils.formatStandardDate(twentySecondsAgo);
868
869 final ClassicHttpRequest req1 =
870 new BasicClassicHttpRequest("GET", "/");
871 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
872 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
873 resp1.setHeader("Last-Modified", lmDate);
874 resp1.setHeader("Cache-Control","max-age=5");
875
876 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
877 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
878
879 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
880
881 execute(req1);
882
883 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
884
885 execute(req2);
886
887 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
888 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
889
890 final ClassicHttpRequest captured = reqCapture.getValue();
891 final Header ifModifiedSince = captured.getFirstHeader("If-Modified-Since");
892 assertEquals(lmDate, ifModifiedSince.getValue());
893 }
894
895
896
897
898
899
900
901
902
903 @Test
904 public void testUsesBothLastModifiedAndETagForConditionalRequestsIfAvailable() throws Exception {
905 final Instant twentySecondsAgo = now.plusSeconds(20);
906 final String lmDate = DateUtils.formatStandardDate(twentySecondsAgo);
907 final String etag = "\"etag\"";
908
909 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
910 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
911 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
912 resp1.setHeader("Last-Modified", lmDate);
913 resp1.setHeader("Cache-Control","max-age=5");
914 resp1.setHeader("ETag", etag);
915
916 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
917 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
918
919 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
920
921 execute(req1);
922
923 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
924
925 execute(req2);
926
927 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
928 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
929
930 final ClassicHttpRequest captured = reqCapture.getValue();
931 final Header ifModifiedSince = captured.getFirstHeader("If-Modified-Since");
932 assertEquals(lmDate, ifModifiedSince.getValue());
933 final Header ifNoneMatch = captured.getFirstHeader("If-None-Match");
934 assertEquals(etag, ifNoneMatch.getValue());
935 }
936
937
938
939
940
941
942
943
944
945 @Test
946 public void testRevalidatesCachedResponseWithExpirationInThePast() throws Exception {
947 final Instant oneSecondAgo = now.minusSeconds(1);
948 final Instant oneSecondFromNow = now.plusSeconds(1);
949 final Instant twoSecondsFromNow = now.plusSeconds(2);
950 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
951 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
952 resp1.setHeader("ETag","\"etag\"");
953 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
954 resp1.setHeader("Expires",DateUtils.formatStandardDate(oneSecondAgo));
955 resp1.setHeader("Cache-Control", "public");
956
957 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
958
959 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
960 final ClassicHttpRequest revalidate = new BasicClassicHttpRequest("GET", "/");
961 revalidate.setHeader("If-None-Match","\"etag\"");
962
963 final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
964 resp2.setHeader("Date", DateUtils.formatStandardDate(twoSecondsFromNow));
965 resp2.setHeader("Expires", DateUtils.formatStandardDate(oneSecondFromNow));
966 resp2.setHeader("ETag","\"etag\"");
967
968 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(revalidate), Mockito.any())).thenReturn(resp2);
969
970 execute(req1);
971 final ClassicHttpResponse result = execute(req2);
972
973 assertEquals(HttpStatus.SC_OK, result.getCode());
974 }
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989 @Test
990 public void testRetriesValidationThatResultsInAnOlderDated304Response() throws Exception {
991 final Instant elevenSecondsAgo = now.minusSeconds(11);
992 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
993 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
994 resp1.setHeader("ETag","\"etag\"");
995 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
996 resp1.setHeader("Cache-Control","max-age=5");
997
998 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
999 final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
1000 resp2.setHeader("ETag","\"etag\"");
1001 resp2.setHeader("Date", DateUtils.formatStandardDate(elevenSecondsAgo));
1002
1003 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1004
1005 execute(req1);
1006
1007 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1008
1009 execute(req2);
1010
1011 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
1012 Mockito.verify(mockExecChain, Mockito.times(3)).proceed(reqCapture.capture(), Mockito.any());
1013
1014 final ClassicHttpRequest captured = reqCapture.getValue();
1015 boolean hasMaxAge0 = false;
1016 boolean hasNoCache = false;
1017 final Iterator<HeaderElement> it = MessageSupport.iterate(captured, HttpHeaders.CACHE_CONTROL);
1018 while (it.hasNext()) {
1019 final HeaderElement elt = it.next();
1020 if ("max-age".equals(elt.getName())) {
1021 try {
1022 final int maxage = Integer.parseInt(elt.getValue());
1023 if (maxage == 0) {
1024 hasMaxAge0 = true;
1025 }
1026 } catch (final NumberFormatException nfe) {
1027
1028 }
1029 } else if ("no-cache".equals(elt.getName())) {
1030 hasNoCache = true;
1031 }
1032 }
1033 assertTrue(hasMaxAge0 || hasNoCache);
1034 assertNull(captured.getFirstHeader("If-None-Match"));
1035 assertNull(captured.getFirstHeader("If-Modified-Since"));
1036 assertNull(captured.getFirstHeader("If-Range"));
1037 assertNull(captured.getFirstHeader("If-Match"));
1038 assertNull(captured.getFirstHeader("If-Unmodified-Since"));
1039 }
1040
1041
1042
1043
1044
1045
1046
1047
1048 @Test
1049 public void testSendsAllVariantEtagsInConditionalRequest() throws Exception {
1050 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET","/");
1051 req1.setHeader("User-Agent","agent1");
1052 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1053 resp1.setHeader("Cache-Control","max-age=3600");
1054 resp1.setHeader("Vary","User-Agent");
1055 resp1.setHeader("Etag","\"etag1\"");
1056
1057 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET","/");
1058 req2.setHeader("User-Agent","agent2");
1059 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1060 resp2.setHeader("Cache-Control","max-age=3600");
1061 resp2.setHeader("Vary","User-Agent");
1062 resp2.setHeader("Etag","\"etag2\"");
1063
1064 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET","/");
1065 req3.setHeader("User-Agent","agent3");
1066 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
1067
1068 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1069
1070 execute(req1);
1071
1072 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1073
1074 execute(req2);
1075
1076 Mockito.when(mockExecChain.proceed(Mockito.any(),Mockito.any())).thenReturn(resp3);
1077
1078 execute(req3);
1079
1080 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
1081 Mockito.verify(mockExecChain, Mockito.times(3)).proceed(reqCapture.capture(), Mockito.any());
1082
1083 final ClassicHttpRequest captured = reqCapture.getValue();
1084 boolean foundEtag1 = false;
1085 boolean foundEtag2 = false;
1086 for(final Header h : captured.getHeaders("If-None-Match")) {
1087 for(final String etag : h.getValue().split(",")) {
1088 if ("\"etag1\"".equals(etag.trim())) {
1089 foundEtag1 = true;
1090 }
1091 if ("\"etag2\"".equals(etag.trim())) {
1092 foundEtag2 = true;
1093 }
1094 }
1095 }
1096 assertTrue(foundEtag1 && foundEtag2);
1097 }
1098
1099
1100
1101
1102
1103
1104
1105
1106 @Test
1107 public void testResponseToExistingVariantsUpdatesEntry() throws Exception {
1108
1109 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1110 req1.setHeader("User-Agent", "agent1");
1111
1112 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1113 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1114 resp1.setHeader("Vary", "User-Agent");
1115 resp1.setHeader("Cache-Control", "max-age=3600");
1116 resp1.setHeader("ETag", "\"etag1\"");
1117
1118 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1119 req2.setHeader("User-Agent", "agent2");
1120
1121 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1122 resp2.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1123 resp2.setHeader("Vary", "User-Agent");
1124 resp2.setHeader("Cache-Control", "max-age=3600");
1125 resp2.setHeader("ETag", "\"etag2\"");
1126
1127 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1128 req3.setHeader("User-Agent", "agent3");
1129
1130 final ClassicHttpResponse resp3 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1131 resp3.setHeader("Date", DateUtils.formatStandardDate(now));
1132 resp3.setHeader("ETag", "\"etag1\"");
1133
1134 final ClassicHttpRequest req4 = new BasicClassicHttpRequest("GET", "/");
1135 req4.setHeader("User-Agent", "agent1");
1136
1137 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1138
1139 execute(req1);
1140
1141 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1142
1143 execute(req2);
1144
1145 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
1146
1147 final ClassicHttpResponse result1 = execute(req3);
1148 final ClassicHttpResponse result2 = execute(req4);
1149
1150 assertEquals(HttpStatus.SC_OK, result1.getCode());
1151 assertEquals("\"etag1\"", result1.getFirstHeader("ETag").getValue());
1152 assertEquals(DateUtils.formatStandardDate(now), result1.getFirstHeader("Date").getValue());
1153 assertEquals(DateUtils.formatStandardDate(now), result2.getFirstHeader("Date").getValue());
1154 }
1155
1156 @Test
1157 public void testResponseToExistingVariantsIsCachedForFutureResponses() throws Exception {
1158
1159 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1160 req1.setHeader("User-Agent", "agent1");
1161
1162 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1163 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1164 resp1.setHeader("Vary", "User-Agent");
1165 resp1.setHeader("Cache-Control", "max-age=3600");
1166 resp1.setHeader("ETag", "\"etag1\"");
1167
1168 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1169
1170 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1171 req2.setHeader("User-Agent", "agent2");
1172
1173 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1174 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
1175 resp2.setHeader("ETag", "\"etag1\"");
1176
1177 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1178
1179 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1180 req3.setHeader("User-Agent", "agent2");
1181
1182 execute(req1);
1183 execute(req2);
1184 execute(req3);
1185 }
1186
1187
1188
1189
1190
1191
1192
1193
1194 @Test
1195 public void variantNegotiationsDoNotIncludeEtagsForPartialResponses() throws Exception {
1196 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1197 req1.setHeader("User-Agent", "agent1");
1198 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1199 resp1.setHeader("Cache-Control", "max-age=3600");
1200 resp1.setHeader("Vary", "User-Agent");
1201 resp1.setHeader("ETag", "\"etag1\"");
1202
1203 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
1204 req2.setHeader("User-Agent", "agent2");
1205 req2.setHeader("Range", "bytes=0-49");
1206 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
1207 resp2.setEntity(HttpTestUtils.makeBody(50));
1208 resp2.setHeader("Content-Length","50");
1209 resp2.setHeader("Content-Range","bytes 0-49/100");
1210 resp2.setHeader("Vary","User-Agent");
1211 resp2.setHeader("ETag", "\"etag2\"");
1212 resp2.setHeader("Cache-Control","max-age=3600");
1213 resp2.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
1214
1215 final ClassicHttpRequest req3 = HttpTestUtils.makeDefaultRequest();
1216 req3.setHeader("User-Agent", "agent3");
1217
1218 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
1219 resp1.setHeader("Cache-Control", "max-age=3600");
1220 resp1.setHeader("Vary", "User-Agent");
1221 resp1.setHeader("ETag", "\"etag3\"");
1222
1223 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1224
1225 execute(req1);
1226
1227 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1228
1229 execute(req2);
1230
1231 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
1232
1233 execute(req3);
1234
1235 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
1236 Mockito.verify(mockExecChain, Mockito.times(3)).proceed(reqCapture.capture(), Mockito.any());
1237
1238 final ClassicHttpRequest captured = reqCapture.getValue();
1239 final Iterator<HeaderElement> it = MessageSupport.iterate(captured, HttpHeaders.IF_NONE_MATCH);
1240 while (it.hasNext()) {
1241 final HeaderElement elt = it.next();
1242 assertNotEquals("\"etag2\"", elt.toString());
1243 }
1244 }
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255 @Test
1256 public void cachedEntryShouldNotBeUsedIfMoreRecentMentionInContentLocation() throws Exception {
1257 final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
1258 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1259 resp1.setHeader("Cache-Control","max-age=3600");
1260 resp1.setHeader("ETag", "\"old-etag\"");
1261 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1262
1263 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1264
1265 final ClassicHttpRequest req2 = new HttpPost("http://foo.example.com/bar");
1266 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1267 resp2.setHeader("ETag", "\"new-etag\"");
1268 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
1269 resp2.setHeader("Content-Location", "http://foo.example.com/");
1270
1271 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1272
1273 final ClassicHttpRequest req3 = new HttpGet("http://foo.example.com");
1274 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
1275
1276 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
1277
1278 execute(req1);
1279 execute(req2);
1280 execute(req3);
1281 }
1282
1283
1284
1285
1286
1287
1288
1289
1290 @Test
1291 public void responseToGetWithQueryFrom1_0OriginAndNoExpiresIsNotCached() throws Exception {
1292 final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/bar?baz=quux");
1293 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
1294 resp2.setVersion(HttpVersion.HTTP_1_0);
1295 resp2.setEntity(HttpTestUtils.makeBody(200));
1296 resp2.setHeader("Content-Length","200");
1297 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
1298
1299 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1300
1301 execute(req2);
1302 }
1303
1304 @Test
1305 public void responseToGetWithQueryFrom1_0OriginVia1_1ProxyAndNoExpiresIsNotCached() throws Exception {
1306 final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/bar?baz=quux");
1307 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
1308 resp2.setVersion(HttpVersion.HTTP_1_0);
1309 resp2.setEntity(HttpTestUtils.makeBody(200));
1310 resp2.setHeader("Content-Length","200");
1311 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
1312 resp2.setHeader("Via","1.0 someproxy");
1313
1314 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1315
1316 execute(req2);
1317 }
1318
1319
1320
1321
1322
1323
1324
1325
1326 @Test
1327 public void shouldInvalidateNonvariantCacheEntryForUnknownMethod() throws Exception {
1328 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1329 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1330 resp1.setHeader("Cache-Control","max-age=3600");
1331
1332 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1333
1334 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("FROB", "/");
1335 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1336 resp2.setHeader("Cache-Control","max-age=3600");
1337
1338 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1339
1340 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1341 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
1342 resp3.setHeader("ETag", "\"etag\"");
1343
1344 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
1345
1346 execute(req1);
1347 execute(req2);
1348 final ClassicHttpResponse result = execute(req3);
1349
1350 assertTrue(HttpTestUtils.semanticallyTransparent(resp3, result));
1351 }
1352
1353 @Test
1354 public void shouldInvalidateAllVariantsForUnknownMethod() throws Exception {
1355 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1356 req1.setHeader("User-Agent", "agent1");
1357 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1358 resp1.setHeader("Cache-Control","max-age=3600");
1359 resp1.setHeader("Vary", "User-Agent");
1360
1361 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1362 req2.setHeader("User-Agent", "agent2");
1363 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1364 resp2.setHeader("Cache-Control","max-age=3600");
1365 resp2.setHeader("Vary", "User-Agent");
1366
1367 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("FROB", "/");
1368 req3.setHeader("User-Agent", "agent3");
1369 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
1370 resp3.setHeader("Cache-Control","max-age=3600");
1371
1372 final ClassicHttpRequest req4 = new BasicClassicHttpRequest("GET", "/");
1373 req4.setHeader("User-Agent", "agent1");
1374 final ClassicHttpResponse resp4 = HttpTestUtils.make200Response();
1375 resp4.setHeader("ETag", "\"etag1\"");
1376
1377 final ClassicHttpRequest req5 = new BasicClassicHttpRequest("GET", "/");
1378 req5.setHeader("User-Agent", "agent2");
1379 final ClassicHttpResponse resp5 = HttpTestUtils.make200Response();
1380 resp5.setHeader("ETag", "\"etag2\"");
1381
1382 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1383
1384 execute(req1);
1385
1386 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1387
1388 execute(req2);
1389
1390 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
1391
1392 execute(req3);
1393
1394 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp4);
1395
1396 final ClassicHttpResponse result4 = execute(req4);
1397
1398 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp5);
1399
1400 final ClassicHttpResponse result5 = execute(req5);
1401
1402 assertTrue(HttpTestUtils.semanticallyTransparent(resp4, result4));
1403 assertTrue(HttpTestUtils.semanticallyTransparent(resp5, result5));
1404 }
1405
1406
1407
1408
1409
1410
1411
1412
1413 @Test
1414 public void cacheShouldUpdateWithNewCacheableResponse() throws Exception {
1415 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1416 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1417 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1418 resp1.setHeader("Cache-Control", "max-age=3600");
1419 resp1.setHeader("ETag", "\"etag1\"");
1420
1421 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1422
1423 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
1424 req2.setHeader("Cache-Control", "max-age=0");
1425 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1426 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
1427 resp2.setHeader("Cache-Control", "max-age=3600");
1428 resp2.setHeader("ETag", "\"etag2\"");
1429
1430 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1431
1432 final ClassicHttpRequest req3 = HttpTestUtils.makeDefaultRequest();
1433
1434 execute(req1);
1435 execute(req2);
1436 final ClassicHttpResponse result = execute(req3);
1437
1438 assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
1439 }
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452 @Test
1453 public void expiresEqualToDateWithNoCacheControlIsNotCacheable() throws Exception {
1454 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1455 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1456 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
1457 resp1.setHeader("Expires", DateUtils.formatStandardDate(now));
1458 resp1.removeHeaders("Cache-Control");
1459
1460 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1461
1462 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
1463 req2.setHeader("Cache-Control", "max-stale=1000");
1464 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1465 resp2.setHeader("ETag", "\"etag2\"");
1466
1467 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1468
1469 execute(req1);
1470 final ClassicHttpResponse result = execute(req2);
1471
1472 assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
1473 }
1474
1475 @Test
1476 public void expiresPriorToDateWithNoCacheControlIsNotCacheable() throws Exception {
1477 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1478 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1479 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
1480 resp1.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsAgo));
1481 resp1.removeHeaders("Cache-Control");
1482
1483 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1484
1485 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
1486 req2.setHeader("Cache-Control", "max-stale=1000");
1487 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1488 resp2.setHeader("ETag", "\"etag2\"");
1489
1490 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1491
1492 execute(req1);
1493 final ClassicHttpResponse result = execute(req2);
1494
1495 assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
1496 }
1497
1498
1499
1500
1501
1502
1503
1504 @Test
1505 public void otherFreshnessRequestDirectivesNotAllowedWithNoCache() throws Exception {
1506 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1507 req1.setHeader("Cache-Control", "min-fresh=10, no-cache");
1508 req1.addHeader("Cache-Control", "max-stale=0, max-age=0");
1509
1510 execute(req1);
1511
1512 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
1513 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
1514
1515 final ClassicHttpRequest captured = reqCapture.getValue();
1516 boolean foundNoCache = false;
1517 boolean foundDisallowedDirective = false;
1518 final List<String> disallowed =
1519 Arrays.asList("min-fresh", "max-stale", "max-age");
1520 final Iterator<HeaderElement> it = MessageSupport.iterate(captured, HttpHeaders.CACHE_CONTROL);
1521 while (it.hasNext()) {
1522 final HeaderElement elt = it.next();
1523 if (disallowed.contains(elt.getName())) {
1524 foundDisallowedDirective = true;
1525 }
1526 if ("no-cache".equals(elt.getName())) {
1527 foundNoCache = true;
1528 }
1529 }
1530 assertTrue(foundNoCache);
1531 assertFalse(foundDisallowedDirective);
1532 }
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543 @Test
1544 public void cacheMissResultsIn504WithOnlyIfCached() throws Exception {
1545 final ClassicHttpRequest req = HttpTestUtils.makeDefaultRequest();
1546 req.setHeader("Cache-Control", "only-if-cached");
1547
1548 final ClassicHttpResponse result = execute(req);
1549
1550 assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, result.getCode());
1551 }
1552
1553 @Test
1554 public void cacheHitOkWithOnlyIfCached() throws Exception {
1555 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1556 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1557 resp1.setHeader("Cache-Control","max-age=3600");
1558
1559 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1560
1561 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
1562 req2.setHeader("Cache-Control", "only-if-cached");
1563
1564 execute(req1);
1565 final ClassicHttpResponse result = execute(req2);
1566
1567 assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result));
1568 }
1569
1570 @Test
1571 public void returns504ForStaleEntryWithOnlyIfCached() throws Exception {
1572 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1573 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1574 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1575 resp1.setHeader("Cache-Control","max-age=5");
1576
1577 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1578
1579 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
1580 req2.setHeader("Cache-Control", "only-if-cached");
1581
1582 execute(req1);
1583 final ClassicHttpResponse result = execute(req2);
1584
1585 assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, result.getCode());
1586 }
1587
1588 @Test
1589 public void returnsStaleCacheEntryWithOnlyIfCachedAndMaxStale() throws Exception {
1590
1591 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1592 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1593 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1594 resp1.setHeader("Cache-Control","max-age=5");
1595
1596 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1597
1598 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
1599 req2.setHeader("Cache-Control", "max-stale=20, only-if-cached");
1600
1601 execute(req1);
1602 final ClassicHttpResponse result = execute(req2);
1603
1604 assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result));
1605 }
1606
1607 @Test
1608 public void issues304EvenWithWeakETag() throws Exception {
1609 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1610 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1611 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1612 resp1.setHeader("Cache-Control", "max-age=300");
1613 resp1.setHeader("ETag","W/\"weak-sauce\"");
1614
1615 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1616
1617 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
1618 req2.setHeader("If-None-Match","W/\"weak-sauce\"");
1619
1620 execute(req1);
1621 final ClassicHttpResponse result = execute(req2);
1622
1623 assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
1624 }
1625
1626 }