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