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