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