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