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