1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.client5.http.impl.cache;
28
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.net.SocketTimeoutException;
32 import java.time.Instant;
33 import java.time.temporal.ChronoUnit;
34 import java.util.Arrays;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Random;
38 import java.util.regex.Matcher;
39 import java.util.regex.Pattern;
40
41 import org.apache.hc.client5.http.ClientProtocolException;
42 import org.apache.hc.client5.http.HttpRoute;
43 import org.apache.hc.client5.http.auth.StandardAuthScheme;
44 import org.apache.hc.client5.http.cache.HttpCacheEntry;
45 import org.apache.hc.client5.http.classic.ExecChain;
46 import org.apache.hc.client5.http.classic.ExecRuntime;
47 import org.apache.hc.client5.http.protocol.HttpClientContext;
48 import org.apache.hc.client5.http.utils.DateUtils;
49 import org.apache.hc.core5.http.ClassicHttpRequest;
50 import org.apache.hc.core5.http.ClassicHttpResponse;
51 import org.apache.hc.core5.http.Header;
52 import org.apache.hc.core5.http.HeaderElement;
53 import org.apache.hc.core5.http.HeaderElements;
54 import org.apache.hc.core5.http.HttpEntity;
55 import org.apache.hc.core5.http.HttpException;
56 import org.apache.hc.core5.http.HttpHeaders;
57 import org.apache.hc.core5.http.HttpHost;
58 import org.apache.hc.core5.http.HttpStatus;
59 import org.apache.hc.core5.http.HttpVersion;
60 import org.apache.hc.core5.http.ProtocolVersion;
61 import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
62 import org.apache.hc.core5.http.io.entity.StringEntity;
63 import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
64 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
65 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
66 import org.apache.hc.core5.http.message.BasicHeader;
67 import org.apache.hc.core5.http.message.MessageSupport;
68 import org.junit.jupiter.api.Assertions;
69 import org.junit.jupiter.api.BeforeEach;
70 import org.junit.jupiter.api.Test;
71 import org.mockito.ArgumentCaptor;
72 import org.mockito.Mock;
73 import org.mockito.Mockito;
74 import org.mockito.MockitoAnnotations;
75
76
77
78
79
80
81
82
83
84
85 public class TestProtocolRequirements {
86
87 static final int MAX_BYTES = 1024;
88 static final int MAX_ENTRIES = 100;
89 static final int ENTITY_LENGTH = 128;
90
91 HttpHost host;
92 HttpRoute route;
93 HttpEntity body;
94 HttpClientContext context;
95 @Mock
96 ExecChain mockExecChain;
97 @Mock
98 ExecRuntime mockExecRuntime;
99 @Mock
100 HttpCache mockCache;
101 ClassicHttpRequest request;
102 ClassicHttpResponse originResponse;
103 CacheConfig config;
104 CachingExec impl;
105 HttpCache cache;
106
107 @BeforeEach
108 public void setUp() throws Exception {
109 MockitoAnnotations.openMocks(this);
110 host = new HttpHost("foo.example.com", 80);
111
112 route = new HttpRoute(host);
113
114 body = HttpTestUtils.makeBody(ENTITY_LENGTH);
115
116 request = new BasicClassicHttpRequest("GET", "/foo");
117
118 context = HttpClientContext.create();
119
120 originResponse = HttpTestUtils.make200Response();
121
122 config = CacheConfig.custom()
123 .setMaxCacheEntries(MAX_ENTRIES)
124 .setMaxObjectSize(MAX_BYTES)
125 .build();
126
127 cache = new BasicHttpCache(config);
128 impl = new CachingExec(cache, null, config);
129
130 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
131 }
132
133 public ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
134 return impl.execute(
135 ClassicRequestBuilder.copy(request).build(),
136 new ExecChain.Scope("test", route, request, mockExecRuntime, context),
137 mockExecChain);
138 }
139
140 @Test
141 public void testCacheMissOnGETUsesOriginResponse() throws Exception {
142
143 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(request), Mockito.any())).thenReturn(originResponse);
144
145 final ClassicHttpResponse result = execute(request);
146
147 Assertions.assertTrue(HttpTestUtils.semanticallyTransparent(originResponse, result));
148 }
149
150
151
152
153
154
155
156
157
158
159
160
161 @Test
162 public void testHigherMajorProtocolVersionsOnRequestSwitchToTunnelBehavior() throws Exception {
163
164
165
166 request = new BasicClassicHttpRequest("GET", "/foo");
167 request.setVersion(new ProtocolVersion("HTTP", 2, 13));
168
169 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(request), Mockito.any())).thenReturn(originResponse);
170
171 final ClassicHttpResponse result = execute(request);
172
173 Assertions.assertSame(originResponse, result);
174 }
175
176 @Test
177 public void testHigher1_XProtocolVersionsDowngradeTo1_1() throws Exception {
178
179 request = new BasicClassicHttpRequest("GET", "/foo");
180 request.setVersion(new ProtocolVersion("HTTP", 1, 2));
181
182 final ClassicHttpRequest downgraded = new BasicClassicHttpRequest("GET", "/foo");
183 downgraded.setVersion(HttpVersion.HTTP_1_1);
184
185 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(downgraded), Mockito.any())).thenReturn(originResponse);
186
187 final ClassicHttpResponse result = execute(request);
188
189 Assertions.assertTrue(HttpTestUtils.semanticallyTransparent(originResponse, result));
190 }
191
192
193
194
195
196
197
198
199
200
201 @Test
202 public void testRequestsWithLowerProtocolVersionsGetUpgradedTo1_1() throws Exception {
203
204 request = new BasicClassicHttpRequest("GET", "/foo");
205 request.setVersion(new ProtocolVersion("HTTP", 1, 0));
206 final ClassicHttpRequest upgraded = new BasicClassicHttpRequest("GET", "/foo");
207 upgraded.setVersion(HttpVersion.HTTP_1_1);
208
209 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(upgraded), Mockito.any())).thenReturn(originResponse);
210
211 final ClassicHttpResponse result = execute(request);
212
213 Assertions.assertTrue(HttpTestUtils.semanticallyTransparent(originResponse, result));
214 }
215
216
217
218
219
220
221
222
223
224 @Test
225 public void testLowerOriginResponsesUpgradedToOurVersion1_1() throws Exception {
226 originResponse = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
227 originResponse.setVersion(new ProtocolVersion("HTTP", 1, 2));
228 originResponse.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
229 originResponse.setHeader("Server", "MockOrigin/1.0");
230 originResponse.setEntity(body);
231
232
233
234 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
235
236 final ClassicHttpResponse result = execute(request);
237
238 Assertions.assertEquals(HttpVersion.HTTP_1_1, result.getVersion());
239 }
240
241 @Test
242 public void testResponseToA1_0RequestShouldUse1_1() throws Exception {
243 request = new BasicClassicHttpRequest("GET", "/foo");
244 request.setVersion(new ProtocolVersion("HTTP", 1, 0));
245
246 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
247
248 final ClassicHttpResponse result = execute(request);
249
250 Assertions.assertEquals(HttpVersion.HTTP_1_1, result.getVersion());
251 }
252
253
254
255
256
257 @Test
258 public void testForwardsUnknownHeadersOnRequestsFromHigherProtocolVersions() throws Exception {
259 request = new BasicClassicHttpRequest("GET", "/foo");
260 request.setVersion(new ProtocolVersion("HTTP", 1, 2));
261 request.removeHeaders("Connection");
262 request.addHeader("X-Unknown-Header", "some-value");
263
264 final ClassicHttpRequest downgraded = new BasicClassicHttpRequest("GET", "/foo");
265 downgraded.setVersion(HttpVersion.HTTP_1_1);
266 downgraded.removeHeaders("Connection");
267 downgraded.addHeader("X-Unknown-Header", "some-value");
268
269 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(downgraded), Mockito.any())).thenReturn(originResponse);
270
271 execute(request);
272 }
273
274
275
276
277
278 @Test
279 public void testTransferCodingsAreNotSentToAnHTTP_1_0Client() throws Exception {
280
281 originResponse.setHeader("Transfer-Encoding", "identity");
282
283 final ClassicHttpRequest originalRequest = new BasicClassicHttpRequest("GET", "/foo");
284 originalRequest.setVersion(new ProtocolVersion("HTTP", 1, 0));
285
286 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
287
288 final ClassicHttpResponse result = execute(originalRequest);
289
290 Assertions.assertNull(result.getFirstHeader("TE"));
291 Assertions.assertNull(result.getFirstHeader("Transfer-Encoding"));
292 }
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307 private void testOrderOfMultipleHeadersIsPreservedOnRequests(final String h, final ClassicHttpRequest request) throws Exception {
308 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
309
310 execute(request);
311
312 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
313 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
314
315 final ClassicHttpRequest forwarded = reqCapture.getValue();
316 final String expected = HttpTestUtils.getCanonicalHeaderValue(request, h);
317 final String actual = HttpTestUtils.getCanonicalHeaderValue(forwarded, h);
318 if (!actual.contains(expected)) {
319 Assertions.assertEquals(expected, actual);
320 }
321 }
322
323 @Test
324 public void testOrderOfMultipleAcceptHeaderValuesIsPreservedOnRequests() throws Exception {
325 request.addHeader("Accept", "audio/*; q=0.2, audio/basic");
326 request.addHeader("Accept", "text/*, text/html, text/html;level=1, */*");
327 testOrderOfMultipleHeadersIsPreservedOnRequests("Accept", request);
328 }
329
330 @Test
331 public void testOrderOfMultipleAcceptCharsetHeadersIsPreservedOnRequests() throws Exception {
332 request.addHeader("Accept-Charset", "iso-8859-5");
333 request.addHeader("Accept-Charset", "unicode-1-1;q=0.8");
334 testOrderOfMultipleHeadersIsPreservedOnRequests("Accept-Charset", request);
335 }
336
337 @Test
338 public void testOrderOfMultipleAcceptEncodingHeadersIsPreservedOnRequests() throws Exception {
339 request.addHeader("Accept-Encoding", "identity");
340 request.addHeader("Accept-Encoding", "compress, gzip");
341 testOrderOfMultipleHeadersIsPreservedOnRequests("Accept-Encoding", request);
342 }
343
344 @Test
345 public void testOrderOfMultipleAcceptLanguageHeadersIsPreservedOnRequests() throws Exception {
346 request.addHeader("Accept-Language", "da, en-gb;q=0.8, en;q=0.7");
347 request.addHeader("Accept-Language", "i-cherokee");
348 testOrderOfMultipleHeadersIsPreservedOnRequests("Accept-Encoding", request);
349 }
350
351 @Test
352 public void testOrderOfMultipleAllowHeadersIsPreservedOnRequests() throws Exception {
353 final BasicClassicHttpRequest put = new BasicClassicHttpRequest("PUT", "/");
354 put.setEntity(body);
355 put.addHeader("Allow", "GET, HEAD");
356 put.addHeader("Allow", "DELETE");
357 put.addHeader("Content-Length", "128");
358 testOrderOfMultipleHeadersIsPreservedOnRequests("Allow", put);
359 }
360
361 @Test
362 public void testOrderOfMultipleCacheControlHeadersIsPreservedOnRequests() throws Exception {
363 request.addHeader("Cache-Control", "max-age=5");
364 request.addHeader("Cache-Control", "min-fresh=10");
365 testOrderOfMultipleHeadersIsPreservedOnRequests("Cache-Control", request);
366 }
367
368 @Test
369 public void testOrderOfMultipleContentEncodingHeadersIsPreservedOnRequests() throws Exception {
370 final BasicClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
371 post.setEntity(body);
372 post.addHeader("Content-Encoding", "gzip");
373 post.addHeader("Content-Encoding", "compress");
374 post.addHeader("Content-Length", "128");
375 testOrderOfMultipleHeadersIsPreservedOnRequests("Content-Encoding", post);
376 }
377
378 @Test
379 public void testOrderOfMultipleContentLanguageHeadersIsPreservedOnRequests() throws Exception {
380 final BasicClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
381 post.setEntity(body);
382 post.addHeader("Content-Language", "mi");
383 post.addHeader("Content-Language", "en");
384 post.addHeader("Content-Length", "128");
385 testOrderOfMultipleHeadersIsPreservedOnRequests("Content-Language", post);
386 }
387
388 @Test
389 public void testOrderOfMultipleExpectHeadersIsPreservedOnRequests() throws Exception {
390 final BasicClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
391 post.setEntity(body);
392 post.addHeader("Expect", "100-continue");
393 post.addHeader("Expect", "x-expect=true");
394 post.addHeader("Content-Length", "128");
395 testOrderOfMultipleHeadersIsPreservedOnRequests("Expect", post);
396 }
397
398 @Test
399 public void testOrderOfMultiplePragmaHeadersIsPreservedOnRequests() throws Exception {
400 request.addHeader("Pragma", "no-cache");
401 request.addHeader("Pragma", "x-pragma-1, x-pragma-2");
402 testOrderOfMultipleHeadersIsPreservedOnRequests("Pragma", request);
403 }
404
405 @Test
406 public void testOrderOfMultipleViaHeadersIsPreservedOnRequests() throws Exception {
407 request.addHeader("Via", "1.0 fred, 1.1 nowhere.com (Apache/1.1)");
408 request.addHeader("Via", "1.0 ricky, 1.1 mertz, 1.0 lucy");
409 testOrderOfMultipleHeadersIsPreservedOnRequests("Via", request);
410 }
411
412 @Test
413 public void testOrderOfMultipleWarningHeadersIsPreservedOnRequests() throws Exception {
414 request.addHeader("Warning", "199 fred \"bargle\"");
415 request.addHeader("Warning", "199 barney \"bungle\"");
416 testOrderOfMultipleHeadersIsPreservedOnRequests("Warning", request);
417 }
418
419 private void testOrderOfMultipleHeadersIsPreservedOnResponses(final String h) throws Exception {
420 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
421
422 final ClassicHttpResponse result = execute(request);
423
424 Assertions.assertNotNull(result);
425 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(originResponse, h), HttpTestUtils
426 .getCanonicalHeaderValue(result, h));
427
428 }
429
430 @Test
431 public void testOrderOfMultipleAllowHeadersIsPreservedOnResponses() throws Exception {
432 originResponse = new BasicClassicHttpResponse(405, "Method Not Allowed");
433 originResponse.addHeader("Allow", "HEAD");
434 originResponse.addHeader("Allow", "DELETE");
435 testOrderOfMultipleHeadersIsPreservedOnResponses("Allow");
436 }
437
438 @Test
439 public void testOrderOfMultipleCacheControlHeadersIsPreservedOnResponses() throws Exception {
440 originResponse.addHeader("Cache-Control", "max-age=0");
441 originResponse.addHeader("Cache-Control", "no-store, must-revalidate");
442 testOrderOfMultipleHeadersIsPreservedOnResponses("Cache-Control");
443 }
444
445 @Test
446 public void testOrderOfMultipleContentEncodingHeadersIsPreservedOnResponses() throws Exception {
447 originResponse.addHeader("Content-Encoding", "gzip");
448 originResponse.addHeader("Content-Encoding", "compress");
449 testOrderOfMultipleHeadersIsPreservedOnResponses("Content-Encoding");
450 }
451
452 @Test
453 public void testOrderOfMultipleContentLanguageHeadersIsPreservedOnResponses() throws Exception {
454 originResponse.addHeader("Content-Language", "mi");
455 originResponse.addHeader("Content-Language", "en");
456 testOrderOfMultipleHeadersIsPreservedOnResponses("Content-Language");
457 }
458
459 @Test
460 public void testOrderOfMultiplePragmaHeadersIsPreservedOnResponses() throws Exception {
461 originResponse.addHeader("Pragma", "no-cache, x-pragma-2");
462 originResponse.addHeader("Pragma", "x-pragma-1");
463 testOrderOfMultipleHeadersIsPreservedOnResponses("Pragma");
464 }
465
466 @Test
467 public void testOrderOfMultipleViaHeadersIsPreservedOnResponses() throws Exception {
468 originResponse.addHeader("Via", "1.0 fred, 1.1 nowhere.com (Apache/1.1)");
469 originResponse.addHeader("Via", "1.0 ricky, 1.1 mertz, 1.0 lucy");
470 testOrderOfMultipleHeadersIsPreservedOnResponses("Via");
471 }
472
473 @Test
474 public void testOrderOfMultipleWWWAuthenticateHeadersIsPreservedOnResponses() throws Exception {
475 originResponse.addHeader("WWW-Authenticate", "x-challenge-1");
476 originResponse.addHeader("WWW-Authenticate", "x-challenge-2");
477 testOrderOfMultipleHeadersIsPreservedOnResponses("WWW-Authenticate");
478 }
479
480
481
482
483
484
485
486
487
488 private void testUnknownResponseStatusCodeIsNotCached(final int code) throws Exception {
489
490 originResponse = new BasicClassicHttpResponse(code, "Moo");
491 originResponse.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
492 originResponse.setHeader("Server", "MockOrigin/1.0");
493 originResponse.setHeader("Cache-Control", "max-age=3600");
494 originResponse.setEntity(body);
495
496 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
497
498 execute(request);
499
500
501 Mockito.verifyNoInteractions(mockCache);
502 }
503
504 @Test
505 public void testUnknownResponseStatusCodesAreNotCached() throws Exception {
506 for (int i = 102; i <= 199; i++) {
507 testUnknownResponseStatusCodeIsNotCached(i);
508 }
509 for (int i = 207; i <= 299; i++) {
510 testUnknownResponseStatusCodeIsNotCached(i);
511 }
512 for (int i = 308; i <= 399; i++) {
513 testUnknownResponseStatusCodeIsNotCached(i);
514 }
515 for (int i = 418; i <= 499; i++) {
516 testUnknownResponseStatusCodeIsNotCached(i);
517 }
518 for (int i = 506; i <= 999; i++) {
519 testUnknownResponseStatusCodeIsNotCached(i);
520 }
521 }
522
523
524
525
526
527
528
529 @Test
530 public void testUnknownHeadersOnRequestsAreForwarded() throws Exception {
531 request.addHeader("X-Unknown-Header", "blahblah");
532 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
533
534 execute(request);
535
536 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
537 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
538 final ClassicHttpRequest forwarded = reqCapture.getValue();
539 final Header[] hdrs = forwarded.getHeaders("X-Unknown-Header");
540 Assertions.assertEquals(1, hdrs.length);
541 Assertions.assertEquals("blahblah", hdrs[0].getValue());
542 }
543
544 @Test
545 public void testUnknownHeadersOnResponsesAreForwarded() throws Exception {
546 originResponse.addHeader("X-Unknown-Header", "blahblah");
547 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
548
549 final ClassicHttpResponse result = execute(request);
550
551 final Header[] hdrs = result.getHeaders("X-Unknown-Header");
552 Assertions.assertEquals(1, hdrs.length);
553 Assertions.assertEquals("blahblah", hdrs[0].getValue());
554 }
555
556
557
558
559
560
561
562
563 @Test
564 public void testRequestsExpecting100ContinueBehaviorShouldSetExpectHeader() throws Exception {
565 final BasicClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
566 post.setHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE);
567 post.setHeader("Content-Length", "128");
568 post.setEntity(new StringEntity("whatever"));
569
570 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
571
572 execute(post);
573
574 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
575 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
576 final ClassicHttpRequest forwarded = reqCapture.getValue();
577 boolean foundExpect = false;
578 final Iterator<HeaderElement> it = MessageSupport.iterate(forwarded, HttpHeaders.EXPECT);
579 while (it.hasNext()) {
580 final HeaderElement elt = it.next();
581 if ("100-continue".equalsIgnoreCase(elt.getName())) {
582 foundExpect = true;
583 break;
584 }
585 }
586 Assertions.assertTrue(foundExpect);
587 }
588
589
590
591
592
593
594
595
596 @Test
597 public void testRequestsNotExpecting100ContinueBehaviorShouldNotSetExpectContinueHeader() throws Exception {
598 final BasicClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
599 post.setHeader("Content-Length", "128");
600 post.setEntity(new StringEntity("whatever"));
601
602 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
603
604 execute(post);
605
606 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
607 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
608 final ClassicHttpRequest forwarded = reqCapture.getValue();
609 boolean foundExpect = false;
610 final Iterator<HeaderElement> it = MessageSupport.iterate(forwarded, HttpHeaders.EXPECT);
611 while (it.hasNext()) {
612 final HeaderElement elt = it.next();
613 if ("100-continue".equalsIgnoreCase(elt.getName())) {
614 foundExpect = true;
615 break;
616 }
617 }
618 Assertions.assertFalse(foundExpect);
619 }
620
621
622
623
624
625
626
627
628
629
630 @Test
631 public void testExpectHeadersAreForwardedOnRequests() throws Exception {
632
633
634
635
636
637
638
639 }
640
641
642
643
644
645
646
647
648
649
650 @Test
651 public void test100ContinueResponsesAreNotForwardedTo1_0ClientsWhoDidNotAskForThem() throws Exception {
652
653 final BasicClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
654 post.setVersion(new ProtocolVersion("HTTP", 1, 0));
655 post.setEntity(body);
656 post.setHeader("Content-Length", "128");
657
658 originResponse = new BasicClassicHttpResponse(100, "Continue");
659 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
660
661
662
663 Assertions.assertThrows(ClientProtocolException.class, () -> execute(post));
664 }
665
666
667
668
669
670
671 @Test
672 public void testResponsesToOPTIONSAreNotCacheable() throws Exception {
673 request = new BasicClassicHttpRequest("OPTIONS", "/");
674 originResponse.addHeader("Cache-Control", "max-age=3600");
675
676 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
677
678 execute(request);
679
680 Mockito.verifyNoInteractions(mockCache);
681 }
682
683
684
685
686
687
688
689 @Test
690 public void test200ResponseToOPTIONSWithNoBodyShouldIncludeContentLengthZero() throws Exception {
691
692 request = new BasicClassicHttpRequest("OPTIONS", "/");
693 originResponse.setEntity(null);
694 originResponse.setHeader("Content-Length", "0");
695
696 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
697
698 final ClassicHttpResponse result = execute(request);
699
700 final Header contentLength = result.getFirstHeader("Content-Length");
701 Assertions.assertNotNull(contentLength);
702 Assertions.assertEquals("0", contentLength.getValue());
703 }
704
705
706
707
708
709
710
711
712
713
714 @Test
715 public void testDoesNotForwardOPTIONSWhenMaxForwardsIsZeroOnAbsoluteURIRequest() throws Exception {
716 request = new BasicClassicHttpRequest("OPTIONS", "*");
717 request.setHeader("Max-Forwards", "0");
718
719 execute(request);
720 }
721
722
723
724
725
726
727
728 @Test
729 public void testDecrementsMaxForwardsWhenForwardingOPTIONSRequest() throws Exception {
730
731 request = new BasicClassicHttpRequest("OPTIONS", "*");
732 request.setHeader("Max-Forwards", "7");
733
734 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
735
736 execute(request);
737
738 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
739 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
740
741 final ClassicHttpRequest captured = reqCapture.getValue();
742 Assertions.assertEquals("6", captured.getFirstHeader("Max-Forwards").getValue());
743 }
744
745
746
747
748
749
750
751 @Test
752 public void testDoesNotAddAMaxForwardsHeaderToForwardedOPTIONSRequests() throws Exception {
753 request = new BasicClassicHttpRequest("OPTIONS", "/");
754 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
755
756 execute(request);
757
758 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
759 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
760
761 final ClassicHttpRequest forwarded = reqCapture.getValue();
762 Assertions.assertNull(forwarded.getFirstHeader("Max-Forwards"));
763 }
764
765
766
767
768
769
770
771 @Test
772 public void testResponseToAHEADRequestMustNotHaveABody() throws Exception {
773 request = new BasicClassicHttpRequest("HEAD", "/");
774 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
775
776 final ClassicHttpResponse result = execute(request);
777
778 Assertions.assertTrue(result.getEntity() == null || result.getEntity().getContentLength() == 0);
779 }
780
781
782
783
784
785
786
787
788
789 private void testHEADResponseWithUpdatedEntityFieldsMakeACacheEntryStale(final String eHeader,
790 final String oldVal, final String newVal) throws Exception {
791
792
793 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
794 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
795 resp1.addHeader("Cache-Control", "max-age=3600");
796 resp1.setHeader(eHeader, oldVal);
797
798
799 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("HEAD", "/");
800 req2.addHeader("Cache-Control", "no-cache");
801 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
802 resp2.setEntity(null);
803 resp2.setHeader(eHeader, newVal);
804
805
806 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
807 req3.addHeader("Cache-Control", "max-stale=0");
808 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
809 resp3.setHeader(eHeader, newVal);
810
811 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(req1), Mockito.any())).thenReturn(originResponse);
812 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(req2), Mockito.any())).thenReturn(originResponse);
813 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(req3), Mockito.any())).thenReturn(resp3);
814
815 execute(req1);
816 execute(req2);
817 execute(req3);
818 }
819
820 @Test
821 public void testHEADResponseWithUpdatedContentLengthFieldMakeACacheEntryStale() throws Exception {
822 testHEADResponseWithUpdatedEntityFieldsMakeACacheEntryStale("Content-Length", "128", "127");
823 }
824
825 @Test
826 public void testHEADResponseWithUpdatedContentMD5FieldMakeACacheEntryStale() throws Exception {
827 testHEADResponseWithUpdatedEntityFieldsMakeACacheEntryStale("Content-MD5",
828 "Q2hlY2sgSW50ZWdyaXR5IQ==", "Q2hlY2sgSW50ZWdyaXR5IR==");
829
830 }
831
832 @Test
833 public void testHEADResponseWithUpdatedETagFieldMakeACacheEntryStale() throws Exception {
834 testHEADResponseWithUpdatedEntityFieldsMakeACacheEntryStale("ETag", "\"etag1\"",
835 "\"etag2\"");
836 }
837
838 @Test
839 public void testHEADResponseWithUpdatedLastModifiedFieldMakeACacheEntryStale() throws Exception {
840 final Instant now = Instant.now();
841 final Instant tenSecondsAgo = now.minusSeconds(10);
842 final Instant sixSecondsAgo = now.minusSeconds(6);
843 testHEADResponseWithUpdatedEntityFieldsMakeACacheEntryStale("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo), DateUtils.formatStandardDate(sixSecondsAgo));
844 }
845
846
847
848
849
850
851
852 @Test
853 public void testResponsesToPOSTWithoutCacheControlOrExpiresAreNotCached() throws Exception {
854
855 final BasicClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
856 post.setHeader("Content-Length", "128");
857 post.setEntity(HttpTestUtils.makeBody(128));
858
859 originResponse.removeHeaders("Cache-Control");
860 originResponse.removeHeaders("Expires");
861
862 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
863
864 execute(post);
865
866 Mockito.verifyNoInteractions(mockCache);
867 }
868
869
870
871
872
873
874 @Test
875 public void testResponsesToPUTsAreNotCached() throws Exception {
876
877 final BasicClassicHttpRequest put = new BasicClassicHttpRequest("PUT", "/");
878 put.setEntity(HttpTestUtils.makeBody(128));
879 put.addHeader("Content-Length", "128");
880
881 originResponse.setHeader("Cache-Control", "max-age=3600");
882
883 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
884
885 execute(put);
886
887 Mockito.verifyNoInteractions(mockCache);
888 }
889
890
891
892
893
894
895 @Test
896 public void testResponsesToDELETEsAreNotCached() throws Exception {
897
898 request = new BasicClassicHttpRequest("DELETE", "/");
899 originResponse.setHeader("Cache-Control", "max-age=3600");
900
901 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
902
903 execute(request);
904
905 Mockito.verifyNoInteractions(mockCache);
906 }
907
908
909
910
911
912
913 @Test
914 public void testResponsesToTRACEsAreNotCached() throws Exception {
915
916 request = new BasicClassicHttpRequest("TRACE", "/");
917 originResponse.setHeader("Cache-Control", "max-age=3600");
918
919 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
920
921 execute(request);
922
923 Mockito.verifyNoInteractions(mockCache);
924 }
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945 @Test
946 public void test206ResponseGeneratedFromCacheMustHaveContentRangeOrMultipartByteRangesContentType() throws Exception {
947
948 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
949 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
950 resp1.setHeader("ETag", "\"etag\"");
951 resp1.setHeader("Cache-Control", "max-age=3600");
952
953 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
954 req2.setHeader("Range", "bytes=0-50");
955
956 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
957
958 execute(req1);
959 final ClassicHttpResponse result = execute(req2);
960
961 if (HttpStatus.SC_PARTIAL_CONTENT == result.getCode()) {
962 if (result.getFirstHeader("Content-Range") == null) {
963 final HeaderElement elt = MessageSupport.parse(result.getFirstHeader("Content-Type"))[0];
964 Assertions.assertTrue("multipart/byteranges".equalsIgnoreCase(elt.getName()));
965 Assertions.assertNotNull(elt.getParameterByName("boundary"));
966 Assertions.assertNotNull(elt.getParameterByName("boundary").getValue());
967 Assertions.assertNotEquals("", elt.getParameterByName("boundary").getValue().trim());
968 }
969 }
970 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
971 }
972
973 @Test
974 public void test206ResponseGeneratedFromCacheMustHaveABodyThatMatchesContentLengthHeaderIfPresent() throws Exception {
975
976 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
977 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
978 resp1.setHeader("ETag", "\"etag\"");
979 resp1.setHeader("Cache-Control", "max-age=3600");
980
981 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
982 req2.setHeader("Range", "bytes=0-50");
983
984 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
985
986 execute(req1);
987 final ClassicHttpResponse result = execute(req2);
988
989 if (HttpStatus.SC_PARTIAL_CONTENT == result.getCode()) {
990 final Header h = result.getFirstHeader("Content-Length");
991 if (h != null) {
992 final int contentLength = Integer.parseInt(h.getValue());
993 int bytesRead = 0;
994 final InputStream i = result.getEntity().getContent();
995 while ((i.read()) != -1) {
996 bytesRead++;
997 }
998 i.close();
999 Assertions.assertEquals(contentLength, bytesRead);
1000 }
1001 }
1002 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
1003 }
1004
1005 @Test
1006 public void test206ResponseGeneratedFromCacheMustHaveDateHeader() throws Exception {
1007 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1008 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1009 resp1.setHeader("ETag", "\"etag\"");
1010 resp1.setHeader("Cache-Control", "max-age=3600");
1011
1012 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1013 req2.setHeader("Range", "bytes=0-50");
1014
1015 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1016
1017 execute(req1);
1018 final ClassicHttpResponse result = execute(req2);
1019
1020 if (HttpStatus.SC_PARTIAL_CONTENT == result.getCode()) {
1021 Assertions.assertNotNull(result.getFirstHeader("Date"));
1022 }
1023 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
1024 }
1025
1026 @Test
1027 public void test206ResponseReturnedToClientMustHaveDateHeader() throws Exception {
1028 request.addHeader("Range", "bytes=0-50");
1029 originResponse = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
1030 originResponse.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
1031 originResponse.setHeader("Server", "MockOrigin/1.0");
1032 originResponse.setEntity(HttpTestUtils.makeBody(500));
1033 originResponse.setHeader("Content-Range", "bytes 0-499/1234");
1034 originResponse.removeHeaders("Date");
1035
1036 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1037
1038 final ClassicHttpResponse result = execute(request);
1039 Assertions.assertTrue(result.getCode() != HttpStatus.SC_PARTIAL_CONTENT
1040 || result.getFirstHeader("Date") != null);
1041
1042 }
1043
1044 @Test
1045 public void test206ContainsETagIfA200ResponseWouldHaveIncludedIt() throws Exception {
1046 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1047
1048 originResponse.addHeader("Cache-Control", "max-age=3600");
1049 originResponse.addHeader("ETag", "\"etag1\"");
1050
1051 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1052 req2.addHeader("Range", "bytes=0-50");
1053
1054 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1055
1056 execute(req1);
1057 final ClassicHttpResponse result = execute(req2);
1058
1059 if (result.getCode() == HttpStatus.SC_PARTIAL_CONTENT) {
1060 Assertions.assertNotNull(result.getFirstHeader("ETag"));
1061 }
1062 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
1063 }
1064
1065 @Test
1066 public void test206ContainsContentLocationIfA200ResponseWouldHaveIncludedIt() throws Exception {
1067 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1068
1069 originResponse.addHeader("Cache-Control", "max-age=3600");
1070 originResponse.addHeader("Content-Location", "http://foo.example.com/other/url");
1071
1072 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1073 req2.addHeader("Range", "bytes=0-50");
1074
1075 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1076
1077 execute(req1);
1078 final ClassicHttpResponse result = execute(req2);
1079
1080 if (result.getCode() == HttpStatus.SC_PARTIAL_CONTENT) {
1081 Assertions.assertNotNull(result.getFirstHeader("Content-Location"));
1082 }
1083 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
1084 }
1085
1086 @Test
1087 public void test206ResponseIncludesVariantHeadersIfValueMightDiffer() throws Exception {
1088
1089 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1090 req1.addHeader("Accept-Encoding", "gzip");
1091
1092 final Instant now = Instant.now();
1093 final Instant inOneHour = Instant.now().plus(1, ChronoUnit.HOURS);
1094 originResponse.addHeader("Cache-Control", "max-age=3600");
1095 originResponse.addHeader("Expires", DateUtils.formatStandardDate(inOneHour));
1096 originResponse.addHeader("Vary", "Accept-Encoding");
1097
1098 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1099 req2.addHeader("Cache-Control", "no-cache");
1100 req2.addHeader("Accept-Encoding", "gzip");
1101 final Instant nextSecond = Instant.now().plusSeconds(1);
1102 final Instant inTwoHoursPlusASec = now.plus(2, ChronoUnit.HOURS).plus(1, ChronoUnit.SECONDS);
1103
1104 final ClassicHttpResponse originResponse2 = HttpTestUtils.make200Response();
1105 originResponse2.setHeader("Date", DateUtils.formatStandardDate(nextSecond));
1106 originResponse2.setHeader("Cache-Control", "max-age=7200");
1107 originResponse2.setHeader("Expires", DateUtils.formatStandardDate(inTwoHoursPlusASec));
1108 originResponse2.setHeader("Vary", "Accept-Encoding");
1109
1110 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1111 req3.addHeader("Range", "bytes=0-50");
1112 req3.addHeader("Accept-Encoding", "gzip");
1113
1114 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1115
1116 execute(req1);
1117
1118 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse2);
1119
1120 execute(req2);
1121 final ClassicHttpResponse result = execute(req3);
1122
1123
1124 if (result.getCode() == HttpStatus.SC_PARTIAL_CONTENT) {
1125 Assertions.assertNotNull(result.getFirstHeader("Expires"));
1126 Assertions.assertNotNull(result.getFirstHeader("Cache-Control"));
1127 Assertions.assertNotNull(result.getFirstHeader("Vary"));
1128 }
1129 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1130 }
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140 @Test
1141 public void test206ResponseToConditionalRangeRequestDoesNotIncludeOtherEntityHeaders() throws Exception {
1142
1143 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1144
1145 final Instant now = Instant.now();
1146 final Instant oneHourAgo = now.minus(1, ChronoUnit.HOURS);
1147 originResponse = HttpTestUtils.make200Response();
1148 originResponse.addHeader("Allow", "GET,HEAD");
1149 originResponse.addHeader("Cache-Control", "max-age=3600");
1150 originResponse.addHeader("Content-Language", "en");
1151 originResponse.addHeader("Content-Encoding", "x-coding");
1152 originResponse.addHeader("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
1153 originResponse.addHeader("Content-Length", "128");
1154 originResponse.addHeader("Content-Type", "application/octet-stream");
1155 originResponse.addHeader("Last-Modified", DateUtils.formatStandardDate(oneHourAgo));
1156 originResponse.addHeader("ETag", "W/\"weak-tag\"");
1157
1158 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1159 req2.addHeader("If-Range", "W/\"weak-tag\"");
1160 req2.addHeader("Range", "bytes=0-50");
1161
1162 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1163
1164 execute(req1);
1165 final ClassicHttpResponse result = execute(req2);
1166
1167 if (result.getCode() == HttpStatus.SC_PARTIAL_CONTENT) {
1168 Assertions.assertNull(result.getFirstHeader("Allow"));
1169 Assertions.assertNull(result.getFirstHeader("Content-Encoding"));
1170 Assertions.assertNull(result.getFirstHeader("Content-Language"));
1171 Assertions.assertNull(result.getFirstHeader("Content-MD5"));
1172 Assertions.assertNull(result.getFirstHeader("Last-Modified"));
1173 }
1174 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
1175 }
1176
1177
1178
1179
1180
1181
1182
1183
1184 @Test
1185 public void test206ResponseToIfRangeWithStrongValidatorReturnsAllEntityHeaders() throws Exception {
1186
1187 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1188
1189 final Instant now = Instant.now();
1190 final Instant oneHourAgo = now.minus(1, ChronoUnit.HOURS);
1191 originResponse.addHeader("Allow", "GET,HEAD");
1192 originResponse.addHeader("Cache-Control", "max-age=3600");
1193 originResponse.addHeader("Content-Language", "en");
1194 originResponse.addHeader("Content-Encoding", "x-coding");
1195 originResponse.addHeader("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
1196 originResponse.addHeader("Content-Length", "128");
1197 originResponse.addHeader("Content-Type", "application/octet-stream");
1198 originResponse.addHeader("Last-Modified", DateUtils.formatStandardDate(oneHourAgo));
1199 originResponse.addHeader("ETag", "\"strong-tag\"");
1200
1201 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1202 req2.addHeader("If-Range", "\"strong-tag\"");
1203 req2.addHeader("Range", "bytes=0-50");
1204
1205 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1206
1207 execute(req1);
1208 final ClassicHttpResponse result = execute(req2);
1209
1210 if (result.getCode() == HttpStatus.SC_PARTIAL_CONTENT) {
1211 Assertions.assertEquals("GET,HEAD", result.getFirstHeader("Allow").getValue());
1212 Assertions.assertEquals("max-age=3600", result.getFirstHeader("Cache-Control").getValue());
1213 Assertions.assertEquals("en", result.getFirstHeader("Content-Language").getValue());
1214 Assertions.assertEquals("x-coding", result.getFirstHeader("Content-Encoding").getValue());
1215 Assertions.assertEquals("Q2hlY2sgSW50ZWdyaXR5IQ==", result.getFirstHeader("Content-MD5")
1216 .getValue());
1217 Assertions.assertEquals(originResponse.getFirstHeader("Last-Modified").getValue(), result
1218 .getFirstHeader("Last-Modified").getValue());
1219 }
1220 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1221 }
1222
1223
1224
1225
1226
1227
1228
1229
1230 @Test
1231 public void test206ResponseIsNotCombinedWithPreviousContentIfETagDoesNotMatch() throws Exception {
1232
1233 final Instant now = Instant.now();
1234
1235 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1236 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1237 resp1.setHeader("Cache-Control", "max-age=3600");
1238 resp1.setHeader("ETag", "\"etag1\"");
1239 final byte[] bytes1 = new byte[128];
1240 Arrays.fill(bytes1, (byte) 1);
1241 resp1.setEntity(new ByteArrayEntity(bytes1, null));
1242
1243 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1244 req2.setHeader("Cache-Control", "no-cache");
1245 req2.setHeader("Range", "bytes=0-50");
1246
1247 final Instant inOneSecond = now.plusSeconds(1);
1248 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT,
1249 "Partial Content");
1250 resp2.setHeader("Date", DateUtils.formatStandardDate(inOneSecond));
1251 resp2.setHeader("Server", resp1.getFirstHeader("Server").getValue());
1252 resp2.setHeader("ETag", "\"etag2\"");
1253 resp2.setHeader("Content-Range", "bytes 0-50/128");
1254 final byte[] bytes2 = new byte[51];
1255 Arrays.fill(bytes2, (byte) 2);
1256 resp2.setEntity(new ByteArrayEntity(bytes2, null));
1257
1258 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1259
1260 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1261
1262 execute(req1);
1263
1264 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1265
1266 execute(req2);
1267
1268 final ClassicHttpResponse result = execute(req3);
1269
1270 final InputStream i = result.getEntity().getContent();
1271 int b;
1272 boolean found1 = false;
1273 boolean found2 = false;
1274 while ((b = i.read()) != -1) {
1275 if (b == 1) {
1276 found1 = true;
1277 }
1278 if (b == 2) {
1279 found2 = true;
1280 }
1281 }
1282 i.close();
1283 Assertions.assertFalse(found1 && found2);
1284 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1285 }
1286
1287 @Test
1288 public void test206ResponseIsNotCombinedWithPreviousContentIfLastModifiedDoesNotMatch() throws Exception {
1289
1290 final Instant now = Instant.now();
1291
1292 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1293 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1294 final Instant oneHourAgo = now.minus(1, ChronoUnit.HOURS);
1295 resp1.setHeader("Cache-Control", "max-age=3600");
1296 resp1.setHeader("Last-Modified", DateUtils.formatStandardDate(oneHourAgo));
1297 final byte[] bytes1 = new byte[128];
1298 Arrays.fill(bytes1, (byte) 1);
1299 resp1.setEntity(new ByteArrayEntity(bytes1, null));
1300
1301 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1302 req2.setHeader("Cache-Control", "no-cache");
1303 req2.setHeader("Range", "bytes=0-50");
1304
1305 final Instant inOneSecond = now.plusSeconds(1);
1306 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT,
1307 "Partial Content");
1308 resp2.setHeader("Date", DateUtils.formatStandardDate(inOneSecond));
1309 resp2.setHeader("Server", resp1.getFirstHeader("Server").getValue());
1310 resp2.setHeader("Last-Modified", DateUtils.formatStandardDate(now));
1311 resp2.setHeader("Content-Range", "bytes 0-50/128");
1312 final byte[] bytes2 = new byte[51];
1313 Arrays.fill(bytes2, (byte) 2);
1314 resp2.setEntity(new ByteArrayEntity(bytes2, null));
1315
1316 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1317
1318 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1319
1320 execute(req1);
1321
1322 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1323
1324 execute(req2);
1325
1326 final ClassicHttpResponse result = execute(req3);
1327
1328 final InputStream i = result.getEntity().getContent();
1329 int b;
1330 boolean found1 = false;
1331 boolean found2 = false;
1332 while ((b = i.read()) != -1) {
1333 if (b == 1) {
1334 found1 = true;
1335 }
1336 if (b == 2) {
1337 found2 = true;
1338 }
1339 }
1340 i.close();
1341 Assertions.assertFalse(found1 && found2);
1342 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1343 }
1344
1345
1346
1347
1348
1349
1350
1351 @Test
1352 public void test206ResponsesAreNotCachedIfTheCacheDoesNotSupportRangeAndContentRangeHeaders() throws Exception {
1353
1354 if (!impl.supportsRangeAndContentRangeHeaders()) {
1355 request = new BasicClassicHttpRequest("GET", "/");
1356 request.addHeader("Range", "bytes=0-50");
1357
1358 originResponse = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT,"Partial Content");
1359 originResponse.setHeader("Content-Range", "bytes 0-50/128");
1360 originResponse.setHeader("Cache-Control", "max-age=3600");
1361 final byte[] bytes = new byte[51];
1362 new Random().nextBytes(bytes);
1363 originResponse.setEntity(new ByteArrayEntity(bytes, null));
1364
1365 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1366
1367 execute(request);
1368 Mockito.verifyNoInteractions(mockCache);
1369 }
1370 }
1371
1372
1373
1374
1375
1376
1377
1378 @Test
1379 public void test303ResponsesAreNotCached() throws Exception {
1380
1381 request = new BasicClassicHttpRequest("GET", "/");
1382
1383 originResponse = new BasicClassicHttpResponse(HttpStatus.SC_SEE_OTHER, "See Other");
1384 originResponse.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
1385 originResponse.setHeader("Server", "MockServer/1.0");
1386 originResponse.setHeader("Cache-Control", "max-age=3600");
1387 originResponse.setHeader("Content-Type", "application/x-cachingclient-test");
1388 originResponse.setHeader("Location", "http://foo.example.com/other");
1389 originResponse.setEntity(HttpTestUtils.makeBody(ENTITY_LENGTH));
1390
1391 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1392
1393 execute(request);
1394
1395 Mockito.verifyNoInteractions(mockCache);
1396 }
1397
1398
1399
1400
1401
1402
1403
1404
1405 @Test
1406 public void test304ResponseWithDateHeaderForwardedFromOriginIncludesDateHeader() throws Exception {
1407
1408 request.setHeader("If-None-Match", "\"etag\"");
1409
1410 originResponse = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED,"Not Modified");
1411 originResponse.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
1412 originResponse.setHeader("Server", "MockServer/1.0");
1413 originResponse.setHeader("ETag", "\"etag\"");
1414
1415 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1416
1417 final ClassicHttpResponse result = execute(request);
1418
1419 Assertions.assertNotNull(result.getFirstHeader("Date"));
1420 }
1421
1422 @Test
1423 public void test304ResponseGeneratedFromCacheIncludesDateHeader() throws Exception {
1424
1425 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1426 originResponse.setHeader("Cache-Control", "max-age=3600");
1427 originResponse.setHeader("ETag", "\"etag\"");
1428
1429 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1430 req2.setHeader("If-None-Match", "\"etag\"");
1431
1432 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1433
1434 execute(req1);
1435 final ClassicHttpResponse result = execute(req2);
1436
1437 if (result.getCode() == HttpStatus.SC_NOT_MODIFIED) {
1438 Assertions.assertNotNull(result.getFirstHeader("Date"));
1439 }
1440 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
1441 }
1442
1443
1444
1445
1446
1447
1448
1449
1450 @Test
1451 public void test304ResponseGeneratedFromCacheIncludesEtagIfOriginResponseDid() throws Exception {
1452 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1453 originResponse.setHeader("Cache-Control", "max-age=3600");
1454 originResponse.setHeader("ETag", "\"etag\"");
1455
1456 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1457 req2.setHeader("If-None-Match", "\"etag\"");
1458
1459 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1460
1461 execute(req1);
1462 final ClassicHttpResponse result = execute(req2);
1463
1464 if (result.getCode() == HttpStatus.SC_NOT_MODIFIED) {
1465 Assertions.assertNotNull(result.getFirstHeader("ETag"));
1466 }
1467 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
1468 }
1469
1470 @Test
1471 public void test304ResponseGeneratedFromCacheIncludesContentLocationIfOriginResponseDid() throws Exception {
1472 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1473 originResponse.setHeader("Cache-Control", "max-age=3600");
1474 originResponse.setHeader("Content-Location", "http://foo.example.com/other");
1475 originResponse.setHeader("ETag", "\"etag\"");
1476
1477 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1478 req2.setHeader("If-None-Match", "\"etag\"");
1479
1480 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1481
1482 execute(req1);
1483 final ClassicHttpResponse result = execute(req2);
1484
1485 if (result.getCode() == HttpStatus.SC_NOT_MODIFIED) {
1486 Assertions.assertNotNull(result.getFirstHeader("Content-Location"));
1487 }
1488 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
1489 }
1490
1491
1492
1493
1494
1495
1496
1497
1498 @Test
1499 public void test304ResponseGeneratedFromCacheIncludesExpiresCacheControlAndOrVaryIfResponseMightDiffer() throws Exception {
1500
1501 final Instant now = Instant.now();
1502 final Instant inTwoHours = now.plus(2, ChronoUnit.HOURS);
1503
1504 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1505 req1.setHeader("Accept-Encoding", "gzip");
1506
1507 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1508 resp1.setHeader("ETag", "\"v1\"");
1509 resp1.setHeader("Cache-Control", "max-age=7200");
1510 resp1.setHeader("Expires", DateUtils.formatStandardDate(inTwoHours));
1511 resp1.setHeader("Vary", "Accept-Encoding");
1512 resp1.setEntity(HttpTestUtils.makeBody(ENTITY_LENGTH));
1513
1514 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1515 req1.setHeader("Accept-Encoding", "gzip");
1516 req1.setHeader("Cache-Control", "no-cache");
1517
1518 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1519 resp2.setHeader("ETag", "\"v2\"");
1520 resp2.setHeader("Cache-Control", "max-age=3600");
1521 resp2.setHeader("Expires", DateUtils.formatStandardDate(inTwoHours));
1522 resp2.setHeader("Vary", "Accept-Encoding");
1523 resp2.setEntity(HttpTestUtils.makeBody(ENTITY_LENGTH));
1524
1525 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1526 req3.setHeader("Accept-Encoding", "gzip");
1527 req3.setHeader("If-None-Match", "\"v2\"");
1528
1529 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1530
1531 execute(req1);
1532
1533 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1534
1535 execute(req2);
1536 final ClassicHttpResponse result = execute(req3);
1537
1538 if (result.getCode() == HttpStatus.SC_NOT_MODIFIED) {
1539 Assertions.assertNotNull(result.getFirstHeader("Expires"));
1540 Assertions.assertNotNull(result.getFirstHeader("Cache-Control"));
1541 Assertions.assertNotNull(result.getFirstHeader("Vary"));
1542 }
1543 Mockito.verify(mockExecChain, Mockito.times(3)).proceed(Mockito.any(), Mockito.any());
1544 }
1545
1546
1547
1548
1549
1550
1551
1552
1553 @Test
1554 public void test304GeneratedFromCacheOnWeakValidatorDoesNotIncludeOtherEntityHeaders() throws Exception {
1555
1556 final Instant now = Instant.now();
1557 final Instant oneHourAgo = now.minus(1, ChronoUnit.HOURS);
1558
1559 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1560
1561 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1562 resp1.setHeader("ETag", "W/\"v1\"");
1563 resp1.setHeader("Allow", "GET,HEAD");
1564 resp1.setHeader("Content-Encoding", "x-coding");
1565 resp1.setHeader("Content-Language", "en");
1566 resp1.setHeader("Content-Length", "128");
1567 resp1.setHeader("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
1568 resp1.setHeader("Content-Type", "application/octet-stream");
1569 resp1.setHeader("Last-Modified", DateUtils.formatStandardDate(oneHourAgo));
1570 resp1.setHeader("Cache-Control", "max-age=7200");
1571
1572 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1573 req2.setHeader("If-None-Match", "W/\"v1\"");
1574
1575 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(req1), Mockito.any())).thenReturn(resp1);
1576
1577 execute(req1);
1578 final ClassicHttpResponse result = execute(req2);
1579
1580 if (result.getCode() == HttpStatus.SC_NOT_MODIFIED) {
1581 Assertions.assertNull(result.getFirstHeader("Allow"));
1582 Assertions.assertNull(result.getFirstHeader("Content-Encoding"));
1583 Assertions.assertNull(result.getFirstHeader("Content-Length"));
1584 Assertions.assertNull(result.getFirstHeader("Content-MD5"));
1585 Assertions.assertNull(result.getFirstHeader("Content-Type"));
1586 Assertions.assertNull(result.getFirstHeader("Last-Modified"));
1587 }
1588 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
1589 }
1590
1591
1592
1593
1594
1595
1596
1597
1598 @Test
1599 public void testNotModifiedOfNonCachedEntityShouldRevalidateWithUnconditionalGET() throws Exception {
1600
1601 final Instant now = Instant.now();
1602
1603
1604 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1605 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1606 resp1.setHeader("ETag", "\"etag1\"");
1607 resp1.setHeader("Cache-Control", "max-age=3600");
1608
1609
1610 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1611 req2.setHeader("Cache-Control", "max-age=0,max-stale=0");
1612
1613
1614 final ClassicHttpRequest unconditionalValidation = new BasicClassicHttpRequest("GET", "/");
1615
1616 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1617 resp1.setHeader("ETag", "\"etag2\"");
1618 resp1.setHeader("Cache-Control", "max-age=3600");
1619
1620 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1621
1622
1623
1624 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(unconditionalValidation), Mockito.any())).thenReturn(resp2);
1625
1626 execute(req1);
1627 execute(req2);
1628
1629 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1630 }
1631
1632
1633
1634
1635
1636
1637
1638
1639 @Test
1640 public void testCacheEntryIsUpdatedWithNewFieldValuesIn304Response() throws Exception {
1641
1642 final Instant now = Instant.now();
1643 final Instant inFiveSeconds = now.plusSeconds(5);
1644
1645 final ClassicHttpRequest initialRequest = new BasicClassicHttpRequest("GET", "/");
1646
1647 final ClassicHttpResponse cachedResponse = HttpTestUtils.make200Response();
1648 cachedResponse.setHeader("Cache-Control", "max-age=3600");
1649 cachedResponse.setHeader("ETag", "\"etag\"");
1650
1651 final ClassicHttpRequest secondRequest = new BasicClassicHttpRequest("GET", "/");
1652 secondRequest.setHeader("Cache-Control", "max-age=0,max-stale=0");
1653
1654 final ClassicHttpRequest conditionalValidationRequest = new BasicClassicHttpRequest("GET", "/");
1655 conditionalValidationRequest.setHeader("If-None-Match", "\"etag\"");
1656
1657
1658 final ClassicHttpResponse conditionalResponse = HttpTestUtils.make304Response();
1659 conditionalResponse.setHeader("Date", DateUtils.formatStandardDate(inFiveSeconds));
1660 conditionalResponse.setHeader("Server", "MockUtils/1.0");
1661 conditionalResponse.setHeader("ETag", "\"etag\"");
1662 conditionalResponse.setHeader("X-Extra", "junk");
1663
1664
1665 final ClassicHttpResponse unconditionalResponse = HttpTestUtils.make200Response();
1666 unconditionalResponse.setHeader("Date", DateUtils.formatStandardDate(inFiveSeconds));
1667 unconditionalResponse.setHeader("ETag", "\"etag\"");
1668
1669 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(cachedResponse);
1670 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(conditionalValidationRequest), Mockito.any())).thenReturn(conditionalResponse);
1671
1672 execute(initialRequest);
1673 final ClassicHttpResponse result = execute(secondRequest);
1674
1675 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1676
1677 Assertions.assertEquals(DateUtils.formatStandardDate(inFiveSeconds), result.getFirstHeader("Date").getValue());
1678 Assertions.assertEquals("junk", result.getFirstHeader("X-Extra").getValue());
1679 }
1680
1681
1682
1683
1684
1685
1686
1687
1688 @Test
1689 public void testMustIncludeWWWAuthenticateHeaderOnAnOrigin401Response() throws Exception {
1690 originResponse = new BasicClassicHttpResponse(401, "Unauthorized");
1691 originResponse.setHeader("WWW-Authenticate", "x-scheme x-param");
1692
1693 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1694
1695 final ClassicHttpResponse result = execute(request);
1696 Assertions.assertEquals(401, result.getCode());
1697 Assertions.assertNotNull(result.getFirstHeader("WWW-Authenticate"));
1698 }
1699
1700
1701
1702
1703
1704
1705
1706 @Test
1707 public void testMustIncludeAllowHeaderFromAnOrigin405Response() throws Exception {
1708 originResponse = new BasicClassicHttpResponse(405, "Method Not Allowed");
1709 originResponse.setHeader("Allow", "GET, HEAD");
1710
1711 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1712
1713 final ClassicHttpResponse result = execute(request);
1714 Assertions.assertEquals(405, result.getCode());
1715 Assertions.assertNotNull(result.getFirstHeader("Allow"));
1716 }
1717
1718
1719
1720
1721
1722
1723
1724
1725 @Test
1726 public void testMustIncludeProxyAuthenticateHeaderFromAnOrigin407Response() throws Exception {
1727 originResponse = new BasicClassicHttpResponse(407, "Proxy Authentication Required");
1728 originResponse.setHeader("Proxy-Authenticate", "x-scheme x-param");
1729
1730 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1731
1732 final ClassicHttpResponse result = execute(request);
1733 Assertions.assertEquals(407, result.getCode());
1734 Assertions.assertNotNull(result.getFirstHeader("Proxy-Authenticate"));
1735 }
1736
1737
1738
1739
1740
1741
1742
1743 @Test
1744 public void testMustNotAddMultipartByteRangeContentTypeTo416Response() throws Exception {
1745 originResponse = new BasicClassicHttpResponse(416, "Requested Range Not Satisfiable");
1746
1747 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1748
1749 final ClassicHttpResponse result = execute(request);
1750
1751 Assertions.assertEquals(416, result.getCode());
1752 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.CONTENT_TYPE);
1753 while (it.hasNext()) {
1754 final HeaderElement elt = it.next();
1755 Assertions.assertFalse("multipart/byteranges".equalsIgnoreCase(elt.getName()));
1756 }
1757 }
1758
1759 @Test
1760 public void testMustNotUseMultipartByteRangeContentTypeOnCacheGenerated416Responses() throws Exception {
1761
1762 originResponse.setEntity(HttpTestUtils.makeBody(ENTITY_LENGTH));
1763 originResponse.setHeader("Content-Length", "128");
1764 originResponse.setHeader("Cache-Control", "max-age=3600");
1765
1766 final ClassicHttpRequest rangeReq = new BasicClassicHttpRequest("GET", "/");
1767 rangeReq.setHeader("Range", "bytes=1000-1200");
1768
1769 final ClassicHttpResponse orig416 = new BasicClassicHttpResponse(416,
1770 "Requested Range Not Satisfiable");
1771
1772
1773
1774 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1775 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(rangeReq), Mockito.any())).thenReturn(orig416);
1776
1777 execute(request);
1778 final ClassicHttpResponse result = execute(rangeReq);
1779
1780
1781 Assertions.assertEquals(416, result.getCode());
1782 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.CONTENT_TYPE);
1783 while (it.hasNext()) {
1784 final HeaderElement elt = it.next();
1785 Assertions.assertFalse("multipart/byteranges".equalsIgnoreCase(elt.getName()));
1786 }
1787 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1788 }
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818 @Test
1819 public void testMustReturnACacheEntryIfItCanRevalidateIt() throws Exception {
1820
1821 final Instant now = Instant.now();
1822 final Instant tenSecondsAgo = now.minusSeconds(10);
1823 final Instant nineSecondsAgo = now.minusSeconds(9);
1824 final Instant eightSecondsAgo = now.minusSeconds(8);
1825
1826 final Header[] hdrs = new Header[] {
1827 new BasicHeader("Date", DateUtils.formatStandardDate(nineSecondsAgo)),
1828 new BasicHeader("Cache-Control", "max-age=0"),
1829 new BasicHeader("ETag", "\"etag\""),
1830 new BasicHeader("Content-Length", "128")
1831 };
1832
1833 final byte[] bytes = new byte[128];
1834 new Random().nextBytes(bytes);
1835
1836 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
1837
1838 impl = new CachingExec(mockCache, null, config);
1839
1840 request = new BasicClassicHttpRequest("GET", "/thing");
1841
1842 final ClassicHttpRequest validate = new BasicClassicHttpRequest("GET", "/thing");
1843 validate.setHeader("If-None-Match", "\"etag\"");
1844
1845 final ClassicHttpResponse notModified = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1846 notModified.setHeader("Date", DateUtils.formatStandardDate(now));
1847 notModified.setHeader("ETag", "\"etag\"");
1848
1849 Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
1850 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(validate), Mockito.any())).thenReturn(notModified);
1851 Mockito.when(mockCache.updateCacheEntry(
1852 Mockito.eq(host),
1853 RequestEquivalent.eq(request),
1854 Mockito.eq(entry),
1855 ResponseEquivalent.eq(notModified),
1856 Mockito.any(),
1857 Mockito.any()))
1858 .thenReturn(HttpTestUtils.makeCacheEntry());
1859
1860 execute(request);
1861
1862 Mockito.verify(mockCache).updateCacheEntry(
1863 Mockito.any(),
1864 Mockito.any(),
1865 Mockito.any(),
1866 Mockito.any(),
1867 Mockito.any(),
1868 Mockito.any());
1869 }
1870
1871 @Test
1872 public void testMustReturnAFreshEnoughCacheEntryIfItHasIt() throws Exception {
1873
1874 final Instant now = Instant.now();
1875 final Instant tenSecondsAgo = now.minusSeconds(10);
1876 final Instant nineSecondsAgo = now.plusSeconds(9);
1877 final Instant eightSecondsAgo = now.plusSeconds(8);
1878
1879 final Header[] hdrs = new Header[] {
1880 new BasicHeader("Date", DateUtils.formatStandardDate(nineSecondsAgo)),
1881 new BasicHeader("Cache-Control", "max-age=3600"),
1882 new BasicHeader("Content-Length", "128")
1883 };
1884
1885 final byte[] bytes = new byte[128];
1886 new Random().nextBytes(bytes);
1887
1888 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
1889
1890 impl = new CachingExec(mockCache, null, config);
1891 request = new BasicClassicHttpRequest("GET", "/thing");
1892
1893 Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
1894
1895 final ClassicHttpResponse result = execute(request);
1896
1897 Assertions.assertEquals(200, result.getCode());
1898 }
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914 @Test
1915 public void testMustServeAppropriateErrorOrWarningIfNoOriginCommunicationPossible() throws Exception {
1916
1917 final Instant now = Instant.now();
1918 final Instant tenSecondsAgo = now.minusSeconds(10);
1919 final Instant nineSecondsAgo = now.plusSeconds(9);
1920 final Instant eightSecondsAgo = now.plusSeconds(8);
1921
1922 final Header[] hdrs = new Header[] {
1923 new BasicHeader("Date", DateUtils.formatStandardDate(nineSecondsAgo)),
1924 new BasicHeader("Cache-Control", "max-age=0"),
1925 new BasicHeader("Content-Length", "128"),
1926 new BasicHeader("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo))
1927 };
1928
1929 final byte[] bytes = new byte[128];
1930 new Random().nextBytes(bytes);
1931
1932 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
1933
1934 impl = new CachingExec(mockCache, null, config);
1935 request = new BasicClassicHttpRequest("GET", "/thing");
1936
1937 Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
1938 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(
1939 new IOException("can't talk to origin!"));
1940
1941 final ClassicHttpResponse result = execute(request);
1942
1943 final int status = result.getCode();
1944 Assertions.assertEquals(200, result.getCode());
1945 boolean foundWarning = false;
1946 for (final Header h : result.getHeaders("Warning")) {
1947 if (h.getValue().split(" ")[0].equals("111")) {
1948 foundWarning = true;
1949 }
1950 }
1951 Assertions.assertTrue(foundWarning);
1952 }
1953
1954
1955
1956
1957
1958
1959
1960
1961 @Test
1962 public void testAttachesWarningHeaderWhenGeneratingStaleResponse() throws Exception {
1963
1964 }
1965
1966
1967
1968
1969
1970
1971
1972 @Test
1973 public void test1xxWarningsAreDeletedAfterSuccessfulRevalidation() throws Exception {
1974
1975 final Instant now = Instant.now();
1976 final Instant twentyFiveSecondsAgo = now.minusSeconds(25);
1977 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1978 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1979 resp1.setHeader("Date", DateUtils.formatStandardDate(twentyFiveSecondsAgo));
1980 resp1.setHeader("ETag", "\"etag\"");
1981 resp1.setHeader("Cache-Control", "max-age=5");
1982 resp1.setHeader("Warning", "110 squid \"stale stuff\"");
1983 resp1.setHeader("Via", "1.1 fred");
1984
1985 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1986
1987 final ClassicHttpRequest validate = new BasicClassicHttpRequest("GET", "/");
1988 validate.setHeader("If-None-Match", "\"etag\"");
1989
1990 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED,
1991 "Not Modified");
1992 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
1993 resp2.setHeader("Server", "MockServer/1.0");
1994 resp2.setHeader("ETag", "\"etag\"");
1995 resp2.setHeader("Via", "1.1 fred");
1996
1997 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1998 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(validate), Mockito.any())).thenReturn(resp2);
1999
2000 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
2001
2002
2003 final ClassicHttpResponse stale = execute(req1);
2004 Assertions.assertNotNull(stale.getFirstHeader("Warning"));
2005
2006 final ClassicHttpResponse result1 = execute(req2);
2007 final ClassicHttpResponse result2 = execute(req3);
2008
2009 boolean found1xxWarning = false;
2010 final Iterator<HeaderElement> it = MessageSupport.iterate(result1, HttpHeaders.WARNING);
2011 while (it.hasNext()) {
2012 final HeaderElement elt = it.next();
2013 if (elt.getName().startsWith("1")) {
2014 found1xxWarning = true;
2015 }
2016 }
2017 final Iterator<HeaderElement> it2 = MessageSupport.iterate(result2, HttpHeaders.WARNING);
2018 while (it2.hasNext()) {
2019 final HeaderElement elt = it2.next();
2020 if (elt.getName().startsWith("1")) {
2021 found1xxWarning = true;
2022 }
2023 }
2024 Assertions.assertFalse(found1xxWarning);
2025 }
2026
2027
2028
2029
2030
2031
2032
2033
2034 @Test
2035 public void test2xxWarningsAreNotDeletedAfterSuccessfulRevalidation() throws Exception {
2036 final Instant now = Instant.now();
2037 final Instant tenSecondsAgo = now.minusSeconds(10);
2038 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2039 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2040 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
2041 resp1.setHeader("ETag", "\"etag\"");
2042 resp1.setHeader("Cache-Control", "max-age=5");
2043 resp1.setHeader("Via", "1.1 xproxy");
2044 resp1.setHeader("Warning", "214 xproxy \"transformed stuff\"");
2045
2046 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2047
2048 final ClassicHttpRequest validate = new BasicClassicHttpRequest("GET", "/");
2049 validate.setHeader("If-None-Match", "\"etag\"");
2050
2051 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED,
2052 "Not Modified");
2053 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
2054 resp2.setHeader("Server", "MockServer/1.0");
2055 resp2.setHeader("ETag", "\"etag\"");
2056 resp1.setHeader("Via", "1.1 xproxy");
2057
2058 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
2059
2060 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2061 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(validate), Mockito.any())).thenReturn(resp2);
2062
2063 final ClassicHttpResponse stale = execute(req1);
2064 Assertions.assertNotNull(stale.getFirstHeader("Warning"));
2065
2066 final ClassicHttpResponse result1 = execute(req2);
2067 final ClassicHttpResponse result2 = execute(req3);
2068
2069 boolean found214Warning = false;
2070 final Iterator<HeaderElement> it = MessageSupport.iterate(result1, HttpHeaders.WARNING);
2071 while (it.hasNext()) {
2072 final HeaderElement elt = it.next();
2073 final String[] parts = elt.getName().split(" ");
2074 if ("214".equals(parts[0])) {
2075 found214Warning = true;
2076 }
2077 }
2078 Assertions.assertTrue(found214Warning);
2079
2080 found214Warning = false;
2081 final Iterator<HeaderElement> it2 = MessageSupport.iterate(result2, HttpHeaders.WARNING);
2082 while (it2.hasNext()) {
2083 final HeaderElement elt = it2.next();
2084 final String[] parts = elt.getName().split(" ");
2085 if ("214".equals(parts[0])) {
2086 found214Warning = true;
2087 }
2088 }
2089 Assertions.assertTrue(found214Warning);
2090 }
2091
2092
2093
2094
2095
2096
2097
2098
2099 @Test
2100 public void testAgeHeaderPopulatedFromCacheEntryCurrentAge() throws Exception {
2101
2102 final Instant now = Instant.now();
2103 final Instant tenSecondsAgo = now.minusSeconds(10);
2104 final Instant nineSecondsAgo = now.minusSeconds(9);
2105 final Instant eightSecondsAgo = now.minusSeconds(8);
2106
2107 final Header[] hdrs = new Header[] {
2108 new BasicHeader("Date", DateUtils.formatStandardDate(nineSecondsAgo)),
2109 new BasicHeader("Cache-Control", "max-age=3600"),
2110 new BasicHeader("Content-Length", "128")
2111 };
2112
2113 final byte[] bytes = new byte[128];
2114 new Random().nextBytes(bytes);
2115
2116 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
2117
2118 impl = new CachingExec(mockCache, null, config);
2119 request = new BasicClassicHttpRequest("GET", "/thing");
2120
2121 Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
2122
2123 final ClassicHttpResponse result = execute(request);
2124
2125 Assertions.assertEquals(200, result.getCode());
2126 Assertions.assertEquals("11", result.getFirstHeader("Age").getValue());
2127 }
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145 @Test
2146 public void testHeuristicCacheOlderThan24HoursHasWarningAttached() throws Exception {
2147
2148 final Instant now = Instant.now();
2149 final Instant thirtySixHoursAgo = now.minus(26, ChronoUnit.HOURS);
2150 final Instant oneYearAgo = now.minus(1, ChronoUnit.HOURS);
2151 final Instant requestTime = thirtySixHoursAgo.minusSeconds(1);
2152 final Instant responseTime = thirtySixHoursAgo.plusSeconds(1);
2153
2154 final Header[] hdrs = new Header[] {
2155 new BasicHeader("Date", DateUtils.formatStandardDate(thirtySixHoursAgo)),
2156 new BasicHeader("Cache-Control", "public"),
2157 new BasicHeader("Last-Modified", DateUtils.formatStandardDate(oneYearAgo)),
2158 new BasicHeader("Content-Length", "128")
2159 };
2160
2161 final byte[] bytes = new byte[128];
2162 new Random().nextBytes(bytes);
2163
2164 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(requestTime, responseTime, hdrs, bytes);
2165
2166 impl = new CachingExec(mockCache, null, config);
2167
2168 request = new BasicClassicHttpRequest("GET", "/thing");
2169
2170 final ClassicHttpResponse validated = HttpTestUtils.make200Response();
2171 validated.setHeader("Cache-Control", "public");
2172 validated.setHeader("Last-Modified", DateUtils.formatStandardDate(oneYearAgo));
2173 validated.setHeader("Content-Length", "128");
2174 validated.setEntity(new ByteArrayEntity(bytes, null));
2175
2176 final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry();
2177
2178 Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
2179 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(validated);
2180 Mockito.when(mockCache.createCacheEntry(
2181 Mockito.any(),
2182 Mockito.any(),
2183 ResponseEquivalent.eq(validated),
2184 Mockito.any(),
2185 Mockito.any(),
2186 Mockito.any())).thenReturn(cacheEntry);
2187
2188 final ClassicHttpResponse result = execute(request);
2189
2190 Assertions.assertEquals(200, result.getCode());
2191
2192 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
2193 Mockito.verify(mockExecChain, Mockito.atMostOnce()).proceed(reqCapture.capture(), Mockito.any());
2194 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
2195 if (allRequests.isEmpty()) {
2196
2197 boolean found113Warning = false;
2198 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.WARNING);
2199 while (it.hasNext()) {
2200 final HeaderElement elt = it.next();
2201 final String[] parts = elt.getName().split(" ");
2202 if ("113".equals(parts[0])) {
2203 found113Warning = true;
2204 break;
2205 }
2206 }
2207 Assertions.assertTrue(found113Warning);
2208 }
2209 Mockito.verify(mockCache).createCacheEntry(
2210 Mockito.any(),
2211 Mockito.any(),
2212 Mockito.any(),
2213 Mockito.any(),
2214 Mockito.any(),
2215 Mockito.any());
2216 }
2217
2218
2219
2220
2221
2222
2223
2224
2225 @Test
2226 public void testKeepsMostRecentDateHeaderForFreshResponse() throws Exception {
2227
2228 final Instant now = Instant.now();
2229 final Instant inFiveSecond = now.plusSeconds(5);
2230
2231
2232 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2233
2234 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2235 resp1.setHeader("Date", DateUtils.formatStandardDate(inFiveSecond));
2236 resp1.setHeader("ETag", "\"etag1\"");
2237 resp1.setHeader("Cache-Control", "max-age=3600");
2238 resp1.setHeader("Content-Length", "128");
2239
2240
2241 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2242 req2.setHeader("Cache-Control", "no-cache");
2243
2244 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
2245 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
2246 resp2.setHeader("ETag", "\"etag2\"");
2247 resp2.setHeader("Cache-Control", "max-age=3600");
2248 resp2.setHeader("Content-Length", "128");
2249
2250 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
2251
2252 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2253
2254 execute(req1);
2255
2256 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
2257
2258 execute(req2);
2259 final ClassicHttpResponse result = execute(req3);
2260 Assertions.assertEquals("\"etag1\"", result.getFirstHeader("ETag").getValue());
2261 }
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277 private ClassicHttpResponse testRequestWithWeakETagValidatorIsNotAllowed(final String header) throws Exception {
2278 final ClassicHttpResponse response = execute(request);
2279
2280
2281 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
2282 Mockito.verify(mockExecChain, Mockito.atMostOnce()).proceed(reqCapture.capture(), Mockito.any());
2283 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
2284 if (!allRequests.isEmpty()) {
2285 final ClassicHttpRequest forwarded = reqCapture.getValue();
2286 if (forwarded != null) {
2287 final Header h = forwarded.getFirstHeader(header);
2288 if (h != null) {
2289 Assertions.assertFalse(h.getValue().startsWith("W/"));
2290 }
2291 }
2292 }
2293 return response;
2294 }
2295
2296 @Test
2297 public void testSubrangeGETWithWeakETagIsNotAllowed() throws Exception {
2298 request = new BasicClassicHttpRequest("GET", "/");
2299 request.setHeader("Range", "bytes=0-500");
2300 request.setHeader("If-Range", "W/\"etag\"");
2301
2302 final ClassicHttpResponse response = testRequestWithWeakETagValidatorIsNotAllowed("If-Range");
2303 Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getCode());
2304 }
2305
2306 @Test
2307 public void testPUTWithIfMatchWeakETagIsNotAllowed() throws Exception {
2308 final ClassicHttpRequest put = new BasicClassicHttpRequest("PUT", "/");
2309 put.setEntity(HttpTestUtils.makeBody(128));
2310 put.setHeader("Content-Length", "128");
2311 put.setHeader("If-Match", "W/\"etag\"");
2312 request = put;
2313
2314 testRequestWithWeakETagValidatorIsNotAllowed("If-Match");
2315 }
2316
2317 @Test
2318 public void testPUTWithIfNoneMatchWeakETagIsNotAllowed() throws Exception {
2319 final ClassicHttpRequest put = new BasicClassicHttpRequest("PUT", "/");
2320 put.setEntity(HttpTestUtils.makeBody(128));
2321 put.setHeader("Content-Length", "128");
2322 put.setHeader("If-None-Match", "W/\"etag\"");
2323 request = put;
2324
2325 testRequestWithWeakETagValidatorIsNotAllowed("If-None-Match");
2326 }
2327
2328 @Test
2329 public void testDELETEWithIfMatchWeakETagIsNotAllowed() throws Exception {
2330 request = new BasicClassicHttpRequest("DELETE", "/");
2331 request.setHeader("If-Match", "W/\"etag\"");
2332
2333 testRequestWithWeakETagValidatorIsNotAllowed("If-Match");
2334 }
2335
2336 @Test
2337 public void testDELETEWithIfNoneMatchWeakETagIsNotAllowed() throws Exception {
2338 request = new BasicClassicHttpRequest("DELETE", "/");
2339 request.setHeader("If-None-Match", "W/\"etag\"");
2340
2341 testRequestWithWeakETagValidatorIsNotAllowed("If-None-Match");
2342 }
2343
2344
2345
2346
2347
2348
2349
2350
2351 @Test
2352 public void testSubrangeGETMustUseStrongComparisonForCachedResponse() throws Exception {
2353 final Instant now = Instant.now();
2354 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2355 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2356 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
2357 resp1.setHeader("Cache-Control", "max-age=3600");
2358 resp1.setHeader("ETag", "\"etag\"");
2359
2360
2361
2362
2363
2364
2365 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2366 req2.setHeader("Range", "bytes=0-50");
2367 req2.setHeader("If-Range", "W/\"etag\"");
2368
2369 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2370
2371 execute(req1);
2372 final ClassicHttpResponse result = execute(req2);
2373
2374 Assertions.assertNotEquals(HttpStatus.SC_PARTIAL_CONTENT, result.getCode());
2375
2376 Mockito.verify(mockExecChain).proceed(Mockito.any(), Mockito.any());
2377 }
2378
2379
2380
2381
2382
2383
2384
2385
2386 @Test
2387 public void testValidationMustUseETagIfProvidedByOriginServer() throws Exception {
2388
2389 final Instant now = Instant.now();
2390 final Instant tenSecondsAgo = now.minusSeconds(10);
2391
2392 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2393 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2394 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
2395 resp1.setHeader("Cache-Control", "max-age=3600");
2396 resp1.setHeader("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
2397 resp1.setHeader("ETag", "W/\"etag\"");
2398
2399 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2400 req2.setHeader("Cache-Control", "max-age=0,max-stale=0");
2401
2402 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2403
2404 execute(req1);
2405 execute(req2);
2406
2407 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
2408 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
2409
2410 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
2411 Assertions.assertEquals(2, allRequests.size());
2412 final ClassicHttpRequest validation = allRequests.get(1);
2413 boolean isConditional = false;
2414 final String[] conditionalHeaders = { "If-Range", "If-Modified-Since", "If-Unmodified-Since",
2415 "If-Match", "If-None-Match" };
2416
2417 for (final String ch : conditionalHeaders) {
2418 if (validation.getFirstHeader(ch) != null) {
2419 isConditional = true;
2420 break;
2421 }
2422 }
2423
2424 if (isConditional) {
2425 boolean foundETag = false;
2426 final Iterator<HeaderElement> it = MessageSupport.iterate(validation, HttpHeaders.IF_MATCH);
2427 while (it.hasNext()) {
2428 final HeaderElement elt = it.next();
2429 if ("W/\"etag\"".equals(elt.getName())) {
2430 foundETag = true;
2431 }
2432 }
2433 final Iterator<HeaderElement> it2 = MessageSupport.iterate(validation, HttpHeaders.IF_NONE_MATCH);
2434 while (it2.hasNext()) {
2435 final HeaderElement elt = it2.next();
2436 if ("W/\"etag\"".equals(elt.getName())) {
2437 foundETag = true;
2438 }
2439 }
2440 Assertions.assertTrue(foundETag);
2441 }
2442 }
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453 @Test
2454 public void testConditionalRequestWhereNotAllValidatorsMatchCannotBeServedFromCache() throws Exception {
2455 final Instant now = Instant.now();
2456 final Instant tenSecondsAgo = now.minusSeconds(10);
2457 final Instant twentySecondsAgo = now.plusSeconds(20);
2458
2459 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2460 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2461 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
2462 resp1.setHeader("Cache-Control", "max-age=3600");
2463 resp1.setHeader("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
2464 resp1.setHeader("ETag", "W/\"etag\"");
2465
2466 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2467 req2.setHeader("If-None-Match", "W/\"etag\"");
2468 req2.setHeader("If-Modified-Since", DateUtils.formatStandardDate(twentySecondsAgo));
2469
2470
2471 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2472
2473 execute(req1);
2474 final ClassicHttpResponse result = execute(req2);
2475
2476 Assertions.assertNotEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
2477 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
2478 }
2479
2480 @Test
2481 public void testConditionalRequestWhereAllValidatorsMatchMayBeServedFromCache() throws Exception {
2482 final Instant now = Instant.now();
2483 final Instant tenSecondsAgo = now.minusSeconds(10);
2484
2485 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2486 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2487 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
2488 resp1.setHeader("Cache-Control", "max-age=3600");
2489 resp1.setHeader("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
2490 resp1.setHeader("ETag", "W/\"etag\"");
2491
2492 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2493 req2.setHeader("If-None-Match", "W/\"etag\"");
2494 req2.setHeader("If-Modified-Since", DateUtils.formatStandardDate(tenSecondsAgo));
2495
2496
2497 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2498
2499 execute(req1);
2500 execute(req2);
2501
2502 Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
2503 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
2504 }
2505
2506
2507
2508
2509
2510
2511
2512
2513 @Test
2514 public void testCacheWithoutSupportForRangeAndContentRangeHeadersDoesNotCacheA206Response() throws Exception {
2515
2516 if (!impl.supportsRangeAndContentRangeHeaders()) {
2517 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
2518 req.setHeader("Range", "bytes=0-50");
2519
2520 final ClassicHttpResponse resp = new BasicClassicHttpResponse(206, "Partial Content");
2521 resp.setHeader("Content-Range", "bytes 0-50/128");
2522 resp.setHeader("ETag", "\"etag\"");
2523 resp.setHeader("Cache-Control", "max-age=3600");
2524
2525 Mockito.when(mockExecChain.proceed(Mockito.any(),Mockito.any())).thenReturn(resp);
2526
2527 execute(req);
2528
2529 Mockito.verifyNoInteractions(mockCache);
2530 }
2531 }
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544 @Test
2545 public void test302ResponseWithoutExplicitCacheabilityIsNotReturnedFromCache() throws Exception {
2546 originResponse = new BasicClassicHttpResponse(302, "Temporary Redirect");
2547 originResponse.setHeader("Location", "http://foo.example.com/other");
2548 originResponse.removeHeaders("Expires");
2549 originResponse.removeHeaders("Cache-Control");
2550
2551 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2552
2553 execute(request);
2554 execute(request);
2555
2556 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
2557 }
2558
2559
2560
2561
2562
2563
2564 private void testDoesNotModifyHeaderFromOrigin(final String header, final String value) throws Exception {
2565 originResponse = HttpTestUtils.make200Response();
2566 originResponse.setHeader(header, value);
2567
2568 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2569
2570 final ClassicHttpResponse result = execute(request);
2571
2572 Assertions.assertEquals(value, result.getFirstHeader(header).getValue());
2573 }
2574
2575 @Test
2576 public void testDoesNotModifyContentLocationHeaderFromOrigin() throws Exception {
2577
2578 final String url = "http://foo.example.com/other";
2579 testDoesNotModifyHeaderFromOrigin("Content-Location", url);
2580 }
2581
2582 @Test
2583 public void testDoesNotModifyContentMD5HeaderFromOrigin() throws Exception {
2584 testDoesNotModifyHeaderFromOrigin("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
2585 }
2586
2587 @Test
2588 public void testDoesNotModifyEtagHeaderFromOrigin() throws Exception {
2589 testDoesNotModifyHeaderFromOrigin("Etag", "\"the-etag\"");
2590 }
2591
2592 @Test
2593 public void testDoesNotModifyLastModifiedHeaderFromOrigin() throws Exception {
2594 final String lm = DateUtils.formatStandardDate(Instant.now());
2595 testDoesNotModifyHeaderFromOrigin("Last-Modified", lm);
2596 }
2597
2598 private void testDoesNotAddHeaderToOriginResponse(final String header) throws Exception {
2599 originResponse.removeHeaders(header);
2600
2601 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2602
2603 final ClassicHttpResponse result = execute(request);
2604
2605 Assertions.assertNull(result.getFirstHeader(header));
2606 }
2607
2608 @Test
2609 public void testDoesNotAddContentLocationToOriginResponse() throws Exception {
2610 testDoesNotAddHeaderToOriginResponse("Content-Location");
2611 }
2612
2613 @Test
2614 public void testDoesNotAddContentMD5ToOriginResponse() throws Exception {
2615 testDoesNotAddHeaderToOriginResponse("Content-MD5");
2616 }
2617
2618 @Test
2619 public void testDoesNotAddEtagToOriginResponse() throws Exception {
2620 testDoesNotAddHeaderToOriginResponse("ETag");
2621 }
2622
2623 @Test
2624 public void testDoesNotAddLastModifiedToOriginResponse() throws Exception {
2625 testDoesNotAddHeaderToOriginResponse("Last-Modified");
2626 }
2627
2628 private void testDoesNotModifyHeaderFromOriginOnCacheHit(final String header, final String value) throws Exception {
2629
2630 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2631 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2632
2633 originResponse = HttpTestUtils.make200Response();
2634 originResponse.setHeader("Cache-Control", "max-age=3600");
2635 originResponse.setHeader(header, value);
2636
2637 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2638
2639 execute(req1);
2640 final ClassicHttpResponse result = execute(req2);
2641
2642 Assertions.assertEquals(value, result.getFirstHeader(header).getValue());
2643 }
2644
2645 @Test
2646 public void testDoesNotModifyContentLocationFromOriginOnCacheHit() throws Exception {
2647 final String url = "http://foo.example.com/other";
2648 testDoesNotModifyHeaderFromOriginOnCacheHit("Content-Location", url);
2649 }
2650
2651 @Test
2652 public void testDoesNotModifyContentMD5FromOriginOnCacheHit() throws Exception {
2653 testDoesNotModifyHeaderFromOriginOnCacheHit("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
2654 }
2655
2656 @Test
2657 public void testDoesNotModifyEtagFromOriginOnCacheHit() throws Exception {
2658 testDoesNotModifyHeaderFromOriginOnCacheHit("Etag", "\"the-etag\"");
2659 }
2660
2661 @Test
2662 public void testDoesNotModifyLastModifiedFromOriginOnCacheHit() throws Exception {
2663 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
2664 testDoesNotModifyHeaderFromOriginOnCacheHit("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
2665 }
2666
2667 private void testDoesNotAddHeaderOnCacheHit(final String header) throws Exception {
2668
2669 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2670 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2671
2672 originResponse.addHeader("Cache-Control", "max-age=3600");
2673 originResponse.removeHeaders(header);
2674
2675 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2676
2677 execute(req1);
2678 final ClassicHttpResponse result = execute(req2);
2679
2680 Assertions.assertNull(result.getFirstHeader(header));
2681 }
2682
2683 @Test
2684 public void testDoesNotAddContentLocationHeaderOnCacheHit() throws Exception {
2685 testDoesNotAddHeaderOnCacheHit("Content-Location");
2686 }
2687
2688 @Test
2689 public void testDoesNotAddContentMD5HeaderOnCacheHit() throws Exception {
2690 testDoesNotAddHeaderOnCacheHit("Content-MD5");
2691 }
2692
2693 @Test
2694 public void testDoesNotAddETagHeaderOnCacheHit() throws Exception {
2695 testDoesNotAddHeaderOnCacheHit("ETag");
2696 }
2697
2698 @Test
2699 public void testDoesNotAddLastModifiedHeaderOnCacheHit() throws Exception {
2700 testDoesNotAddHeaderOnCacheHit("Last-Modified");
2701 }
2702
2703 private void testDoesNotModifyHeaderOnRequest(final String header, final String value) throws Exception {
2704 final BasicClassicHttpRequest req = new BasicClassicHttpRequest("POST","/");
2705 req.setEntity(HttpTestUtils.makeBody(128));
2706 req.setHeader("Content-Length","128");
2707 req.setHeader(header,value);
2708
2709 execute(req);
2710
2711 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
2712 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
2713
2714 final ClassicHttpRequest captured = reqCapture.getValue();
2715 Assertions.assertEquals(value, captured.getFirstHeader(header).getValue());
2716 }
2717
2718 @Test
2719 public void testDoesNotModifyContentLocationHeaderOnRequest() throws Exception {
2720 final String url = "http://foo.example.com/other";
2721 testDoesNotModifyHeaderOnRequest("Content-Location",url);
2722 }
2723
2724 @Test
2725 public void testDoesNotModifyContentMD5HeaderOnRequest() throws Exception {
2726 testDoesNotModifyHeaderOnRequest("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
2727 }
2728
2729 @Test
2730 public void testDoesNotModifyETagHeaderOnRequest() throws Exception {
2731 testDoesNotModifyHeaderOnRequest("ETag","\"etag\"");
2732 }
2733
2734 @Test
2735 public void testDoesNotModifyLastModifiedHeaderOnRequest() throws Exception {
2736 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
2737 testDoesNotModifyHeaderOnRequest("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
2738 }
2739
2740 private void testDoesNotAddHeaderToRequestIfNotPresent(final String header) throws Exception {
2741 final BasicClassicHttpRequest req = new BasicClassicHttpRequest("POST","/");
2742 req.setEntity(HttpTestUtils.makeBody(128));
2743 req.setHeader("Content-Length","128");
2744 req.removeHeaders(header);
2745
2746 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2747
2748 execute(req);
2749
2750 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
2751 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
2752
2753 final ClassicHttpRequest captured = reqCapture.getValue();
2754 Assertions.assertNull(captured.getFirstHeader(header));
2755 }
2756
2757 @Test
2758 public void testDoesNotAddContentLocationToRequestIfNotPresent() throws Exception {
2759 testDoesNotAddHeaderToRequestIfNotPresent("Content-Location");
2760 }
2761
2762 @Test
2763 public void testDoesNotAddContentMD5ToRequestIfNotPresent() throws Exception {
2764 testDoesNotAddHeaderToRequestIfNotPresent("Content-MD5");
2765 }
2766
2767 @Test
2768 public void testDoesNotAddETagToRequestIfNotPresent() throws Exception {
2769 testDoesNotAddHeaderToRequestIfNotPresent("ETag");
2770 }
2771
2772 @Test
2773 public void testDoesNotAddLastModifiedToRequestIfNotPresent() throws Exception {
2774 testDoesNotAddHeaderToRequestIfNotPresent("Last-Modified");
2775 }
2776
2777
2778
2779
2780
2781
2782
2783 @Test
2784 public void testDoesNotModifyExpiresHeaderFromOrigin() throws Exception {
2785 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
2786 testDoesNotModifyHeaderFromOrigin("Expires", DateUtils.formatStandardDate(tenSecondsAgo));
2787 }
2788
2789 @Test
2790 public void testDoesNotModifyExpiresHeaderFromOriginOnCacheHit() throws Exception {
2791 final Instant inTenSeconds = Instant.now().plusSeconds(10);
2792 testDoesNotModifyHeaderFromOriginOnCacheHit("Expires", DateUtils.formatStandardDate(inTenSeconds));
2793 }
2794
2795 @Test
2796 public void testExpiresHeaderMatchesDateIfAddedToOriginResponse() throws Exception {
2797 originResponse.removeHeaders("Expires");
2798
2799 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2800
2801 final ClassicHttpResponse result = execute(request);
2802
2803 final Header expHdr = result.getFirstHeader("Expires");
2804 if (expHdr != null) {
2805 Assertions.assertEquals(result.getFirstHeader("Date").getValue(),
2806 expHdr.getValue());
2807 }
2808 }
2809
2810 @Test
2811 public void testExpiresHeaderMatchesDateIfAddedToCacheHit() throws Exception {
2812 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2813 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2814
2815 originResponse.setHeader("Cache-Control","max-age=3600");
2816 originResponse.removeHeaders("Expires");
2817
2818 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2819
2820 execute(req1);
2821 final ClassicHttpResponse result = execute(req2);
2822
2823 final Header expHdr = result.getFirstHeader("Expires");
2824 if (expHdr != null) {
2825 Assertions.assertEquals(result.getFirstHeader("Date").getValue(),
2826 expHdr.getValue());
2827 }
2828 }
2829
2830
2831
2832
2833
2834
2835
2836 private void testDoesNotModifyHeaderFromOriginResponseWithNoTransform(final String header, final String value) throws Exception {
2837 originResponse.addHeader("Cache-Control","no-transform");
2838 originResponse.setHeader(header, value);
2839
2840 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2841
2842 final ClassicHttpResponse result = execute(request);
2843
2844 Assertions.assertEquals(value, result.getFirstHeader(header).getValue());
2845 }
2846
2847 @Test
2848 public void testDoesNotModifyContentEncodingHeaderFromOriginResponseWithNoTransform() throws Exception {
2849 testDoesNotModifyHeaderFromOriginResponseWithNoTransform("Content-Encoding","gzip");
2850 }
2851
2852 @Test
2853 public void testDoesNotModifyContentRangeHeaderFromOriginResponseWithNoTransform() throws Exception {
2854 request.setHeader("If-Range","\"etag\"");
2855 request.setHeader("Range","bytes=0-49");
2856
2857 originResponse = new BasicClassicHttpResponse(206, "Partial Content");
2858 originResponse.setEntity(HttpTestUtils.makeBody(50));
2859 testDoesNotModifyHeaderFromOriginResponseWithNoTransform("Content-Range","bytes 0-49/128");
2860 }
2861
2862 @Test
2863 public void testDoesNotModifyContentTypeHeaderFromOriginResponseWithNoTransform() throws Exception {
2864 testDoesNotModifyHeaderFromOriginResponseWithNoTransform("Content-Type","text/html;charset=utf-8");
2865 }
2866
2867 private void testDoesNotModifyHeaderOnCachedResponseWithNoTransform(final String header, final String value) throws Exception {
2868 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2869 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2870
2871 originResponse.addHeader("Cache-Control","max-age=3600, no-transform");
2872 originResponse.setHeader(header, value);
2873
2874 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2875
2876 execute(req1);
2877 final ClassicHttpResponse result = execute(req2);
2878
2879 Assertions.assertEquals(value, result.getFirstHeader(header).getValue());
2880 }
2881
2882 @Test
2883 public void testDoesNotModifyContentEncodingHeaderOnCachedResponseWithNoTransform() throws Exception {
2884 testDoesNotModifyHeaderOnCachedResponseWithNoTransform("Content-Encoding","gzip");
2885 }
2886
2887 @Test
2888 public void testDoesNotModifyContentTypeHeaderOnCachedResponseWithNoTransform() throws Exception {
2889 testDoesNotModifyHeaderOnCachedResponseWithNoTransform("Content-Type","text/html;charset=utf-8");
2890 }
2891
2892 @Test
2893 public void testDoesNotModifyContentRangeHeaderOnCachedResponseWithNoTransform() throws Exception {
2894 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2895 req1.setHeader("If-Range","\"etag\"");
2896 req1.setHeader("Range","bytes=0-49");
2897 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2898 req2.setHeader("If-Range","\"etag\"");
2899 req2.setHeader("Range","bytes=0-49");
2900
2901 originResponse.addHeader("Cache-Control","max-age=3600, no-transform");
2902 originResponse.setHeader("Content-Range", "bytes 0-49/128");
2903
2904 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2905
2906 execute(req1);
2907 final ClassicHttpResponse result = execute(req2);
2908
2909 Assertions.assertEquals("bytes 0-49/128",
2910 result.getFirstHeader("Content-Range").getValue());
2911
2912 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
2913 }
2914
2915 @Test
2916 public void testDoesNotAddContentEncodingHeaderToOriginResponseWithNoTransformIfNotPresent() throws Exception {
2917 originResponse.addHeader("Cache-Control","no-transform");
2918 testDoesNotAddHeaderToOriginResponse("Content-Encoding");
2919 }
2920
2921 @Test
2922 public void testDoesNotAddContentRangeHeaderToOriginResponseWithNoTransformIfNotPresent() throws Exception {
2923 originResponse.addHeader("Cache-Control","no-transform");
2924 testDoesNotAddHeaderToOriginResponse("Content-Range");
2925 }
2926
2927 @Test
2928 public void testDoesNotAddContentTypeHeaderToOriginResponseWithNoTransformIfNotPresent() throws Exception {
2929 originResponse.addHeader("Cache-Control","no-transform");
2930 testDoesNotAddHeaderToOriginResponse("Content-Type");
2931 }
2932
2933
2934 @Test
2935 public void testDoesNotAddContentEncodingHeaderToCachedResponseWithNoTransformIfNotPresent() throws Exception {
2936 originResponse.addHeader("Cache-Control","no-transform");
2937 testDoesNotAddHeaderOnCacheHit("Content-Encoding");
2938 }
2939
2940 @Test
2941 public void testDoesNotAddContentRangeHeaderToCachedResponseWithNoTransformIfNotPresent() throws Exception {
2942 originResponse.addHeader("Cache-Control","no-transform");
2943 testDoesNotAddHeaderOnCacheHit("Content-Range");
2944 }
2945
2946 @Test
2947 public void testDoesNotAddContentTypeHeaderToCachedResponseWithNoTransformIfNotPresent() throws Exception {
2948 originResponse.addHeader("Cache-Control","no-transform");
2949 testDoesNotAddHeaderOnCacheHit("Content-Type");
2950 }
2951
2952
2953 @Test
2954 public void testDoesNotAddContentEncodingToRequestIfNotPresent() throws Exception {
2955 testDoesNotAddHeaderToRequestIfNotPresent("Content-Encoding");
2956 }
2957
2958 @Test
2959 public void testDoesNotAddContentRangeToRequestIfNotPresent() throws Exception {
2960 testDoesNotAddHeaderToRequestIfNotPresent("Content-Range");
2961 }
2962
2963 @Test
2964 public void testDoesNotAddContentTypeToRequestIfNotPresent() throws Exception {
2965 testDoesNotAddHeaderToRequestIfNotPresent("Content-Type");
2966 }
2967
2968 @Test
2969 public void testDoesNotAddContentEncodingHeaderToRequestIfNotPresent() throws Exception {
2970 testDoesNotAddHeaderToRequestIfNotPresent("Content-Encoding");
2971 }
2972
2973 @Test
2974 public void testDoesNotAddContentRangeHeaderToRequestIfNotPresent() throws Exception {
2975 testDoesNotAddHeaderToRequestIfNotPresent("Content-Range");
2976 }
2977
2978 @Test
2979 public void testDoesNotAddContentTypeHeaderToRequestIfNotPresent() throws Exception {
2980 testDoesNotAddHeaderToRequestIfNotPresent("Content-Type");
2981 }
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994 public void testCachedEntityBodyIsUsedForResponseAfter304Validation() throws Exception {
2995 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2996 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2997 resp1.setHeader("Cache-Control","max-age=3600");
2998 resp1.setHeader("ETag","\"etag\"");
2999
3000 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3001 req2.setHeader("Cache-Control","max-age=0, max-stale=0");
3002 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
3003
3004 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3005
3006 execute(req1);
3007
3008 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3009
3010 final ClassicHttpResponse result = execute(req2);
3011
3012 final InputStream i1 = resp1.getEntity().getContent();
3013 final InputStream i2 = result.getEntity().getContent();
3014 int b1, b2;
3015 while((b1 = i1.read()) != -1) {
3016 b2 = i2.read();
3017 Assertions.assertEquals(b1, b2);
3018 }
3019 b2 = i2.read();
3020 Assertions.assertEquals(-1, b2);
3021 i1.close();
3022 i2.close();
3023 }
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036 private void decorateWithEndToEndHeaders(final ClassicHttpResponse r) {
3037 r.setHeader("Allow","GET");
3038 r.setHeader("Content-Encoding","gzip");
3039 r.setHeader("Content-Language","en");
3040 r.setHeader("Content-Length", "128");
3041 r.setHeader("Content-Location","http://foo.example.com/other");
3042 r.setHeader("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
3043 r.setHeader("Content-Type", "text/html;charset=utf-8");
3044 r.setHeader("Expires", DateUtils.formatStandardDate(Instant.now().plusSeconds(10)));
3045 r.setHeader("Last-Modified", DateUtils.formatStandardDate(Instant.now().minusSeconds(10)));
3046 r.setHeader("Location", "http://foo.example.com/other2");
3047 r.setHeader("Pragma", "x-pragma");
3048 r.setHeader("Retry-After","180");
3049 }
3050
3051 @Test
3052 public void testResponseIncludesCacheEntryEndToEndHeadersForResponseAfter304Validation() throws Exception {
3053 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3054 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
3055 resp1.setHeader("Cache-Control","max-age=3600");
3056 resp1.setHeader("ETag","\"etag\"");
3057 decorateWithEndToEndHeaders(resp1);
3058
3059 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3060 req2.setHeader("Cache-Control", "max-age=0, max-stale=0");
3061 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
3062 resp2.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
3063 resp2.setHeader("Server", "MockServer/1.0");
3064
3065 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3066
3067 execute(req1);
3068
3069 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(req2), Mockito.any())).thenReturn(resp2);
3070 final ClassicHttpResponse result = execute(req2);
3071
3072 final String[] endToEndHeaders = {
3073 "Cache-Control", "ETag", "Allow", "Content-Encoding",
3074 "Content-Language", "Content-Length", "Content-Location",
3075 "Content-MD5", "Content-Type", "Expires", "Last-Modified",
3076 "Location", "Pragma", "Retry-After"
3077 };
3078 for(final String h : endToEndHeaders) {
3079 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp1, h),
3080 HttpTestUtils.getCanonicalHeaderValue(result, h));
3081 }
3082 }
3083
3084 @Test
3085 public void testUpdatedEndToEndHeadersFrom304ArePassedOnResponseAndUpdatedInCacheEntry() throws Exception {
3086
3087 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3088 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
3089 resp1.setHeader("Cache-Control","max-age=3600");
3090 resp1.setHeader("ETag","\"etag\"");
3091 decorateWithEndToEndHeaders(resp1);
3092
3093 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3094 req2.setHeader("Cache-Control", "max-age=0, max-stale=0");
3095 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
3096 resp2.setHeader("Cache-Control", "max-age=1800");
3097 resp2.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
3098 resp2.setHeader("Server", "MockServer/1.0");
3099 resp2.setHeader("Allow", "GET,HEAD");
3100 resp2.setHeader("Content-Language", "en,en-us");
3101 resp2.setHeader("Content-Location", "http://foo.example.com/new");
3102 resp2.setHeader("Content-Type","text/html");
3103 resp2.setHeader("Expires", DateUtils.formatStandardDate(Instant.now().plusSeconds(5)));
3104 resp2.setHeader("Location", "http://foo.example.com/new2");
3105 resp2.setHeader("Pragma","x-new-pragma");
3106 resp2.setHeader("Retry-After","120");
3107
3108 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
3109
3110 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3111
3112 execute(req1);
3113
3114 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3115 final ClassicHttpResponse result1 = execute(req2);
3116 final ClassicHttpResponse result2 = execute(req3);
3117
3118 final String[] endToEndHeaders = {
3119 "Date", "Cache-Control", "Allow", "Content-Language",
3120 "Content-Location", "Content-Type", "Expires", "Location",
3121 "Pragma", "Retry-After"
3122 };
3123 for(final String h : endToEndHeaders) {
3124 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
3125 HttpTestUtils.getCanonicalHeaderValue(result1, h));
3126 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
3127 HttpTestUtils.getCanonicalHeaderValue(result2, h));
3128 }
3129 }
3130
3131
3132
3133
3134
3135 @Test
3136 public void testMultiHeadersAreSuccessfullyReplacedOn304Validation() throws Exception {
3137 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3138 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
3139 resp1.addHeader("Cache-Control","max-age=3600");
3140 resp1.addHeader("Cache-Control","public");
3141 resp1.setHeader("ETag","\"etag\"");
3142
3143 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3144 req2.setHeader("Cache-Control", "max-age=0, max-stale=0");
3145 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
3146 resp2.setHeader("Cache-Control", "max-age=1800");
3147
3148 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
3149
3150 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3151
3152 execute(req1);
3153
3154 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3155
3156 final ClassicHttpResponse result1 = execute(req2);
3157 final ClassicHttpResponse result2 = execute(req3);
3158
3159 final String h = "Cache-Control";
3160 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
3161 HttpTestUtils.getCanonicalHeaderValue(result1, h));
3162 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
3163 HttpTestUtils.getCanonicalHeaderValue(result2, h));
3164 }
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185 @Test
3186 public void testCannotCombinePartialResponseIfIncomingResponseDoesNotHaveACacheValidator() throws Exception {
3187
3188 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3189 req1.setHeader("Range","bytes=0-49");
3190
3191 final Instant now = Instant.now();
3192 final Instant oneSecondAgo = now.minusSeconds(1);
3193 final Instant twoSecondsAgo = Instant.now().plusSeconds(2);
3194
3195 final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3196 resp1.setEntity(HttpTestUtils.makeBody(50));
3197 resp1.setHeader("Server","MockServer/1.0");
3198 resp1.setHeader("Date", DateUtils.formatStandardDate(twoSecondsAgo));
3199 resp1.setHeader("Cache-Control","max-age=3600");
3200 resp1.setHeader("Content-Range","bytes 0-49/128");
3201 resp1.setHeader("ETag","\"etag1\"");
3202
3203 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3204
3205 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3206 req2.setHeader("Range","bytes=50-127");
3207
3208 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3209 resp2.setEntity(HttpTestUtils.makeBody(78));
3210 resp2.setHeader("Cache-Control","max-age=3600");
3211 resp2.setHeader("Content-Range","bytes 50-127/128");
3212 resp2.setHeader("Server","MockServer/1.0");
3213 resp2.setHeader("Date", DateUtils.formatStandardDate(oneSecondAgo));
3214
3215 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3216
3217 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
3218
3219 final ClassicHttpResponse resp3 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
3220 resp3.setEntity(HttpTestUtils.makeBody(128));
3221 resp3.setHeader("Server","MockServer/1.0");
3222 resp3.setHeader("Date", DateUtils.formatStandardDate(now));
3223
3224 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
3225
3226 execute(req1);
3227 execute(req2);
3228 execute(req3);
3229 }
3230
3231 @Test
3232 public void testCannotCombinePartialResponseIfCacheEntryDoesNotHaveACacheValidator() throws Exception {
3233
3234 final Instant now = Instant.now();
3235 final Instant oneSecondAgo = now.minusSeconds(1);
3236 final Instant twoSecondsAgo = Instant.now().plusSeconds(2);
3237
3238 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3239 req1.setHeader("Range","bytes=0-49");
3240
3241 final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3242 resp1.setEntity(HttpTestUtils.makeBody(50));
3243 resp1.setHeader("Cache-Control","max-age=3600");
3244 resp1.setHeader("Content-Range","bytes 0-49/128");
3245 resp1.setHeader("Server","MockServer/1.0");
3246 resp1.setHeader("Date", DateUtils.formatStandardDate(twoSecondsAgo));
3247
3248 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3249
3250 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3251 req2.setHeader("Range","bytes=50-127");
3252
3253 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3254 resp2.setEntity(HttpTestUtils.makeBody(78));
3255 resp2.setHeader("Cache-Control","max-age=3600");
3256 resp2.setHeader("Content-Range","bytes 50-127/128");
3257 resp2.setHeader("ETag","\"etag1\"");
3258 resp2.setHeader("Server","MockServer/1.0");
3259 resp2.setHeader("Date", DateUtils.formatStandardDate(oneSecondAgo));
3260
3261 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3262
3263 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
3264
3265 final ClassicHttpResponse resp3 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
3266 resp3.setEntity(HttpTestUtils.makeBody(128));
3267 resp3.setHeader("Server","MockServer/1.0");
3268 resp3.setHeader("Date", DateUtils.formatStandardDate(now));
3269
3270 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
3271
3272 execute(req1);
3273 execute(req2);
3274 execute(req3);
3275 }
3276
3277 @Test
3278 public void testCannotCombinePartialResponseIfCacheValidatorsDoNotStronglyMatch() throws Exception {
3279
3280 final Instant now = Instant.now();
3281 final Instant oneSecondAgo = now.minusSeconds(1);
3282 final Instant twoSecondsAgo = Instant.now().plusSeconds(2);
3283
3284 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3285 req1.setHeader("Range","bytes=0-49");
3286
3287 final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3288 resp1.setEntity(HttpTestUtils.makeBody(50));
3289 resp1.setHeader("Cache-Control","max-age=3600");
3290 resp1.setHeader("Content-Range","bytes 0-49/128");
3291 resp1.setHeader("ETag","\"etag1\"");
3292 resp1.setHeader("Server","MockServer/1.0");
3293 resp1.setHeader("Date", DateUtils.formatStandardDate(twoSecondsAgo));
3294
3295 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3296
3297 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3298 req2.setHeader("Range","bytes=50-127");
3299
3300 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3301 resp2.setEntity(HttpTestUtils.makeBody(78));
3302 resp2.setHeader("Cache-Control","max-age=3600");
3303 resp2.setHeader("Content-Range","bytes 50-127/128");
3304 resp2.setHeader("ETag","\"etag2\"");
3305 resp2.setHeader("Server","MockServer/1.0");
3306 resp2.setHeader("Date", DateUtils.formatStandardDate(oneSecondAgo));
3307
3308 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3309
3310 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
3311
3312 final ClassicHttpResponse resp3 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
3313 resp3.setEntity(HttpTestUtils.makeBody(128));
3314 resp3.setHeader("Server","MockServer/1.0");
3315 resp3.setHeader("Date", DateUtils.formatStandardDate(now));
3316
3317 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
3318
3319 execute(req1);
3320 execute(req2);
3321 execute(req3);
3322 }
3323
3324 @Test
3325 public void testMustDiscardLeastRecentPartialResponseIfIncomingRequestDoesNotHaveCacheValidator() throws Exception {
3326
3327 final Instant now = Instant.now();
3328 final Instant oneSecondAgo = now.minusSeconds(1);
3329 final Instant twoSecondsAgo = Instant.now().plusSeconds(2);
3330
3331 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3332 req1.setHeader("Range","bytes=0-49");
3333
3334 final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3335 resp1.setEntity(HttpTestUtils.makeBody(50));
3336 resp1.setHeader("Cache-Control","max-age=3600");
3337 resp1.setHeader("Content-Range","bytes 0-49/128");
3338 resp1.setHeader("ETag","\"etag1\"");
3339 resp1.setHeader("Server","MockServer/1.0");
3340 resp1.setHeader("Date", DateUtils.formatStandardDate(twoSecondsAgo));
3341
3342 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3343
3344 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3345 req2.setHeader("Range","bytes=50-127");
3346
3347 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3348 resp2.setEntity(HttpTestUtils.makeBody(78));
3349 resp2.setHeader("Cache-Control","max-age=3600");
3350 resp2.setHeader("Content-Range","bytes 50-127/128");
3351 resp2.setHeader("Server","MockServer/1.0");
3352 resp2.setHeader("Date", DateUtils.formatStandardDate(oneSecondAgo));
3353
3354 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3355
3356 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
3357 req3.setHeader("Range","bytes=0-49");
3358
3359 final ClassicHttpResponse resp3 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
3360 resp3.setEntity(HttpTestUtils.makeBody(128));
3361 resp3.setHeader("Server","MockServer/1.0");
3362 resp3.setHeader("Date", DateUtils.formatStandardDate(now));
3363
3364
3365 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
3366
3367 execute(req1);
3368 execute(req2);
3369 execute(req3);
3370 }
3371
3372 @Test
3373 public void testMustDiscardLeastRecentPartialResponseIfCachedResponseDoesNotHaveCacheValidator() throws Exception {
3374
3375 final Instant now = Instant.now();
3376 final Instant oneSecondAgo = now.minusSeconds(1);
3377 final Instant twoSecondsAgo = Instant.now().plusSeconds(2);
3378
3379 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3380 req1.setHeader("Range","bytes=0-49");
3381
3382 final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3383 resp1.setEntity(HttpTestUtils.makeBody(50));
3384 resp1.setHeader("Cache-Control","max-age=3600");
3385 resp1.setHeader("Content-Range","bytes 0-49/128");
3386 resp1.setHeader("Server","MockServer/1.0");
3387 resp1.setHeader("Date", DateUtils.formatStandardDate(twoSecondsAgo));
3388
3389 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3390
3391 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3392 req2.setHeader("Range","bytes=50-127");
3393
3394 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3395 resp2.setEntity(HttpTestUtils.makeBody(78));
3396 resp2.setHeader("Cache-Control","max-age=3600");
3397 resp2.setHeader("Content-Range","bytes 50-127/128");
3398 resp2.setHeader("ETag","\"etag1\"");
3399 resp2.setHeader("Server","MockServer/1.0");
3400 resp2.setHeader("Date", DateUtils.formatStandardDate(oneSecondAgo));
3401
3402 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3403
3404 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
3405 req3.setHeader("Range","bytes=0-49");
3406
3407 final ClassicHttpResponse resp3 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
3408 resp3.setEntity(HttpTestUtils.makeBody(128));
3409 resp3.setHeader("Server","MockServer/1.0");
3410 resp3.setHeader("Date", DateUtils.formatStandardDate(now));
3411
3412
3413 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
3414
3415 execute(req1);
3416 execute(req2);
3417 execute(req3);
3418 }
3419
3420 @Test
3421 public void testMustDiscardLeastRecentPartialResponseIfCacheValidatorsDoNotStronglyMatch() throws Exception {
3422
3423 final Instant now = Instant.now();
3424 final Instant oneSecondAgo = now.minusSeconds(1);
3425 final Instant twoSecondsAgo = Instant.now().plusSeconds(2);
3426
3427 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3428 req1.setHeader("Range","bytes=0-49");
3429
3430 final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3431 resp1.setEntity(HttpTestUtils.makeBody(50));
3432 resp1.setHeader("Cache-Control","max-age=3600");
3433 resp1.setHeader("Content-Range","bytes 0-49/128");
3434 resp1.setHeader("Etag","\"etag1\"");
3435 resp1.setHeader("Server","MockServer/1.0");
3436 resp1.setHeader("Date", DateUtils.formatStandardDate(twoSecondsAgo));
3437
3438 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3439
3440 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3441 req2.setHeader("Range","bytes=50-127");
3442
3443 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3444 resp2.setEntity(HttpTestUtils.makeBody(78));
3445 resp2.setHeader("Cache-Control","max-age=3600");
3446 resp2.setHeader("Content-Range","bytes 50-127/128");
3447 resp2.setHeader("ETag","\"etag2\"");
3448 resp2.setHeader("Server","MockServer/1.0");
3449 resp2.setHeader("Date", DateUtils.formatStandardDate(oneSecondAgo));
3450
3451 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3452
3453 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
3454 req3.setHeader("Range","bytes=0-49");
3455
3456 final ClassicHttpResponse resp3 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
3457 resp3.setEntity(HttpTestUtils.makeBody(128));
3458 resp3.setHeader("Server","MockServer/1.0");
3459 resp3.setHeader("Date", DateUtils.formatStandardDate(now));
3460
3461
3462 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
3463
3464 execute(req1);
3465 execute(req2);
3466 execute(req3);
3467 }
3468
3469 @Test
3470 public void testMustDiscardLeastRecentPartialResponseIfCacheValidatorsDoNotStronglyMatchEvenIfResponsesOutOfOrder() throws Exception {
3471
3472 final Instant now = Instant.now();
3473 final Instant oneSecondAgo = now.minusSeconds(1);
3474 final Instant twoSecondsAgo = Instant.now().plusSeconds(2);
3475
3476 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3477 req1.setHeader("Range","bytes=0-49");
3478
3479 final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3480 resp1.setEntity(HttpTestUtils.makeBody(50));
3481 resp1.setHeader("Cache-Control","max-age=3600");
3482 resp1.setHeader("Content-Range","bytes 0-49/128");
3483 resp1.setHeader("Etag","\"etag1\"");
3484 resp1.setHeader("Server","MockServer/1.0");
3485 resp1.setHeader("Date", DateUtils.formatStandardDate(oneSecondAgo));
3486
3487 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3488
3489 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3490 req2.setHeader("Range","bytes=50-127");
3491
3492 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3493 resp2.setEntity(HttpTestUtils.makeBody(78));
3494 resp2.setHeader("Cache-Control","max-age=3600");
3495 resp2.setHeader("Content-Range","bytes 50-127/128");
3496 resp2.setHeader("ETag","\"etag2\"");
3497 resp2.setHeader("Server","MockServer/1.0");
3498 resp2.setHeader("Date", DateUtils.formatStandardDate(twoSecondsAgo));
3499
3500 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3501
3502 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
3503 req3.setHeader("Range","bytes=50-127");
3504
3505 final ClassicHttpResponse resp3 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
3506 resp3.setEntity(HttpTestUtils.makeBody(128));
3507 resp3.setHeader("Server","MockServer/1.0");
3508 resp3.setHeader("Date", DateUtils.formatStandardDate(now));
3509
3510
3511 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
3512
3513 execute(req1);
3514 execute(req2);
3515 execute(req3);
3516 }
3517
3518 @Test
3519 public void testMustDiscardCachedPartialResponseIfCacheValidatorsDoNotStronglyMatchAndDateHeadersAreEqual() throws Exception {
3520
3521 final Instant now = Instant.now();
3522 final Instant oneSecondAgo = now.minusSeconds(1);
3523
3524 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3525 req1.setHeader("Range","bytes=0-49");
3526
3527 final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3528 resp1.setEntity(HttpTestUtils.makeBody(50));
3529 resp1.setHeader("Cache-Control","max-age=3600");
3530 resp1.setHeader("Content-Range","bytes 0-49/128");
3531 resp1.setHeader("Etag","\"etag1\"");
3532 resp1.setHeader("Server","MockServer/1.0");
3533 resp1.setHeader("Date", DateUtils.formatStandardDate(oneSecondAgo));
3534
3535 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3536
3537 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3538 req2.setHeader("Range","bytes=50-127");
3539
3540 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
3541 resp2.setEntity(HttpTestUtils.makeBody(78));
3542 resp2.setHeader("Cache-Control","max-age=3600");
3543 resp2.setHeader("Content-Range","bytes 50-127/128");
3544 resp2.setHeader("ETag","\"etag2\"");
3545 resp2.setHeader("Server","MockServer/1.0");
3546 resp2.setHeader("Date", DateUtils.formatStandardDate(oneSecondAgo));
3547
3548 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3549
3550 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
3551 req3.setHeader("Range","bytes=0-49");
3552
3553 final ClassicHttpResponse resp3 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
3554 resp3.setEntity(HttpTestUtils.makeBody(128));
3555 resp3.setHeader("Server","MockServer/1.0");
3556 resp3.setHeader("Date", DateUtils.formatStandardDate(now));
3557
3558
3559 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
3560
3561 execute(req1);
3562 execute(req2);
3563 execute(req3);
3564 }
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575 @Test
3576 public void testCannotUseVariantCacheEntryIfNotAllSelectingRequestHeadersMatch() throws Exception {
3577
3578 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3579 req1.setHeader("Accept-Encoding","gzip");
3580
3581 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
3582 resp1.setHeader("ETag","\"etag1\"");
3583 resp1.setHeader("Cache-Control","max-age=3600");
3584 resp1.setHeader("Vary","Accept-Encoding");
3585
3586 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3587
3588 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3589 req2.removeHeaders("Accept-Encoding");
3590
3591 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
3592 resp2.setHeader("ETag","\"etag1\"");
3593 resp2.setHeader("Cache-Control","max-age=3600");
3594
3595
3596 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3597
3598 execute(req1);
3599 execute(req2);
3600 }
3601
3602
3603
3604
3605
3606
3607
3608 @Test
3609 public void testCannotServeFromCacheForVaryStar() throws Exception {
3610 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3611
3612 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
3613 resp1.setHeader("ETag","\"etag1\"");
3614 resp1.setHeader("Cache-Control","max-age=3600");
3615 resp1.setHeader("Vary","*");
3616
3617 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3618
3619 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3620
3621 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
3622 resp2.setHeader("ETag","\"etag1\"");
3623 resp2.setHeader("Cache-Control","max-age=3600");
3624
3625
3626 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3627
3628 execute(req1);
3629 execute(req2);
3630 }
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654 @Test
3655 public void testNonmatchingVariantCannotBeServedFromCacheUnlessConditionallyValidated() throws Exception {
3656
3657 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3658 req1.setHeader("User-Agent","MyBrowser/1.0");
3659
3660 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
3661 resp1.setHeader("ETag","\"etag1\"");
3662 resp1.setHeader("Cache-Control","max-age=3600");
3663 resp1.setHeader("Vary","User-Agent");
3664 resp1.setHeader("Content-Type","application/octet-stream");
3665
3666 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
3667 req2.setHeader("User-Agent","MyBrowser/1.5");
3668
3669 final ClassicHttpResponse resp200 = HttpTestUtils.make200Response();
3670 resp200.setHeader("ETag","\"etag1\"");
3671 resp200.setHeader("Vary","User-Agent");
3672
3673 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3674
3675 execute(req1);
3676
3677 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(req2), Mockito.any())).thenReturn(resp200);
3678
3679 final ClassicHttpResponse result = execute(req2);
3680
3681 Assertions.assertEquals(HttpStatus.SC_OK, result.getCode());
3682
3683 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
3684
3685 Assertions.assertTrue(HttpTestUtils.semanticallyTransparent(resp200, result));
3686 }
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698 protected void testUnsafeOperationInvalidatesCacheForThatUri(
3699 final ClassicHttpRequest unsafeReq) throws Exception {
3700 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
3701 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
3702 resp1.setHeader("Cache-Control","public, max-age=3600");
3703
3704 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3705
3706 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
3707
3708 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3709
3710 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
3711 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
3712 resp3.setHeader("Cache-Control","public, max-age=3600");
3713
3714
3715 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
3716
3717 execute(req1);
3718 execute(unsafeReq);
3719 execute(req3);
3720 }
3721
3722 protected ClassicHttpRequest makeRequestWithBody(final String method, final String requestUri) {
3723 final ClassicHttpRequest req = new BasicClassicHttpRequest(method, requestUri);
3724 final int nbytes = 128;
3725 req.setEntity(HttpTestUtils.makeBody(nbytes));
3726 req.setHeader("Content-Length", Long.toString(nbytes));
3727 return req;
3728 }
3729
3730 @Test
3731 public void testPutToUriInvalidatesCacheForThatUri() throws Exception {
3732 final ClassicHttpRequest req = makeRequestWithBody("PUT","/");
3733 testUnsafeOperationInvalidatesCacheForThatUri(req);
3734 }
3735
3736 @Test
3737 public void testDeleteToUriInvalidatesCacheForThatUri() throws Exception {
3738 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE","/");
3739 testUnsafeOperationInvalidatesCacheForThatUri(req);
3740 }
3741
3742 @Test
3743 public void testPostToUriInvalidatesCacheForThatUri() throws Exception {
3744 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
3745 testUnsafeOperationInvalidatesCacheForThatUri(req);
3746 }
3747
3748 protected void testUnsafeMethodInvalidatesCacheForHeaderUri(
3749 final ClassicHttpRequest unsafeReq) throws Exception {
3750 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/content");
3751 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
3752 resp1.setHeader("Cache-Control","public, max-age=3600");
3753
3754 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3755
3756 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
3757
3758 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3759
3760 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/content");
3761 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
3762 resp3.setHeader("Cache-Control","public, max-age=3600");
3763
3764
3765 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
3766
3767 execute(req1);
3768 execute(unsafeReq);
3769 execute(req3);
3770 }
3771
3772 protected void testUnsafeMethodInvalidatesCacheForUriInContentLocationHeader(
3773 final ClassicHttpRequest unsafeReq) throws Exception {
3774 unsafeReq.setHeader("Content-Location","http://foo.example.com/content");
3775 testUnsafeMethodInvalidatesCacheForHeaderUri(unsafeReq);
3776 }
3777
3778 protected void testUnsafeMethodInvalidatesCacheForRelativeUriInContentLocationHeader(
3779 final ClassicHttpRequest unsafeReq) throws Exception {
3780 unsafeReq.setHeader("Content-Location","/content");
3781 testUnsafeMethodInvalidatesCacheForHeaderUri(unsafeReq);
3782 }
3783
3784 protected void testUnsafeMethodInvalidatesCacheForUriInLocationHeader(
3785 final ClassicHttpRequest unsafeReq) throws Exception {
3786 unsafeReq.setHeader("Location","http://foo.example.com/content");
3787 testUnsafeMethodInvalidatesCacheForHeaderUri(unsafeReq);
3788 }
3789
3790 @Test
3791 public void testPutInvalidatesCacheForThatUriInContentLocationHeader() throws Exception {
3792 final ClassicHttpRequest req2 = makeRequestWithBody("PUT","/");
3793 testUnsafeMethodInvalidatesCacheForUriInContentLocationHeader(req2);
3794 }
3795
3796 @Test
3797 public void testPutInvalidatesCacheForThatUriInLocationHeader() throws Exception {
3798 final ClassicHttpRequest req = makeRequestWithBody("PUT","/");
3799 testUnsafeMethodInvalidatesCacheForUriInLocationHeader(req);
3800 }
3801
3802 @Test
3803 public void testPutInvalidatesCacheForThatUriInRelativeContentLocationHeader() throws Exception {
3804 final ClassicHttpRequest req = makeRequestWithBody("PUT","/");
3805 testUnsafeMethodInvalidatesCacheForRelativeUriInContentLocationHeader(req);
3806 }
3807
3808 @Test
3809 public void testDeleteInvalidatesCacheForThatUriInContentLocationHeader() throws Exception {
3810 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
3811 testUnsafeMethodInvalidatesCacheForUriInContentLocationHeader(req);
3812 }
3813
3814 @Test
3815 public void testDeleteInvalidatesCacheForThatUriInRelativeContentLocationHeader() throws Exception {
3816 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
3817 testUnsafeMethodInvalidatesCacheForRelativeUriInContentLocationHeader(req);
3818 }
3819
3820 @Test
3821 public void testDeleteInvalidatesCacheForThatUriInLocationHeader() throws Exception {
3822 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
3823 testUnsafeMethodInvalidatesCacheForUriInLocationHeader(req);
3824 }
3825
3826 @Test
3827 public void testPostInvalidatesCacheForThatUriInContentLocationHeader() throws Exception {
3828 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
3829 testUnsafeMethodInvalidatesCacheForUriInContentLocationHeader(req);
3830 }
3831
3832 @Test
3833 public void testPostInvalidatesCacheForThatUriInLocationHeader() throws Exception {
3834 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
3835 testUnsafeMethodInvalidatesCacheForUriInLocationHeader(req);
3836 }
3837
3838 @Test
3839 public void testPostInvalidatesCacheForRelativeUriInContentLocationHeader() throws Exception {
3840 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
3841 testUnsafeMethodInvalidatesCacheForRelativeUriInContentLocationHeader(req);
3842 }
3843
3844
3845
3846
3847
3848
3849
3850 protected void testUnsafeMethodDoesNotInvalidateCacheForHeaderUri(
3851 final ClassicHttpRequest unsafeReq) throws Exception {
3852
3853 final HttpHost otherHost = new HttpHost("bar.example.com", 80);
3854 final HttpRoute otherRoute = new HttpRoute(otherHost);
3855 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/content");
3856 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
3857 resp1.setHeader("Cache-Control","public, max-age=3600");
3858
3859 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
3860
3861 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/content");
3862
3863 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
3864
3865 execute(req1);
3866
3867 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
3868
3869 execute(unsafeReq);
3870 execute(req3);
3871 }
3872
3873 protected void testUnsafeMethodDoesNotInvalidateCacheForUriInContentLocationHeadersFromOtherHosts(
3874 final ClassicHttpRequest unsafeReq) throws Exception {
3875 unsafeReq.setHeader("Content-Location","http://bar.example.com/content");
3876 testUnsafeMethodDoesNotInvalidateCacheForHeaderUri(unsafeReq);
3877 }
3878
3879 protected void testUnsafeMethodDoesNotInvalidateCacheForUriInLocationHeadersFromOtherHosts(
3880 final ClassicHttpRequest unsafeReq) throws Exception {
3881 unsafeReq.setHeader("Location","http://bar.example.com/content");
3882 testUnsafeMethodDoesNotInvalidateCacheForHeaderUri(unsafeReq);
3883 }
3884
3885 @Test
3886 public void testPutDoesNotInvalidateCacheForUriInContentLocationHeadersFromOtherHosts() throws Exception {
3887 final ClassicHttpRequest req = makeRequestWithBody("PUT","/");
3888 testUnsafeMethodDoesNotInvalidateCacheForUriInContentLocationHeadersFromOtherHosts(req);
3889 }
3890
3891 @Test
3892 public void testPutDoesNotInvalidateCacheForUriInLocationHeadersFromOtherHosts() throws Exception {
3893 final ClassicHttpRequest req = makeRequestWithBody("PUT","/");
3894 testUnsafeMethodDoesNotInvalidateCacheForUriInLocationHeadersFromOtherHosts(req);
3895 }
3896
3897 @Test
3898 public void testPostDoesNotInvalidateCacheForUriInContentLocationHeadersFromOtherHosts() throws Exception {
3899 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
3900 testUnsafeMethodDoesNotInvalidateCacheForUriInContentLocationHeadersFromOtherHosts(req);
3901 }
3902
3903 @Test
3904 public void testPostDoesNotInvalidateCacheForUriInLocationHeadersFromOtherHosts() throws Exception {
3905 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
3906 testUnsafeMethodDoesNotInvalidateCacheForUriInLocationHeadersFromOtherHosts(req);
3907 }
3908
3909 @Test
3910 public void testDeleteDoesNotInvalidateCacheForUriInContentLocationHeadersFromOtherHosts() throws Exception {
3911 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
3912 testUnsafeMethodDoesNotInvalidateCacheForUriInContentLocationHeadersFromOtherHosts(req);
3913 }
3914
3915 @Test
3916 public void testDeleteDoesNotInvalidateCacheForUriInLocationHeadersFromOtherHosts() throws Exception {
3917 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
3918 testUnsafeMethodDoesNotInvalidateCacheForUriInLocationHeadersFromOtherHosts(req);
3919 }
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930 private void testRequestIsWrittenThroughToOrigin(final ClassicHttpRequest req) throws Exception {
3931 final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
3932 final ClassicHttpRequest wrapper = req;
3933 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(wrapper), Mockito.any())).thenReturn(resp);
3934
3935 execute(wrapper);
3936 }
3937
3938 @Test
3939 public void testOPTIONSRequestsAreWrittenThroughToOrigin() throws Exception {
3940 final ClassicHttpRequest req = new BasicClassicHttpRequest("OPTIONS","*");
3941 testRequestIsWrittenThroughToOrigin(req);
3942 }
3943
3944 @Test
3945 public void testPOSTRequestsAreWrittenThroughToOrigin() throws Exception {
3946 final ClassicHttpRequest req = new BasicClassicHttpRequest("POST","/");
3947 req.setEntity(HttpTestUtils.makeBody(128));
3948 req.setHeader("Content-Length","128");
3949 testRequestIsWrittenThroughToOrigin(req);
3950 }
3951
3952 @Test
3953 public void testPUTRequestsAreWrittenThroughToOrigin() throws Exception {
3954 final ClassicHttpRequest req = new BasicClassicHttpRequest("PUT","/");
3955 req.setEntity(HttpTestUtils.makeBody(128));
3956 req.setHeader("Content-Length","128");
3957 testRequestIsWrittenThroughToOrigin(req);
3958 }
3959
3960 @Test
3961 public void testDELETERequestsAreWrittenThroughToOrigin() throws Exception {
3962 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
3963 testRequestIsWrittenThroughToOrigin(req);
3964 }
3965
3966 @Test
3967 public void testTRACERequestsAreWrittenThroughToOrigin() throws Exception {
3968 final ClassicHttpRequest req = new BasicClassicHttpRequest("TRACE","/");
3969 testRequestIsWrittenThroughToOrigin(req);
3970 }
3971
3972 @Test
3973 public void testCONNECTRequestsAreWrittenThroughToOrigin() throws Exception {
3974 final ClassicHttpRequest req = new BasicClassicHttpRequest("CONNECT","/");
3975 testRequestIsWrittenThroughToOrigin(req);
3976 }
3977
3978 @Test
3979 public void testUnknownMethodRequestsAreWrittenThroughToOrigin() throws Exception {
3980 final ClassicHttpRequest req = new BasicClassicHttpRequest("UNKNOWN","/");
3981 testRequestIsWrittenThroughToOrigin(req);
3982 }
3983
3984
3985
3986
3987
3988
3989
3990
3991 @Test
3992 public void testTransmitsAgeHeaderIfIncomingAgeHeaderTooBig() throws Exception {
3993 final String reallyOldAge = "1" + Long.MAX_VALUE;
3994 originResponse.setHeader("Age",reallyOldAge);
3995
3996 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
3997
3998 final ClassicHttpResponse result = execute(request);
3999
4000 Assertions.assertEquals("2147483648",
4001 result.getFirstHeader("Age").getValue());
4002 }
4003
4004
4005
4006
4007
4008
4009
4010 @Test
4011 public void testDoesNotModifyAllowHeaderWithUnknownMethods() throws Exception {
4012 final String allowHeaderValue = "GET, HEAD, FOOBAR";
4013 originResponse.setHeader("Allow",allowHeaderValue);
4014 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4015 final ClassicHttpResponse result = execute(request);
4016 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(originResponse,"Allow"),
4017 HttpTestUtils.getCanonicalHeaderValue(result, "Allow"));
4018 }
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045 protected void testSharedCacheRevalidatesAuthorizedResponse(
4046 final ClassicHttpResponse authorizedResponse, final int minTimes, final int maxTimes) throws Exception {
4047 if (config.isSharedCache()) {
4048 final String authorization = StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Q=";
4049 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4050 req1.setHeader("Authorization",authorization);
4051
4052 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
4053 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
4054 resp2.setHeader("Cache-Control","max-age=3600");
4055
4056 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(authorizedResponse);
4057
4058 execute(req1);
4059
4060 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
4061
4062 execute(req2);
4063
4064 Mockito.verify(mockExecChain, Mockito.atLeast(1 + minTimes)).proceed(Mockito.any(), Mockito.any());
4065 Mockito.verify(mockExecChain, Mockito.atMost(1 + maxTimes)).proceed(Mockito.any(), Mockito.any());
4066 }
4067 }
4068
4069 @Test
4070 public void testSharedCacheMustNotNormallyCacheAuthorizedResponses() throws Exception {
4071 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
4072 resp.setHeader("Cache-Control","max-age=3600");
4073 resp.setHeader("ETag","\"etag\"");
4074 testSharedCacheRevalidatesAuthorizedResponse(resp, 1, 1);
4075 }
4076
4077 @Test
4078 public void testSharedCacheMayCacheAuthorizedResponsesWithSMaxAgeHeader() throws Exception {
4079 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
4080 resp.setHeader("Cache-Control","s-maxage=3600");
4081 resp.setHeader("ETag","\"etag\"");
4082 testSharedCacheRevalidatesAuthorizedResponse(resp, 0, 1);
4083 }
4084
4085 @Test
4086 public void testSharedCacheMustRevalidateAuthorizedResponsesWhenSMaxAgeIsZero() throws Exception {
4087 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
4088 resp.setHeader("Cache-Control","s-maxage=0");
4089 resp.setHeader("ETag","\"etag\"");
4090 testSharedCacheRevalidatesAuthorizedResponse(resp, 1, 1);
4091 }
4092
4093 @Test
4094 public void testSharedCacheMayCacheAuthorizedResponsesWithMustRevalidate() throws Exception {
4095 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
4096 resp.setHeader("Cache-Control","must-revalidate");
4097 resp.setHeader("ETag","\"etag\"");
4098 testSharedCacheRevalidatesAuthorizedResponse(resp, 0, 1);
4099 }
4100
4101 @Test
4102 public void testSharedCacheMayCacheAuthorizedResponsesWithCacheControlPublic() throws Exception {
4103 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
4104 resp.setHeader("Cache-Control","public");
4105 testSharedCacheRevalidatesAuthorizedResponse(resp, 0, 1);
4106 }
4107
4108 protected void testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponse(
4109 final ClassicHttpResponse authorizedResponse) throws Exception {
4110 if (config.isSharedCache()) {
4111 final String authorization1 = StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Q=";
4112 final String authorization2 = StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Qy";
4113
4114 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4115 req1.setHeader("Authorization",authorization1);
4116
4117 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
4118 req2.setHeader("Authorization",authorization2);
4119
4120 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
4121
4122 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(authorizedResponse);
4123
4124 execute(req1);
4125
4126 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
4127
4128 execute(req2);
4129
4130 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
4131 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
4132
4133 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
4134 Assertions.assertEquals(2, allRequests.size());
4135
4136 final ClassicHttpRequest captured = allRequests.get(1);
4137 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(req2, "Authorization"),
4138 HttpTestUtils.getCanonicalHeaderValue(captured, "Authorization"));
4139 }
4140 }
4141
4142 @Test
4143 public void testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponsesWithSMaxAge() throws Exception {
4144 final Instant now = Instant.now();
4145 final Instant tenSecondsAgo = now.minusSeconds(10);
4146 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4147 resp1.setHeader("Date",DateUtils.formatStandardDate(tenSecondsAgo));
4148 resp1.setHeader("ETag","\"etag\"");
4149 resp1.setHeader("Cache-Control","s-maxage=5");
4150
4151 testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponse(resp1);
4152 }
4153
4154 @Test
4155 public void testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponsesWithMustRevalidate() throws Exception {
4156 final Instant now = Instant.now();
4157 final Instant tenSecondsAgo = now.minusSeconds(10);
4158 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4159 resp1.setHeader("Date",DateUtils.formatStandardDate(tenSecondsAgo));
4160 resp1.setHeader("ETag","\"etag\"");
4161 resp1.setHeader("Cache-Control","maxage=5, must-revalidate");
4162
4163 testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponse(resp1);
4164 }
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179 @Test
4180 public void testWarning110IsAddedToStaleResponses() throws Exception {
4181 final Instant now = Instant.now();
4182 final Instant tenSecondsAgo = now.minusSeconds(10);
4183 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4184 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4185 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
4186 resp1.setHeader("Cache-Control","max-age=5");
4187 resp1.setHeader("Etag","\"etag\"");
4188
4189 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
4190 req2.setHeader("Cache-Control","max-stale=60");
4191
4192 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
4193
4194 execute(req1);
4195
4196 final ClassicHttpResponse result = execute(req2);
4197
4198 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
4199 Mockito.verify(mockExecChain, Mockito.atMostOnce()).proceed(reqCapture.capture(), Mockito.any());
4200
4201 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
4202 if (allRequests.isEmpty()) {
4203 boolean found110Warning = false;
4204 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.WARNING);
4205 while (it.hasNext()) {
4206 final HeaderElement elt = it.next();
4207 final String[] parts = elt.getName().split("\\s");
4208 if ("110".equals(parts[0])) {
4209 found110Warning = true;
4210 break;
4211 }
4212 }
4213 Assertions.assertTrue(found110Warning);
4214 }
4215 }
4216
4217
4218
4219
4220
4221
4222 @Test
4223 public void testDoesNotTransmitNoCacheDirectivesWithFieldsDownstream() throws Exception {
4224 request.setHeader("Cache-Control","no-cache=\"X-Field\"");
4225
4226 try {
4227 execute(request);
4228 } catch (final ClientProtocolException acceptable) {
4229 }
4230
4231 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
4232 Mockito.verify(mockExecChain, Mockito.atMostOnce()).proceed(reqCapture.capture(), Mockito.any());
4233
4234 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
4235
4236 if (!allRequests.isEmpty()) {
4237 final ClassicHttpRequest captured = reqCapture.getValue();
4238 final Iterator<HeaderElement> it = MessageSupport.iterate(captured, HttpHeaders.CACHE_CONTROL);
4239 while (it.hasNext()) {
4240 final HeaderElement elt = it.next();
4241 if ("no-cache".equals(elt.getName())) {
4242 Assertions.assertNull(elt.getValue());
4243 }
4244 }
4245 }
4246 }
4247
4248
4249
4250
4251
4252
4253
4254 protected void testCacheIsNotUsedWhenRespondingToRequest(final ClassicHttpRequest req) throws Exception {
4255 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4256 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4257 resp1.setHeader("Etag","\"etag\"");
4258 resp1.setHeader("Cache-Control","max-age=3600");
4259
4260 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
4261
4262 execute(req1);
4263
4264 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
4265 resp2.setHeader("Etag","\"etag2\"");
4266 resp2.setHeader("Cache-Control","max-age=1200");
4267
4268 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
4269
4270 final ClassicHttpResponse result = execute(req);
4271
4272 Assertions.assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
4273
4274 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
4275 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
4276
4277 final ClassicHttpRequest captured = reqCapture.getValue();
4278 Assertions.assertTrue(HttpTestUtils.equivalent(req, captured));
4279 }
4280
4281 @Test
4282 public void testCacheIsNotUsedWhenRespondingToRequestWithCacheControlNoCache() throws Exception {
4283 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
4284 req.setHeader("Cache-Control","no-cache");
4285 testCacheIsNotUsedWhenRespondingToRequest(req);
4286 }
4287
4288 @Test
4289 public void testCacheIsNotUsedWhenRespondingToRequestWithPragmaNoCache() throws Exception {
4290 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
4291 req.setHeader("Pragma","no-cache");
4292 testCacheIsNotUsedWhenRespondingToRequest(req);
4293 }
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304 protected void testStaleCacheResponseMustBeRevalidatedWithOrigin(
4305 final ClassicHttpResponse staleResponse) throws Exception {
4306 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4307
4308 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
4309 req2.setHeader("Cache-Control","max-stale=3600");
4310 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
4311 resp2.setHeader("ETag","\"etag2\"");
4312 resp2.setHeader("Cache-Control","max-age=5, must-revalidate");
4313
4314
4315 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(staleResponse);
4316
4317 execute(req1);
4318
4319 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
4320
4321 execute(req2);
4322
4323 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
4324 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
4325
4326 final ClassicHttpRequest reval = reqCapture.getValue();
4327 boolean foundMaxAge0 = false;
4328 final Iterator<HeaderElement> it = MessageSupport.iterate(reval, HttpHeaders.CACHE_CONTROL);
4329 while (it.hasNext()) {
4330 final HeaderElement elt = it.next();
4331 if ("max-age".equalsIgnoreCase(elt.getName())
4332 && "0".equals(elt.getValue())) {
4333 foundMaxAge0 = true;
4334 }
4335 }
4336 Assertions.assertTrue(foundMaxAge0);
4337 }
4338
4339 @Test
4340 public void testStaleEntryWithMustRevalidateIsNotUsedWithoutRevalidatingWithOrigin() throws Exception {
4341 final ClassicHttpResponse response = HttpTestUtils.make200Response();
4342 final Instant now = Instant.now();
4343 final Instant tenSecondsAgo = now.minusSeconds(10);
4344 response.setHeader("Date",DateUtils.formatStandardDate(tenSecondsAgo));
4345 response.setHeader("ETag","\"etag1\"");
4346 response.setHeader("Cache-Control","max-age=5, must-revalidate");
4347
4348 testStaleCacheResponseMustBeRevalidatedWithOrigin(response);
4349 }
4350
4351
4352
4353
4354
4355
4356 protected void testGenerates504IfCannotRevalidateStaleResponse(
4357 final ClassicHttpResponse staleResponse) throws Exception {
4358 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4359
4360 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
4361
4362 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(staleResponse);
4363
4364 execute(req1);
4365
4366 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new SocketTimeoutException());
4367
4368 final ClassicHttpResponse result = execute(req2);
4369
4370 Assertions.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT,
4371 result.getCode());
4372 }
4373
4374 @Test
4375 public void testGenerates504IfCannotRevalidateAMustRevalidateEntry() throws Exception {
4376 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4377 final Instant now = Instant.now();
4378 final Instant tenSecondsAgo = now.minusSeconds(10);
4379 resp1.setHeader("ETag","\"etag\"");
4380 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
4381 resp1.setHeader("Cache-Control","max-age=5,must-revalidate");
4382
4383 testGenerates504IfCannotRevalidateStaleResponse(resp1);
4384 }
4385
4386
4387
4388
4389
4390
4391
4392 @Test
4393 public void testStaleEntryWithProxyRevalidateOnSharedCacheIsNotUsedWithoutRevalidatingWithOrigin() throws Exception {
4394 if (config.isSharedCache()) {
4395 final ClassicHttpResponse response = HttpTestUtils.make200Response();
4396 final Instant now = Instant.now();
4397 final Instant tenSecondsAgo = now.minusSeconds(10);
4398 response.setHeader("Date",DateUtils.formatStandardDate(tenSecondsAgo));
4399 response.setHeader("ETag","\"etag1\"");
4400 response.setHeader("Cache-Control","max-age=5, proxy-revalidate");
4401
4402 testStaleCacheResponseMustBeRevalidatedWithOrigin(response);
4403 }
4404 }
4405
4406 @Test
4407 public void testGenerates504IfSharedCacheCannotRevalidateAProxyRevalidateEntry() throws Exception {
4408 if (config.isSharedCache()) {
4409 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4410 final Instant now = Instant.now();
4411 final Instant tenSecondsAgo = now.minusSeconds(10);
4412 resp1.setHeader("ETag","\"etag\"");
4413 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
4414 resp1.setHeader("Cache-Control","max-age=5,proxy-revalidate");
4415
4416 testGenerates504IfCannotRevalidateStaleResponse(resp1);
4417 }
4418 }
4419
4420
4421
4422
4423
4424
4425
4426 @Test
4427 public void testCacheControlPrivateIsNotCacheableBySharedCache() throws Exception {
4428 if (config.isSharedCache()) {
4429 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4430 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4431 resp1.setHeader("Cache-Control", "private,max-age=3600");
4432
4433 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
4434
4435 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
4436 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
4437
4438 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
4439
4440 execute(req1);
4441 execute(req2);
4442 }
4443 }
4444
4445 @Test
4446 public void testCacheControlPrivateOnFieldIsNotReturnedBySharedCache() throws Exception {
4447 if (config.isSharedCache()) {
4448 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4449 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4450 resp1.setHeader("X-Personal", "stuff");
4451 resp1.setHeader("Cache-Control", "private=\"X-Personal\",s-maxage=3600");
4452
4453 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
4454
4455 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
4456 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
4457
4458
4459 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
4460
4461 execute(req1);
4462 final ClassicHttpResponse result = execute(req2);
4463 Assertions.assertNull(result.getFirstHeader("X-Personal"));
4464
4465 Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
4466 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
4467 }
4468 }
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478 @Test
4479 public void testNoCacheCannotSatisfyASubsequentRequestWithoutRevalidation() throws Exception {
4480 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4481 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4482 resp1.setHeader("ETag","\"etag\"");
4483 resp1.setHeader("Cache-Control","no-cache");
4484
4485 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
4486
4487 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
4488 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
4489
4490
4491 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
4492
4493 execute(req1);
4494 execute(req2);
4495 }
4496
4497 @Test
4498 public void testNoCacheCannotSatisfyASubsequentRequestWithoutRevalidationEvenWithContraryIndications() throws Exception {
4499 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4500 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4501 resp1.setHeader("ETag","\"etag\"");
4502 resp1.setHeader("Cache-Control","no-cache,s-maxage=3600");
4503
4504 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
4505
4506 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
4507 req2.setHeader("Cache-Control","max-stale=7200");
4508 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
4509
4510
4511 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
4512
4513 execute(req1);
4514 execute(req2);
4515 }
4516
4517
4518
4519
4520
4521
4522
4523 @Test
4524 public void testNoCacheOnFieldIsNotReturnedWithoutRevalidation() throws Exception {
4525 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4526 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4527 resp1.setHeader("ETag","\"etag\"");
4528 resp1.setHeader("X-Stuff","things");
4529 resp1.setHeader("Cache-Control","no-cache=\"X-Stuff\", max-age=3600");
4530
4531 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
4532
4533 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
4534 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
4535 resp2.setHeader("ETag","\"etag\"");
4536 resp2.setHeader("X-Stuff","things");
4537 resp2.setHeader("Cache-Control","no-cache=\"X-Stuff\",max-age=3600");
4538
4539 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
4540
4541 execute(req1);
4542 final ClassicHttpResponse result = execute(req2);
4543
4544 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
4545 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(reqCapture.capture(), Mockito.any());
4546
4547 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
4548 if (allRequests.isEmpty()) {
4549 Assertions.assertNull(result.getFirstHeader("X-Stuff"));
4550 }
4551 }
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568 @Test
4569 public void testNoStoreOnRequestIsNotStoredInCache() throws Exception {
4570 request.setHeader("Cache-Control","no-store");
4571 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4572
4573 execute(request);
4574
4575 Mockito.verifyNoInteractions(mockCache);
4576 }
4577
4578 @Test
4579 public void testNoStoreOnRequestIsNotStoredInCacheEvenIfResponseMarkedCacheable() throws Exception {
4580 request.setHeader("Cache-Control","no-store");
4581 originResponse.setHeader("Cache-Control","max-age=3600");
4582 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4583
4584 execute(request);
4585
4586 Mockito.verifyNoInteractions(mockCache);
4587 }
4588
4589 @Test
4590 public void testNoStoreOnResponseIsNotStoredInCache() throws Exception {
4591 originResponse.setHeader("Cache-Control","no-store");
4592 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4593
4594 execute(request);
4595
4596 Mockito.verifyNoInteractions(mockCache);
4597 }
4598
4599 @Test
4600 public void testNoStoreOnResponseIsNotStoredInCacheEvenWithContraryIndicators() throws Exception {
4601 originResponse.setHeader("Cache-Control","no-store,max-age=3600");
4602 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4603
4604 execute(request);
4605
4606 Mockito.verifyNoInteractions(mockCache);
4607 }
4608
4609
4610
4611
4612
4613
4614 @Test
4615 public void testOrderOfMultipleContentEncodingHeaderValuesIsPreserved() throws Exception {
4616 originResponse.addHeader("Content-Encoding","gzip");
4617 originResponse.addHeader("Content-Encoding","deflate");
4618 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4619
4620 final ClassicHttpResponse result = execute(request);
4621 int total_encodings = 0;
4622 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.CONTENT_ENCODING);
4623 while (it.hasNext()) {
4624 final HeaderElement elt = it.next();
4625 switch(total_encodings) {
4626 case 0:
4627 Assertions.assertEquals("gzip", elt.getName());
4628 break;
4629 case 1:
4630 Assertions.assertEquals("deflate", elt.getName());
4631 break;
4632 default:
4633 Assertions.fail("too many encodings");
4634 }
4635 total_encodings++;
4636 }
4637 Assertions.assertEquals(2, total_encodings);
4638 }
4639
4640 @Test
4641 public void testOrderOfMultipleParametersInContentEncodingHeaderIsPreserved() throws Exception {
4642 originResponse.addHeader("Content-Encoding","gzip,deflate");
4643 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4644
4645 final ClassicHttpResponse result = execute(request);
4646 int total_encodings = 0;
4647 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.CONTENT_ENCODING);
4648 while (it.hasNext()) {
4649 final HeaderElement elt = it.next();
4650 switch(total_encodings) {
4651 case 0:
4652 Assertions.assertEquals("gzip", elt.getName());
4653 break;
4654 case 1:
4655 Assertions.assertEquals("deflate", elt.getName());
4656 break;
4657 default:
4658 Assertions.fail("too many encodings");
4659 }
4660 total_encodings++;
4661 }
4662 Assertions.assertEquals(2, total_encodings);
4663 }
4664
4665
4666
4667
4668
4669
4670
4671 @Test
4672 public void testCacheDoesNotAssumeContentLocationHeaderIndicatesAnotherCacheableResource() throws Exception {
4673 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/foo");
4674 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4675 resp1.setHeader("Cache-Control","public,max-age=3600");
4676 resp1.setHeader("Etag","\"etag\"");
4677 resp1.setHeader("Content-Location","http://foo.example.com/bar");
4678
4679 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/bar");
4680 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
4681 resp2.setHeader("Cache-Control","public,max-age=3600");
4682 resp2.setHeader("Etag","\"etag\"");
4683
4684 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
4685 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
4686
4687 execute(req1);
4688 execute(req2);
4689 }
4690
4691
4692
4693
4694
4695
4696
4697 @Test
4698 public void testCachedResponsesWithMissingDateHeadersShouldBeAssignedOne() throws Exception {
4699 originResponse.removeHeaders("Date");
4700 originResponse.setHeader("Cache-Control","public");
4701 originResponse.setHeader("ETag","\"etag\"");
4702
4703 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4704
4705 final ClassicHttpResponse result = execute(request);
4706 Assertions.assertNotNull(result.getFirstHeader("Date"));
4707 }
4708
4709
4710
4711
4712
4713
4714
4715
4716 private void testInvalidExpiresHeaderIsTreatedAsStale(
4717 final String expiresHeader) throws Exception {
4718 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4719 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4720 resp1.setHeader("Cache-Control","public");
4721 resp1.setHeader("ETag","\"etag\"");
4722 resp1.setHeader("Expires", expiresHeader);
4723
4724 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
4725 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
4726
4727 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
4728
4729 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
4730
4731 execute(req1);
4732 execute(req2);
4733 }
4734
4735 @Test
4736 public void testMalformedExpiresHeaderIsTreatedAsStale() throws Exception {
4737 testInvalidExpiresHeaderIsTreatedAsStale("garbage");
4738 }
4739
4740 @Test
4741 public void testExpiresZeroHeaderIsTreatedAsStale() throws Exception {
4742 testInvalidExpiresHeaderIsTreatedAsStale("0");
4743 }
4744
4745
4746
4747
4748
4749
4750 @Test
4751 public void testExpiresHeaderEqualToDateHeaderIsTreatedAsStale() throws Exception {
4752 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
4753 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
4754 resp1.setHeader("Cache-Control","public");
4755 resp1.setHeader("ETag","\"etag\"");
4756 resp1.setHeader("Expires", resp1.getFirstHeader("Date").getValue());
4757
4758 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
4759 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
4760
4761 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
4762
4763 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
4764
4765 execute(req1);
4766 execute(req2);
4767 }
4768
4769
4770
4771
4772
4773
4774 @Test
4775 public void testDoesNotModifyServerResponseHeader() throws Exception {
4776 final String server = "MockServer/1.0";
4777 originResponse.setHeader("Server", server);
4778
4779 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4780
4781 final ClassicHttpResponse result = execute(request);
4782 Assertions.assertEquals(server, result.getFirstHeader("Server").getValue());
4783 }
4784
4785
4786
4787
4788
4789
4790 @Test
4791 public void testOrderOfMultipleTransferEncodingHeadersIsPreserved() throws Exception {
4792 originResponse.addHeader("Transfer-Encoding","chunked");
4793 originResponse.addHeader("Transfer-Encoding","x-transfer");
4794
4795 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4796
4797 final ClassicHttpResponse result = execute(request);
4798 int transfer_encodings = 0;
4799 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.TRANSFER_ENCODING);
4800 while (it.hasNext()) {
4801 final HeaderElement elt = it.next();
4802 switch(transfer_encodings) {
4803 case 0:
4804 Assertions.assertEquals("chunked",elt.getName());
4805 break;
4806 case 1:
4807 Assertions.assertEquals("x-transfer",elt.getName());
4808 break;
4809 default:
4810 Assertions.fail("too many transfer encodings");
4811 }
4812 transfer_encodings++;
4813 }
4814 Assertions.assertEquals(2, transfer_encodings);
4815 }
4816
4817 @Test
4818 public void testOrderOfMultipleTransferEncodingsInSingleHeadersIsPreserved() throws Exception {
4819 originResponse.addHeader("Transfer-Encoding","chunked, x-transfer");
4820
4821 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4822
4823 final ClassicHttpResponse result = execute(request);
4824 int transfer_encodings = 0;
4825 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.TRANSFER_ENCODING);
4826 while (it.hasNext()) {
4827 final HeaderElement elt = it.next();
4828 switch(transfer_encodings) {
4829 case 0:
4830 Assertions.assertEquals("chunked",elt.getName());
4831 break;
4832 case 1:
4833 Assertions.assertEquals("x-transfer",elt.getName());
4834 break;
4835 default:
4836 Assertions.fail("too many transfer encodings");
4837 }
4838 transfer_encodings++;
4839 }
4840 Assertions.assertEquals(2, transfer_encodings);
4841 }
4842
4843
4844
4845
4846
4847
4848
4849
4850
4851 @Test
4852 public void testVaryStarIsNotGeneratedByProxy() throws Exception {
4853 request.setHeader("User-Agent","my-agent/1.0");
4854 originResponse.setHeader("Cache-Control","public, max-age=3600");
4855 originResponse.setHeader("Vary","User-Agent");
4856 originResponse.setHeader("ETag","\"etag\"");
4857
4858 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4859
4860 final ClassicHttpResponse result = execute(request);
4861 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.VARY);
4862 while (it.hasNext()) {
4863 final HeaderElement elt = it.next();
4864 Assertions.assertNotEquals("*", elt.getName());
4865 }
4866 }
4867
4868
4869
4870
4871
4872
4873
4874
4875 @Test
4876 public void testProperlyFormattedViaHeaderIsAddedToRequests() throws Exception {
4877 request.removeHeaders("Via");
4878 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4879
4880 execute(request);
4881
4882 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
4883 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
4884
4885 final ClassicHttpRequest captured = reqCapture.getValue();
4886 final String via = captured.getFirstHeader("Via").getValue();
4887 assertValidViaHeader(via);
4888 }
4889
4890 @Test
4891 public void testProperlyFormattedViaHeaderIsAddedToResponses() throws Exception {
4892 originResponse.removeHeaders("Via");
4893 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4894 final ClassicHttpResponse result = execute(request);
4895 assertValidViaHeader(result.getFirstHeader("Via").getValue());
4896 }
4897
4898
4899 private void assertValidViaHeader(final String via) {
4900
4901
4902
4903
4904
4905
4906
4907 final String[] parts = via.split("\\s+");
4908 Assertions.assertTrue(parts.length >= 2);
4909
4910
4911 final String receivedProtocol = parts[0];
4912 final String[] protocolParts = receivedProtocol.split("/");
4913 Assertions.assertTrue(protocolParts.length >= 1);
4914 Assertions.assertTrue(protocolParts.length <= 2);
4915
4916 final String tokenRegexp = "[^\\p{Cntrl}()<>@,;:\\\\\"/\\[\\]?={} \\t]+";
4917 for(final String protocolPart : protocolParts) {
4918 Assertions.assertTrue(Pattern.matches(tokenRegexp, protocolPart));
4919 }
4920
4921
4922 if (!Pattern.matches(tokenRegexp, parts[1])) {
4923
4924 new HttpHost(parts[1]);
4925 }
4926
4927
4928 if (parts.length > 2) {
4929 final StringBuilder buf = new StringBuilder(parts[2]);
4930 for(int i=3; i<parts.length; i++) {
4931 buf.append(" "); buf.append(parts[i]);
4932 }
4933 Assertions.assertTrue(isValidComment(buf.toString()));
4934 }
4935 }
4936
4937 private boolean isValidComment(final String s) {
4938 final String leafComment = "^\\(([^\\p{Cntrl}()]|\\\\\\p{ASCII})*\\)$";
4939 final String nestedPrefix = "^\\(([^\\p{Cntrl}()]|\\\\\\p{ASCII})*\\(";
4940 final String nestedSuffix = "\\)([^\\p{Cntrl}()]|\\\\\\p{ASCII})*\\)$";
4941
4942 if (Pattern.matches(leafComment,s)) {
4943 return true;
4944 }
4945 final Matcher pref = Pattern.compile(nestedPrefix).matcher(s);
4946 final Matcher suff = Pattern.compile(nestedSuffix).matcher(s);
4947 if (!pref.find()) {
4948 return false;
4949 }
4950 if (!suff.find()) {
4951 return false;
4952 }
4953 return isValidComment(s.substring(pref.end() - 1, suff.start() + 1));
4954 }
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
4966
4967 @Test
4968 public void testViaHeaderOnRequestProperlyRecordsClientProtocol() throws Exception {
4969 final ClassicHttpRequest originalRequest = new BasicClassicHttpRequest("GET", "/");
4970 originalRequest.setVersion(HttpVersion.HTTP_1_0);
4971 request = originalRequest;
4972 request.removeHeaders("Via");
4973
4974 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4975
4976 execute(request);
4977
4978 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
4979 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
4980
4981 final ClassicHttpRequest captured = reqCapture.getValue();
4982 final String via = captured.getFirstHeader("Via").getValue();
4983 final String protocol = via.split("\\s+")[0];
4984 final String[] protoParts = protocol.split("/");
4985 if (protoParts.length > 1) {
4986 Assertions.assertTrue("http".equalsIgnoreCase(protoParts[0]));
4987 }
4988 Assertions.assertEquals("1.0",protoParts[protoParts.length-1]);
4989 }
4990
4991 @Test
4992 public void testViaHeaderOnResponseProperlyRecordsOriginProtocol() throws Exception {
4993
4994 originResponse = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
4995 originResponse.setVersion(HttpVersion.HTTP_1_0);
4996
4997 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
4998
4999 final ClassicHttpResponse result = execute(request);
5000
5001 final String via = result.getFirstHeader("Via").getValue();
5002 final String protocol = via.split("\\s+")[0];
5003 final String[] protoParts = protocol.split("/");
5004 Assertions.assertTrue(protoParts.length >= 1);
5005 Assertions.assertTrue(protoParts.length <= 2);
5006 if (protoParts.length > 1) {
5007 Assertions.assertTrue("http".equalsIgnoreCase(protoParts[0]));
5008 }
5009 Assertions.assertEquals("1.0", protoParts[protoParts.length - 1]);
5010 }
5011
5012
5013
5014
5015
5016
5017 @Test
5018 public void testRetainsWarningHeadersReceivedFromUpstream() throws Exception {
5019 originResponse.removeHeaders("Warning");
5020 final String warning = "199 fred \"misc\"";
5021 originResponse.addHeader("Warning", warning);
5022 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
5023
5024 final ClassicHttpResponse result = execute(request);
5025 Assertions.assertEquals(warning,
5026 result.getFirstHeader("Warning").getValue());
5027 }
5028
5029
5030
5031
5032
5033
5034
5035
5036 @Test
5037 public void testUpdatesWarningHeadersOnValidation() throws Exception {
5038 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
5039 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
5040
5041 final Instant now = Instant.now();
5042 final Instant twentySecondsAgo = now.plusSeconds(20);
5043 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
5044 resp1.setHeader("Date", DateUtils.formatStandardDate(twentySecondsAgo));
5045 resp1.setHeader("Cache-Control","public,max-age=5");
5046 resp1.setHeader("ETag", "\"etag1\"");
5047 final String oldWarning = "113 wilma \"stale\"";
5048 resp1.setHeader("Warning", oldWarning);
5049
5050 final Instant tenSecondsAgo = now.minusSeconds(10);
5051 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
5052 resp2.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
5053 resp2.setHeader("ETag", "\"etag1\"");
5054 final String newWarning = "113 betty \"stale too\"";
5055 resp2.setHeader("Warning", newWarning);
5056
5057 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
5058 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
5059
5060 execute(req1);
5061 final ClassicHttpResponse result = execute(req2);
5062
5063 boolean oldWarningFound = false;
5064 boolean newWarningFound = false;
5065 for(final Header h : result.getHeaders("Warning")) {
5066 for(final String warnValue : h.getValue().split("\\s*,\\s*")) {
5067 if (oldWarning.equals(warnValue)) {
5068 oldWarningFound = true;
5069 } else if (newWarning.equals(warnValue)) {
5070 newWarningFound = true;
5071 }
5072 }
5073 }
5074 Assertions.assertFalse(oldWarningFound);
5075 Assertions.assertTrue(newWarningFound);
5076 }
5077
5078
5079
5080
5081
5082
5083
5084
5085 @Test
5086 public void testWarnDatesAreAddedToWarningsOnLowerProtocolVersions() throws Exception {
5087 final String dateHdr = DateUtils.formatStandardDate(Instant.now());
5088 final String origWarning = "110 fred \"stale\"";
5089 originResponse.setCode(HttpStatus.SC_OK);
5090 originResponse.setVersion(HttpVersion.HTTP_1_0);
5091 originResponse.addHeader("Warning", origWarning);
5092 originResponse.setHeader("Date", dateHdr);
5093 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
5094 final ClassicHttpResponse result = execute(request);
5095
5096
5097
5098
5099 if (HttpVersion.HTTP_1_0.greaterEquals(result.getVersion())) {
5100 Assertions.assertEquals(dateHdr, result.getFirstHeader("Date").getValue());
5101 boolean warningFound = false;
5102 final String targetWarning = origWarning + " \"" + dateHdr + "\"";
5103 for(final Header h : result.getHeaders("Warning")) {
5104 for(final String warning : h.getValue().split("\\s*,\\s*")) {
5105 if (targetWarning.equals(warning)) {
5106 warningFound = true;
5107 break;
5108 }
5109 }
5110 }
5111 Assertions.assertTrue(warningFound);
5112 }
5113 }
5114
5115
5116
5117
5118
5119
5120
5121
5122
5123
5124
5125 @Test
5126 public void testStripsBadlyDatedWarningsFromForwardedResponses() throws Exception {
5127 final Instant now = Instant.now();
5128 final Instant tenSecondsAgo = now.minusSeconds(10);
5129 originResponse.setHeader("Date", DateUtils.formatStandardDate(now));
5130 originResponse.addHeader("Warning", "110 fred \"stale\", 110 wilma \"stale\" \""
5131 + DateUtils.formatStandardDate(tenSecondsAgo) + "\"");
5132 originResponse.setHeader("Cache-Control","no-cache,no-store");
5133 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
5134
5135 final ClassicHttpResponse result = execute(request);
5136
5137 for(final Header h : result.getHeaders("Warning")) {
5138 Assertions.assertFalse(h.getValue().contains("wilma"));
5139 }
5140 }
5141
5142 @Test
5143 public void testStripsBadlyDatedWarningsFromStoredResponses() throws Exception {
5144 final Instant now = Instant.now();
5145 final Instant tenSecondsAgo = now.minusSeconds(10);
5146 originResponse.setHeader("Date", DateUtils.formatStandardDate(now));
5147 originResponse.addHeader("Warning", "110 fred \"stale\", 110 wilma \"stale\" \""
5148 + DateUtils.formatStandardDate(tenSecondsAgo) + "\"");
5149 originResponse.setHeader("Cache-Control","public,max-age=3600");
5150 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
5151
5152 final ClassicHttpResponse result = execute(request);
5153
5154 for(final Header h : result.getHeaders("Warning")) {
5155 Assertions.assertFalse(h.getValue().contains("wilma"));
5156 }
5157 }
5158
5159 @Test
5160 public void testRemovesWarningHeaderIfAllWarnValuesAreBadlyDated() throws Exception {
5161 final Instant now = Instant.now();
5162 final Instant tenSecondsAgo = now.minusSeconds(10);
5163 originResponse.setHeader("Date", DateUtils.formatStandardDate(now));
5164 originResponse.addHeader("Warning", "110 wilma \"stale\" \""
5165 + DateUtils.formatStandardDate(tenSecondsAgo) + "\"");
5166 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
5167
5168 final ClassicHttpResponse result = execute(request);
5169
5170 final Header[] warningHeaders = result.getHeaders("Warning");
5171 Assertions.assertTrue(warningHeaders == null || warningHeaders.length == 0);
5172 }
5173
5174 }