1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.client5.http.impl.cache;
28
29 import static org.hamcrest.MatcherAssert.assertThat;
30
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.net.SocketTimeoutException;
34 import java.time.Instant;
35 import java.time.temporal.ChronoUnit;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Random;
39
40 import org.apache.hc.client5.http.HttpRoute;
41 import org.apache.hc.client5.http.auth.StandardAuthScheme;
42 import org.apache.hc.client5.http.cache.HttpCacheContext;
43 import org.apache.hc.client5.http.cache.HttpCacheEntry;
44 import org.apache.hc.client5.http.classic.ExecChain;
45 import org.apache.hc.client5.http.classic.ExecRuntime;
46 import org.apache.hc.client5.http.utils.DateUtils;
47 import org.apache.hc.core5.http.ClassicHttpRequest;
48 import org.apache.hc.core5.http.ClassicHttpResponse;
49 import org.apache.hc.core5.http.Header;
50 import org.apache.hc.core5.http.HeaderElement;
51 import org.apache.hc.core5.http.HttpEntity;
52 import org.apache.hc.core5.http.HttpException;
53 import org.apache.hc.core5.http.HttpHeaders;
54 import org.apache.hc.core5.http.HttpHost;
55 import org.apache.hc.core5.http.HttpStatus;
56 import org.apache.hc.core5.http.Method;
57 import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
58 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
59 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
60 import org.apache.hc.core5.http.message.BasicHeader;
61 import org.apache.hc.core5.http.message.MessageSupport;
62 import org.hamcrest.MatcherAssert;
63 import org.junit.jupiter.api.Assertions;
64 import org.junit.jupiter.api.BeforeEach;
65 import org.junit.jupiter.api.Test;
66 import org.mockito.ArgumentCaptor;
67 import org.mockito.Mock;
68 import org.mockito.Mockito;
69 import org.mockito.MockitoAnnotations;
70
71
72
73
74
75 public class TestProtocolRequirements {
76
77 static final int MAX_BYTES = 1024;
78 static final int MAX_ENTRIES = 100;
79 static final int ENTITY_LENGTH = 128;
80
81 HttpHost host;
82 HttpRoute route;
83 HttpEntity body;
84 HttpCacheContext context;
85 @Mock
86 ExecChain mockExecChain;
87 @Mock
88 ExecRuntime mockExecRuntime;
89 @Mock
90 HttpCache mockCache;
91 ClassicHttpRequest request;
92 ClassicHttpResponse originResponse;
93 CacheConfig config;
94 CachingExec impl;
95 HttpCache cache;
96
97 @BeforeEach
98 public void setUp() throws Exception {
99 MockitoAnnotations.openMocks(this);
100 host = new HttpHost("foo.example.com", 80);
101
102 route = new HttpRoute(host);
103
104 body = HttpTestUtils.makeBody(ENTITY_LENGTH);
105
106 request = new BasicClassicHttpRequest("GET", "/");
107
108 context = HttpCacheContext.create();
109
110 originResponse = HttpTestUtils.make200Response();
111
112 config = CacheConfig.custom()
113 .setMaxCacheEntries(MAX_ENTRIES)
114 .setMaxObjectSize(MAX_BYTES)
115 .build();
116
117 cache = new BasicHttpCache(config);
118 impl = new CachingExec(cache, null, config);
119
120 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
121 }
122
123 public ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
124 return impl.execute(
125 ClassicRequestBuilder.copy(request).build(),
126 new ExecChain.Scope("test", route, request, mockExecRuntime, context),
127 mockExecChain);
128 }
129
130 @Test
131 public void testCacheMissOnGETUsesOriginResponse() throws Exception {
132
133 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(request), Mockito.any())).thenReturn(originResponse);
134
135 final ClassicHttpResponse result = execute(request);
136
137 Assertions.assertTrue(HttpTestUtils.semanticallyTransparent(originResponse, result));
138 }
139
140 private void testOrderOfMultipleHeadersIsPreservedOnResponses(final String h) throws Exception {
141 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
142
143 final ClassicHttpResponse result = execute(request);
144
145 Assertions.assertNotNull(result);
146 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(originResponse, h), HttpTestUtils
147 .getCanonicalHeaderValue(result, h));
148
149 }
150
151 @Test
152 public void testOrderOfMultipleAllowHeadersIsPreservedOnResponses() throws Exception {
153 originResponse = new BasicClassicHttpResponse(405, "Method Not Allowed");
154 originResponse.addHeader("Allow", "HEAD");
155 originResponse.addHeader("Allow", "DELETE");
156 testOrderOfMultipleHeadersIsPreservedOnResponses("Allow");
157 }
158
159 @Test
160 public void testOrderOfMultipleCacheControlHeadersIsPreservedOnResponses() throws Exception {
161 originResponse.addHeader("Cache-Control", "max-age=0");
162 originResponse.addHeader("Cache-Control", "no-store, must-revalidate");
163 testOrderOfMultipleHeadersIsPreservedOnResponses("Cache-Control");
164 }
165
166 @Test
167 public void testOrderOfMultipleContentEncodingHeadersIsPreservedOnResponses() throws Exception {
168 originResponse.addHeader("Content-Encoding", "gzip");
169 originResponse.addHeader("Content-Encoding", "compress");
170 testOrderOfMultipleHeadersIsPreservedOnResponses("Content-Encoding");
171 }
172
173 @Test
174 public void testOrderOfMultipleContentLanguageHeadersIsPreservedOnResponses() throws Exception {
175 originResponse.addHeader("Content-Language", "mi");
176 originResponse.addHeader("Content-Language", "en");
177 testOrderOfMultipleHeadersIsPreservedOnResponses("Content-Language");
178 }
179
180 @Test
181 public void testOrderOfMultipleViaHeadersIsPreservedOnResponses() throws Exception {
182 originResponse.addHeader(HttpHeaders.VIA, "1.0 fred, 1.1 nowhere.com (Apache/1.1)");
183 originResponse.addHeader(HttpHeaders.VIA, "1.0 ricky, 1.1 mertz, 1.0 lucy");
184 testOrderOfMultipleHeadersIsPreservedOnResponses(HttpHeaders.VIA);
185 }
186
187 @Test
188 public void testOrderOfMultipleWWWAuthenticateHeadersIsPreservedOnResponses() throws Exception {
189 originResponse.addHeader("WWW-Authenticate", "x-challenge-1");
190 originResponse.addHeader("WWW-Authenticate", "x-challenge-2");
191 testOrderOfMultipleHeadersIsPreservedOnResponses("WWW-Authenticate");
192 }
193
194 private void testUnknownResponseStatusCodeIsNotCached(final int code) throws Exception {
195
196 originResponse = new BasicClassicHttpResponse(code, "Moo");
197 originResponse.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
198 originResponse.setHeader("Server", "MockOrigin/1.0");
199 originResponse.setHeader("Cache-Control", "max-age=3600");
200 originResponse.setEntity(body);
201
202 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
203
204 execute(request);
205
206
207 Mockito.verifyNoInteractions(mockCache);
208 }
209
210 @Test
211 public void testUnknownResponseStatusCodesAreNotCached() throws Exception {
212 for (int i = 100; i <= 199; i++) {
213 testUnknownResponseStatusCodeIsNotCached(i);
214 }
215 for (int i = 207; i <= 299; i++) {
216 testUnknownResponseStatusCodeIsNotCached(i);
217 }
218 for (int i = 308; i <= 399; i++) {
219 testUnknownResponseStatusCodeIsNotCached(i);
220 }
221 for (int i = 418; i <= 499; i++) {
222 testUnknownResponseStatusCodeIsNotCached(i);
223 }
224 for (int i = 506; i <= 999; i++) {
225 testUnknownResponseStatusCodeIsNotCached(i);
226 }
227 }
228
229 @Test
230 public void testUnknownHeadersOnRequestsAreForwarded() throws Exception {
231 request.addHeader("X-Unknown-Header", "blahblah");
232 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
233
234 execute(request);
235
236 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
237 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
238 final ClassicHttpRequest forwarded = reqCapture.getValue();
239 MatcherAssert.assertThat(forwarded, ContainsHeaderMatcher.contains("X-Unknown-Header", "blahblah"));
240 }
241
242 @Test
243 public void testUnknownHeadersOnResponsesAreForwarded() throws Exception {
244 originResponse.addHeader("X-Unknown-Header", "blahblah");
245 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
246
247 final ClassicHttpResponse result = execute(request);
248 MatcherAssert.assertThat(result, ContainsHeaderMatcher.contains("X-Unknown-Header", "blahblah"));
249 }
250
251 @Test
252 public void testResponsesToOPTIONSAreNotCacheable() throws Exception {
253 request = new BasicClassicHttpRequest("OPTIONS", "/");
254 originResponse.addHeader("Cache-Control", "max-age=3600");
255
256 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
257
258 execute(request);
259
260 Mockito.verifyNoInteractions(mockCache);
261 }
262
263 @Test
264 public void testResponsesToPOSTWithoutCacheControlOrExpiresAreNotCached() throws Exception {
265
266 final BasicClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
267 post.setHeader("Content-Length", "128");
268 post.setEntity(HttpTestUtils.makeBody(128));
269
270 originResponse.removeHeaders("Cache-Control");
271 originResponse.removeHeaders("Expires");
272
273 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
274
275 execute(post);
276
277 Mockito.verifyNoInteractions(mockCache);
278 }
279
280 @Test
281 public void testResponsesToPUTsAreNotCached() throws Exception {
282
283 final BasicClassicHttpRequest put = new BasicClassicHttpRequest("PUT", "/");
284 put.setEntity(HttpTestUtils.makeBody(128));
285 put.addHeader("Content-Length", "128");
286
287 originResponse.setHeader("Cache-Control", "max-age=3600");
288
289 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
290
291 execute(put);
292
293 Mockito.verifyNoInteractions(mockCache);
294 }
295
296 @Test
297 public void testResponsesToDELETEsAreNotCached() throws Exception {
298
299 request = new BasicClassicHttpRequest("DELETE", "/");
300 originResponse.setHeader("Cache-Control", "max-age=3600");
301
302 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
303
304 execute(request);
305
306 Mockito.verifyNoInteractions(mockCache);
307 }
308
309 @Test
310 public void testResponsesToTRACEsAreNotCached() throws Exception {
311
312 request = new BasicClassicHttpRequest("TRACE", "/");
313 originResponse.setHeader("Cache-Control", "max-age=3600");
314
315 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
316
317 execute(request);
318
319 Mockito.verifyNoInteractions(mockCache);
320 }
321
322 @Test
323 public void test304ResponseGeneratedFromCacheIncludesDateHeader() throws Exception {
324
325 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
326 originResponse.setHeader("Cache-Control", "max-age=3600");
327 originResponse.setHeader("ETag", "\"etag\"");
328
329 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
330 req2.setHeader("If-None-Match", "\"etag\"");
331
332 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
333
334 execute(req1);
335 final ClassicHttpResponse result = execute(req2);
336
337 Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
338 Assertions.assertNotNull(result.getFirstHeader("Date"));
339 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
340 }
341
342 @Test
343 public void test304ResponseGeneratedFromCacheIncludesEtagIfOriginResponseDid() throws Exception {
344 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
345 originResponse.setHeader("Cache-Control", "max-age=3600");
346 originResponse.setHeader("ETag", "\"etag\"");
347
348 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
349 req2.setHeader("If-None-Match", "\"etag\"");
350
351 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
352
353 execute(req1);
354 final ClassicHttpResponse result = execute(req2);
355
356 Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
357 Assertions.assertNotNull(result.getFirstHeader("ETag"));
358 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
359 }
360
361 @Test
362 public void test304ResponseGeneratedFromCacheIncludesContentLocationIfOriginResponseDid() throws Exception {
363 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
364 originResponse.setHeader("Cache-Control", "max-age=3600");
365 originResponse.setHeader("Content-Location", "http://foo.example.com/other");
366 originResponse.setHeader("ETag", "\"etag\"");
367
368 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
369 req2.setHeader("If-None-Match", "\"etag\"");
370
371 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
372
373 execute(req1);
374 final ClassicHttpResponse result = execute(req2);
375
376 Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
377 Assertions.assertNotNull(result.getFirstHeader("Content-Location"));
378 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
379 }
380
381 @Test
382 public void test304ResponseGeneratedFromCacheIncludesExpiresCacheControlAndOrVaryIfResponseMightDiffer() throws Exception {
383
384 final Instant now = Instant.now();
385 final Instant inTwoHours = now.plus(2, ChronoUnit.HOURS);
386
387 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
388 req1.setHeader("Accept-Encoding", "gzip");
389
390 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
391 resp1.setHeader("ETag", "\"v1\"");
392 resp1.setHeader("Cache-Control", "max-age=7200");
393 resp1.setHeader("Expires", DateUtils.formatStandardDate(inTwoHours));
394 resp1.setHeader("Vary", "Accept-Encoding");
395 resp1.setEntity(HttpTestUtils.makeBody(ENTITY_LENGTH));
396
397 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
398 req2.setHeader("Accept-Encoding", "gzip");
399 req2.setHeader("Cache-Control", "no-cache");
400
401 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
402 resp2.setHeader("ETag", "\"v2\"");
403 resp2.setHeader("Cache-Control", "max-age=3600");
404 resp2.setHeader("Expires", DateUtils.formatStandardDate(inTwoHours));
405 resp2.setHeader("Vary", "Accept-Encoding");
406 resp2.setEntity(HttpTestUtils.makeBody(ENTITY_LENGTH));
407
408 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
409 req3.setHeader("Accept-Encoding", "gzip");
410 req3.setHeader("If-None-Match", "\"v2\"");
411
412 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
413
414 execute(req1);
415
416 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
417 execute(req2);
418
419 final ClassicHttpResponse result = execute(req3);
420
421 Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
422 Assertions.assertNotNull(result.getFirstHeader("Expires"));
423 Assertions.assertNotNull(result.getFirstHeader("Cache-Control"));
424 Assertions.assertNotNull(result.getFirstHeader("Vary"));
425 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
426 }
427
428 @Test
429 public void test304GeneratedFromCacheOnWeakValidatorDoesNotIncludeOtherEntityHeaders() throws Exception {
430
431 final Instant now = Instant.now();
432 final Instant oneHourAgo = now.minus(1, ChronoUnit.HOURS);
433
434 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
435
436 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
437 resp1.setHeader("ETag", "W/\"v1\"");
438 resp1.setHeader("Allow", "GET,HEAD");
439 resp1.setHeader("Content-Encoding", "x-coding");
440 resp1.setHeader("Content-Language", "en");
441 resp1.setHeader("Content-Length", "128");
442 resp1.setHeader("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
443 resp1.setHeader("Content-Type", "application/octet-stream");
444 resp1.setHeader("Last-Modified", DateUtils.formatStandardDate(oneHourAgo));
445 resp1.setHeader("Cache-Control", "max-age=7200");
446
447 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
448 req2.setHeader("If-None-Match", "W/\"v1\"");
449
450 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(req1), Mockito.any())).thenReturn(resp1);
451
452 execute(req1);
453 final ClassicHttpResponse result = execute(req2);
454
455 Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
456 Assertions.assertNull(result.getFirstHeader("Allow"));
457 Assertions.assertNull(result.getFirstHeader("Content-Encoding"));
458 Assertions.assertNull(result.getFirstHeader("Content-Length"));
459 Assertions.assertNull(result.getFirstHeader("Content-MD5"));
460 Assertions.assertNull(result.getFirstHeader("Content-Type"));
461 Assertions.assertNull(result.getFirstHeader("Last-Modified"));
462 Mockito.verify(mockExecChain, Mockito.times(1)).proceed(Mockito.any(), Mockito.any());
463 }
464
465 @Test
466 public void testNotModifiedOfNonCachedEntityShouldRevalidateWithUnconditionalGET() throws Exception {
467
468 final Instant now = Instant.now();
469
470
471 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
472 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
473 resp1.setHeader("ETag", "\"etag1\"");
474 resp1.setHeader("Cache-Control", "max-age=3600");
475
476
477 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
478 req2.setHeader("Cache-Control", "max-age=0,max-stale=0");
479
480
481 final ClassicHttpRequest unconditionalValidation = new BasicClassicHttpRequest("GET", "/");
482
483 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
484 resp1.setHeader("ETag", "\"etag2\"");
485 resp1.setHeader("Cache-Control", "max-age=3600");
486
487 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
488
489
490
491 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(unconditionalValidation), Mockito.any())).thenReturn(resp2);
492
493 execute(req1);
494 execute(req2);
495
496 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
497 }
498
499 @Test
500 public void testCacheEntryIsUpdatedWithNewFieldValuesIn304Response() throws Exception {
501
502 final Instant now = Instant.now();
503 final Instant inFiveSeconds = now.plusSeconds(5);
504
505 final ClassicHttpRequest initialRequest = new BasicClassicHttpRequest("GET", "/");
506
507 final ClassicHttpResponse cachedResponse = HttpTestUtils.make200Response();
508 cachedResponse.setHeader("Cache-Control", "max-age=3600");
509 cachedResponse.setHeader("ETag", "\"etag\"");
510
511 final ClassicHttpRequest secondRequest = new BasicClassicHttpRequest("GET", "/");
512 secondRequest.setHeader("Cache-Control", "max-age=0,max-stale=0");
513
514 final ClassicHttpRequest conditionalValidationRequest = new BasicClassicHttpRequest("GET", "/");
515 conditionalValidationRequest.setHeader("If-None-Match", "\"etag\"");
516
517
518 final ClassicHttpResponse conditionalResponse = HttpTestUtils.make304Response();
519 conditionalResponse.setHeader("Date", DateUtils.formatStandardDate(inFiveSeconds));
520 conditionalResponse.setHeader("Server", "MockUtils/1.0");
521 conditionalResponse.setHeader("ETag", "\"etag\"");
522 conditionalResponse.setHeader("X-Extra", "junk");
523
524
525 final ClassicHttpResponse unconditionalResponse = HttpTestUtils.make200Response();
526 unconditionalResponse.setHeader("Date", DateUtils.formatStandardDate(inFiveSeconds));
527 unconditionalResponse.setHeader("ETag", "\"etag\"");
528
529 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(cachedResponse);
530 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(conditionalValidationRequest), Mockito.any())).thenReturn(conditionalResponse);
531
532 execute(initialRequest);
533 final ClassicHttpResponse result = execute(secondRequest);
534
535 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
536
537 Assertions.assertEquals(DateUtils.formatStandardDate(inFiveSeconds), result.getFirstHeader("Date").getValue());
538 Assertions.assertEquals("junk", result.getFirstHeader("X-Extra").getValue());
539 }
540
541 @Test
542 public void testMustReturnACacheEntryIfItCanRevalidateIt() throws Exception {
543
544 final Instant now = Instant.now();
545 final Instant tenSecondsAgo = now.minusSeconds(10);
546 final Instant nineSecondsAgo = now.minusSeconds(9);
547 final Instant eightSecondsAgo = now.minusSeconds(8);
548
549 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo,
550 Method.GET, "/thing", null,
551 200, new Header[] {
552 new BasicHeader("Date", DateUtils.formatStandardDate(nineSecondsAgo)),
553 new BasicHeader("ETag", "\"etag\"")
554 }, HttpTestUtils.makeNullResource());
555
556 impl = new CachingExec(mockCache, null, config);
557
558 request = new BasicClassicHttpRequest("GET", "/thing");
559
560 final ClassicHttpRequest validate = new BasicClassicHttpRequest("GET", "/thing");
561 validate.setHeader("If-None-Match", "\"etag\"");
562
563 final ClassicHttpResponse notModified = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
564 notModified.setHeader("Date", DateUtils.formatStandardDate(now));
565 notModified.setHeader("ETag", "\"etag\"");
566
567 Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
568 new CacheMatch(new CacheHit("key", entry), null));
569 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(validate), Mockito.any())).thenReturn(notModified);
570 final HttpCacheEntry updated = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo,
571 Method.GET, "/thing", null,
572 200, new Header[] {
573 new BasicHeader("Date", DateUtils.formatStandardDate(now)),
574 new BasicHeader("ETag", "\"etag\"")
575 }, HttpTestUtils.makeNullResource());
576 Mockito.when(mockCache.update(
577 Mockito.any(),
578 Mockito.any(),
579 Mockito.any(),
580 Mockito.any(),
581 Mockito.any(),
582 Mockito.any()))
583 .thenReturn(new CacheHit("key", updated));
584
585 execute(request);
586
587 Mockito.verify(mockCache).update(
588 Mockito.any(),
589 Mockito.eq(host),
590 RequestEquivalent.eq(request),
591 ResponseEquivalent.eq(notModified),
592 Mockito.any(),
593 Mockito.any());
594 }
595
596 @Test
597 public void testMustReturnAFreshEnoughCacheEntryIfItHasIt() throws Exception {
598
599 final Instant now = Instant.now();
600 final Instant tenSecondsAgo = now.minusSeconds(10);
601 final Instant nineSecondsAgo = now.plusSeconds(9);
602 final Instant eightSecondsAgo = now.plusSeconds(8);
603
604 final Header[] hdrs = new Header[] {
605 new BasicHeader("Date", DateUtils.formatStandardDate(nineSecondsAgo)),
606 new BasicHeader("Cache-Control", "max-age=3600"),
607 new BasicHeader("Content-Length", "128")
608 };
609
610 final byte[] bytes = new byte[128];
611 new Random().nextBytes(bytes);
612
613 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
614
615 impl = new CachingExec(mockCache, null, config);
616 request = new BasicClassicHttpRequest("GET", "/thing");
617
618 Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
619 new CacheMatch(new CacheHit("key", entry), null));
620
621 final ClassicHttpResponse result = execute(request);
622
623 Assertions.assertEquals(200, result.getCode());
624 }
625
626 @Test
627 public void testAgeHeaderPopulatedFromCacheEntryCurrentAge() throws Exception {
628
629 final Instant now = Instant.now();
630 final Instant tenSecondsAgo = now.minusSeconds(10);
631 final Instant nineSecondsAgo = now.minusSeconds(9);
632 final Instant eightSecondsAgo = now.minusSeconds(8);
633
634 final Header[] hdrs = new Header[] {
635 new BasicHeader("Date", DateUtils.formatStandardDate(nineSecondsAgo)),
636 new BasicHeader("Cache-Control", "max-age=3600"),
637 new BasicHeader("Content-Length", "128")
638 };
639
640 final byte[] bytes = new byte[128];
641 new Random().nextBytes(bytes);
642
643 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
644
645 impl = new CachingExec(mockCache, null, config);
646 request = new BasicClassicHttpRequest("GET", "/");
647
648 Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
649 new CacheMatch(new CacheHit("key", entry), null));
650
651 final ClassicHttpResponse result = execute(request);
652
653 Assertions.assertEquals(200, result.getCode());
654
655
656
657
658
659
660 assertThat(result, ContainsHeaderMatcher.contains("Age", "10"));
661 }
662
663 @Test
664 public void testKeepsMostRecentDateHeaderForFreshResponse() throws Exception {
665
666 final Instant now = Instant.now();
667 final Instant inFiveSecond = now.plusSeconds(5);
668
669
670 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
671
672 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
673 resp1.setHeader("Date", DateUtils.formatStandardDate(inFiveSecond));
674 resp1.setHeader("ETag", "\"etag1\"");
675 resp1.setHeader("Cache-Control", "max-age=3600");
676 resp1.setHeader("Content-Length", "128");
677
678
679 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
680 req2.setHeader("Cache-Control", "no-cache");
681
682 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
683 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
684 resp2.setHeader("ETag", "\"etag2\"");
685 resp2.setHeader("Cache-Control", "max-age=3600");
686 resp2.setHeader("Content-Length", "128");
687
688 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
689
690 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
691
692 execute(req1);
693
694 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
695
696 execute(req2);
697 final ClassicHttpResponse result = execute(req3);
698 Assertions.assertEquals("\"etag1\"", result.getFirstHeader("ETag").getValue());
699 }
700
701 @Test
702 public void testValidationMustUseETagIfProvidedByOriginServer() throws Exception {
703
704 final Instant now = Instant.now();
705 final Instant tenSecondsAgo = now.minusSeconds(10);
706
707 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
708 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
709 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
710 resp1.setHeader("Cache-Control", "max-age=3600");
711 resp1.setHeader("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
712 resp1.setHeader("ETag", "W/\"etag\"");
713
714 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
715 req2.setHeader("Cache-Control", "max-age=0,max-stale=0");
716
717 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
718
719 execute(req1);
720 execute(req2);
721
722 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
723 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
724
725 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
726 Assertions.assertEquals(2, allRequests.size());
727 final ClassicHttpRequest validation = allRequests.get(1);
728 boolean foundETag = false;
729 final Iterator<HeaderElement> it = MessageSupport.iterate(validation, HttpHeaders.IF_MATCH);
730 while (it.hasNext()) {
731 final HeaderElement elt = it.next();
732 if ("W/\"etag\"".equals(elt.getName())) {
733 foundETag = true;
734 }
735 }
736 final Iterator<HeaderElement> it2 = MessageSupport.iterate(validation, HttpHeaders.IF_NONE_MATCH);
737 while (it2.hasNext()) {
738 final HeaderElement elt = it2.next();
739 if ("W/\"etag\"".equals(elt.getName())) {
740 foundETag = true;
741 }
742 }
743 Assertions.assertTrue(foundETag);
744 }
745
746 @Test
747 public void testConditionalRequestWhereNotAllValidatorsMatchCannotBeServedFromCache() throws Exception {
748 final Instant now = Instant.now();
749 final Instant tenSecondsAgo = now.minusSeconds(10);
750 final Instant twentySecondsAgo = now.plusSeconds(20);
751
752 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
753 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
754 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
755 resp1.setHeader("Cache-Control", "max-age=3600");
756 resp1.setHeader("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
757 resp1.setHeader("ETag", "W/\"etag\"");
758
759 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
760 req2.setHeader("If-None-Match", "W/\"etag\"");
761 req2.setHeader("If-Modified-Since", DateUtils.formatStandardDate(twentySecondsAgo));
762
763
764 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
765
766 execute(req1);
767 final ClassicHttpResponse result = execute(req2);
768
769 Assertions.assertNotEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
770 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
771 }
772
773 @Test
774 public void testConditionalRequestWhereAllValidatorsMatchMayBeServedFromCache() throws Exception {
775 final Instant now = Instant.now();
776 final Instant tenSecondsAgo = now.minusSeconds(10);
777
778 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
779 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
780 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
781 resp1.setHeader("Cache-Control", "max-age=3600");
782 resp1.setHeader("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
783 resp1.setHeader("ETag", "W/\"etag\"");
784
785 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
786 req2.setHeader("If-None-Match", "W/\"etag\"");
787 req2.setHeader("If-Modified-Since", DateUtils.formatStandardDate(tenSecondsAgo));
788
789
790 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
791
792 execute(req1);
793 execute(req2);
794
795 Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
796 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
797 }
798
799 @Test
800 public void testCacheWithoutSupportForRangeAndContentRangeHeadersDoesNotCacheA206Response() throws Exception {
801 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
802 req.setHeader("Range", "bytes=0-50");
803
804 final ClassicHttpResponse resp = new BasicClassicHttpResponse(206, "Partial Content");
805 resp.setHeader("Content-Range", "bytes 0-50/128");
806 resp.setHeader("ETag", "\"etag\"");
807 resp.setHeader("Cache-Control", "max-age=3600");
808
809 Mockito.when(mockExecChain.proceed(Mockito.any(),Mockito.any())).thenReturn(resp);
810
811 execute(req);
812
813 Mockito.verifyNoInteractions(mockCache);
814 }
815
816 @Test
817 public void test302ResponseWithoutExplicitCacheabilityIsNotReturnedFromCache() throws Exception {
818 originResponse = new BasicClassicHttpResponse(302, "Temporary Redirect");
819 originResponse.setHeader("Location", "http://foo.example.com/other");
820 originResponse.removeHeaders("Expires");
821 originResponse.removeHeaders("Cache-Control");
822
823 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
824
825 execute(request);
826 execute(request);
827
828 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
829 }
830
831 private void testDoesNotModifyHeaderFromOrigin(final String header, final String value) throws Exception {
832 originResponse = HttpTestUtils.make200Response();
833 originResponse.setHeader(header, value);
834
835 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
836
837 final ClassicHttpResponse result = execute(request);
838
839 Assertions.assertEquals(value, result.getFirstHeader(header).getValue());
840 }
841
842 @Test
843 public void testDoesNotModifyContentLocationHeaderFromOrigin() throws Exception {
844
845 final String url = "http://foo.example.com/other";
846 testDoesNotModifyHeaderFromOrigin("Content-Location", url);
847 }
848
849 @Test
850 public void testDoesNotModifyContentMD5HeaderFromOrigin() throws Exception {
851 testDoesNotModifyHeaderFromOrigin("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
852 }
853
854 @Test
855 public void testDoesNotModifyEtagHeaderFromOrigin() throws Exception {
856 testDoesNotModifyHeaderFromOrigin("Etag", "\"the-etag\"");
857 }
858
859 @Test
860 public void testDoesNotModifyLastModifiedHeaderFromOrigin() throws Exception {
861 final String lm = DateUtils.formatStandardDate(Instant.now());
862 testDoesNotModifyHeaderFromOrigin("Last-Modified", lm);
863 }
864
865 private void testDoesNotAddHeaderToOriginResponse(final String header) throws Exception {
866 originResponse.removeHeaders(header);
867
868 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
869
870 final ClassicHttpResponse result = execute(request);
871
872 Assertions.assertNull(result.getFirstHeader(header));
873 }
874
875 @Test
876 public void testDoesNotAddContentLocationToOriginResponse() throws Exception {
877 testDoesNotAddHeaderToOriginResponse("Content-Location");
878 }
879
880 @Test
881 public void testDoesNotAddContentMD5ToOriginResponse() throws Exception {
882 testDoesNotAddHeaderToOriginResponse("Content-MD5");
883 }
884
885 @Test
886 public void testDoesNotAddEtagToOriginResponse() throws Exception {
887 testDoesNotAddHeaderToOriginResponse("ETag");
888 }
889
890 @Test
891 public void testDoesNotAddLastModifiedToOriginResponse() throws Exception {
892 testDoesNotAddHeaderToOriginResponse("Last-Modified");
893 }
894
895 private void testDoesNotModifyHeaderFromOriginOnCacheHit(final String header, final String value) throws Exception {
896
897 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
898 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
899
900 originResponse = HttpTestUtils.make200Response();
901 originResponse.setHeader("Cache-Control", "max-age=3600");
902 originResponse.setHeader(header, value);
903
904 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
905
906 execute(req1);
907 final ClassicHttpResponse result = execute(req2);
908
909 Assertions.assertEquals(value, result.getFirstHeader(header).getValue());
910 }
911
912 @Test
913 public void testDoesNotModifyContentLocationFromOriginOnCacheHit() throws Exception {
914 final String url = "http://foo.example.com/other";
915 testDoesNotModifyHeaderFromOriginOnCacheHit("Content-Location", url);
916 }
917
918 @Test
919 public void testDoesNotModifyContentMD5FromOriginOnCacheHit() throws Exception {
920 testDoesNotModifyHeaderFromOriginOnCacheHit("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
921 }
922
923 @Test
924 public void testDoesNotModifyEtagFromOriginOnCacheHit() throws Exception {
925 testDoesNotModifyHeaderFromOriginOnCacheHit("Etag", "\"the-etag\"");
926 }
927
928 @Test
929 public void testDoesNotModifyLastModifiedFromOriginOnCacheHit() throws Exception {
930 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
931 testDoesNotModifyHeaderFromOriginOnCacheHit("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
932 }
933
934 private void testDoesNotAddHeaderOnCacheHit(final String header) throws Exception {
935
936 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
937 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
938
939 originResponse.addHeader("Cache-Control", "max-age=3600");
940 originResponse.removeHeaders(header);
941
942 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
943
944 execute(req1);
945 final ClassicHttpResponse result = execute(req2);
946
947 Assertions.assertNull(result.getFirstHeader(header));
948 }
949
950 @Test
951 public void testDoesNotAddContentLocationHeaderOnCacheHit() throws Exception {
952 testDoesNotAddHeaderOnCacheHit("Content-Location");
953 }
954
955 @Test
956 public void testDoesNotAddContentMD5HeaderOnCacheHit() throws Exception {
957 testDoesNotAddHeaderOnCacheHit("Content-MD5");
958 }
959
960 @Test
961 public void testDoesNotAddETagHeaderOnCacheHit() throws Exception {
962 testDoesNotAddHeaderOnCacheHit("ETag");
963 }
964
965 @Test
966 public void testDoesNotAddLastModifiedHeaderOnCacheHit() throws Exception {
967 testDoesNotAddHeaderOnCacheHit("Last-Modified");
968 }
969
970 private void testDoesNotModifyHeaderOnRequest(final String header, final String value) throws Exception {
971 final BasicClassicHttpRequest req = new BasicClassicHttpRequest("POST","/");
972 req.setEntity(HttpTestUtils.makeBody(128));
973 req.setHeader("Content-Length","128");
974 req.setHeader(header,value);
975
976 execute(req);
977
978 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
979 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
980
981 final ClassicHttpRequest captured = reqCapture.getValue();
982 Assertions.assertEquals(value, captured.getFirstHeader(header).getValue());
983 }
984
985 @Test
986 public void testDoesNotModifyContentLocationHeaderOnRequest() throws Exception {
987 final String url = "http://foo.example.com/other";
988 testDoesNotModifyHeaderOnRequest("Content-Location",url);
989 }
990
991 @Test
992 public void testDoesNotModifyContentMD5HeaderOnRequest() throws Exception {
993 testDoesNotModifyHeaderOnRequest("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
994 }
995
996 @Test
997 public void testDoesNotModifyETagHeaderOnRequest() throws Exception {
998 testDoesNotModifyHeaderOnRequest("ETag","\"etag\"");
999 }
1000
1001 @Test
1002 public void testDoesNotModifyLastModifiedHeaderOnRequest() throws Exception {
1003 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
1004 testDoesNotModifyHeaderOnRequest("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
1005 }
1006
1007 private void testDoesNotAddHeaderToRequestIfNotPresent(final String header) throws Exception {
1008 final BasicClassicHttpRequest req = new BasicClassicHttpRequest("POST","/");
1009 req.setEntity(HttpTestUtils.makeBody(128));
1010 req.setHeader("Content-Length","128");
1011 req.removeHeaders(header);
1012
1013 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1014
1015 execute(req);
1016
1017 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
1018 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
1019
1020 final ClassicHttpRequest captured = reqCapture.getValue();
1021 Assertions.assertNull(captured.getFirstHeader(header));
1022 }
1023
1024 @Test
1025 public void testDoesNotAddContentLocationToRequestIfNotPresent() throws Exception {
1026 testDoesNotAddHeaderToRequestIfNotPresent("Content-Location");
1027 }
1028
1029 @Test
1030 public void testDoesNotAddContentMD5ToRequestIfNotPresent() throws Exception {
1031 testDoesNotAddHeaderToRequestIfNotPresent("Content-MD5");
1032 }
1033
1034 @Test
1035 public void testDoesNotAddETagToRequestIfNotPresent() throws Exception {
1036 testDoesNotAddHeaderToRequestIfNotPresent("ETag");
1037 }
1038
1039 @Test
1040 public void testDoesNotAddLastModifiedToRequestIfNotPresent() throws Exception {
1041 testDoesNotAddHeaderToRequestIfNotPresent("Last-Modified");
1042 }
1043
1044 @Test
1045 public void testDoesNotModifyExpiresHeaderFromOrigin() throws Exception {
1046 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
1047 testDoesNotModifyHeaderFromOrigin("Expires", DateUtils.formatStandardDate(tenSecondsAgo));
1048 }
1049
1050 @Test
1051 public void testDoesNotModifyExpiresHeaderFromOriginOnCacheHit() throws Exception {
1052 final Instant inTenSeconds = Instant.now().plusSeconds(10);
1053 testDoesNotModifyHeaderFromOriginOnCacheHit("Expires", DateUtils.formatStandardDate(inTenSeconds));
1054 }
1055
1056 @Test
1057 public void testExpiresHeaderMatchesDateIfAddedToOriginResponse() throws Exception {
1058 originResponse.removeHeaders("Expires");
1059
1060 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1061
1062 final ClassicHttpResponse result = execute(request);
1063
1064 final Header expHdr = result.getFirstHeader("Expires");
1065 if (expHdr != null) {
1066 Assertions.assertEquals(result.getFirstHeader("Date").getValue(),
1067 expHdr.getValue());
1068 }
1069 }
1070
1071 private void testDoesNotModifyHeaderFromOriginResponseWithNoTransform(final String header, final String value) throws Exception {
1072 originResponse.addHeader("Cache-Control","no-transform");
1073 originResponse.setHeader(header, value);
1074
1075 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1076
1077 final ClassicHttpResponse result = execute(request);
1078
1079 Assertions.assertEquals(value, result.getFirstHeader(header).getValue());
1080 }
1081
1082 @Test
1083 public void testDoesNotModifyContentEncodingHeaderFromOriginResponseWithNoTransform() throws Exception {
1084 testDoesNotModifyHeaderFromOriginResponseWithNoTransform("Content-Encoding","gzip");
1085 }
1086
1087 @Test
1088 public void testDoesNotModifyContentRangeHeaderFromOriginResponseWithNoTransform() throws Exception {
1089 request.setHeader("If-Range","\"etag\"");
1090 request.setHeader("Range","bytes=0-49");
1091
1092 originResponse = new BasicClassicHttpResponse(206, "Partial Content");
1093 originResponse.setEntity(HttpTestUtils.makeBody(50));
1094 testDoesNotModifyHeaderFromOriginResponseWithNoTransform("Content-Range","bytes 0-49/128");
1095 }
1096
1097 @Test
1098 public void testDoesNotModifyContentTypeHeaderFromOriginResponseWithNoTransform() throws Exception {
1099 testDoesNotModifyHeaderFromOriginResponseWithNoTransform("Content-Type","text/html;charset=utf-8");
1100 }
1101
1102 private void testDoesNotModifyHeaderOnCachedResponseWithNoTransform(final String header, final String value) throws Exception {
1103 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1104 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1105
1106 originResponse.addHeader("Cache-Control","max-age=3600, no-transform");
1107 originResponse.setHeader(header, value);
1108
1109 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1110
1111 execute(req1);
1112 final ClassicHttpResponse result = execute(req2);
1113
1114 Assertions.assertEquals(value, result.getFirstHeader(header).getValue());
1115 }
1116
1117 @Test
1118 public void testDoesNotModifyContentEncodingHeaderOnCachedResponseWithNoTransform() throws Exception {
1119 testDoesNotModifyHeaderOnCachedResponseWithNoTransform("Content-Encoding","gzip");
1120 }
1121
1122 @Test
1123 public void testDoesNotModifyContentTypeHeaderOnCachedResponseWithNoTransform() throws Exception {
1124 testDoesNotModifyHeaderOnCachedResponseWithNoTransform("Content-Type","text/html;charset=utf-8");
1125 }
1126
1127 @Test
1128 public void testDoesNotAddContentEncodingHeaderToOriginResponseWithNoTransformIfNotPresent() throws Exception {
1129 originResponse.addHeader("Cache-Control","no-transform");
1130 testDoesNotAddHeaderToOriginResponse("Content-Encoding");
1131 }
1132
1133 @Test
1134 public void testDoesNotAddContentRangeHeaderToOriginResponseWithNoTransformIfNotPresent() throws Exception {
1135 originResponse.addHeader("Cache-Control","no-transform");
1136 testDoesNotAddHeaderToOriginResponse("Content-Range");
1137 }
1138
1139 @Test
1140 public void testDoesNotAddContentTypeHeaderToOriginResponseWithNoTransformIfNotPresent() throws Exception {
1141 originResponse.addHeader("Cache-Control","no-transform");
1142 testDoesNotAddHeaderToOriginResponse("Content-Type");
1143 }
1144
1145
1146 @Test
1147 public void testDoesNotAddContentEncodingHeaderToCachedResponseWithNoTransformIfNotPresent() throws Exception {
1148 originResponse.addHeader("Cache-Control","no-transform");
1149 testDoesNotAddHeaderOnCacheHit("Content-Encoding");
1150 }
1151
1152 @Test
1153 public void testDoesNotAddContentRangeHeaderToCachedResponseWithNoTransformIfNotPresent() throws Exception {
1154 originResponse.addHeader("Cache-Control","no-transform");
1155 testDoesNotAddHeaderOnCacheHit("Content-Range");
1156 }
1157
1158 @Test
1159 public void testDoesNotAddContentTypeHeaderToCachedResponseWithNoTransformIfNotPresent() throws Exception {
1160 originResponse.addHeader("Cache-Control","no-transform");
1161 testDoesNotAddHeaderOnCacheHit("Content-Type");
1162 }
1163
1164
1165 @Test
1166 public void testDoesNotAddContentEncodingToRequestIfNotPresent() throws Exception {
1167 testDoesNotAddHeaderToRequestIfNotPresent("Content-Encoding");
1168 }
1169
1170 @Test
1171 public void testDoesNotAddContentRangeToRequestIfNotPresent() throws Exception {
1172 testDoesNotAddHeaderToRequestIfNotPresent("Content-Range");
1173 }
1174
1175 @Test
1176 public void testDoesNotAddContentTypeToRequestIfNotPresent() throws Exception {
1177 testDoesNotAddHeaderToRequestIfNotPresent("Content-Type");
1178 }
1179
1180 @Test
1181 public void testDoesNotAddContentEncodingHeaderToRequestIfNotPresent() throws Exception {
1182 testDoesNotAddHeaderToRequestIfNotPresent("Content-Encoding");
1183 }
1184
1185 @Test
1186 public void testDoesNotAddContentRangeHeaderToRequestIfNotPresent() throws Exception {
1187 testDoesNotAddHeaderToRequestIfNotPresent("Content-Range");
1188 }
1189
1190 @Test
1191 public void testDoesNotAddContentTypeHeaderToRequestIfNotPresent() throws Exception {
1192 testDoesNotAddHeaderToRequestIfNotPresent("Content-Type");
1193 }
1194
1195 @Test
1196 public void testCachedEntityBodyIsUsedForResponseAfter304Validation() throws Exception {
1197 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1198 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1199 resp1.setHeader("Cache-Control","max-age=3600");
1200 resp1.setHeader("ETag","\"etag\"");
1201
1202 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1203 req2.setHeader("Cache-Control","max-age=0, max-stale=0");
1204 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1205
1206 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1207
1208 execute(req1);
1209
1210 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1211
1212 final ClassicHttpResponse result = execute(req2);
1213
1214 try (final InputStream i1 = resp1.getEntity().getContent();
1215 final InputStream i2 = result.getEntity().getContent()) {
1216 int b1, b2;
1217 while((b1 = i1.read()) != -1) {
1218 b2 = i2.read();
1219 Assertions.assertEquals(b1, b2);
1220 }
1221 b2 = i2.read();
1222 Assertions.assertEquals(-1, b2);
1223 }
1224 }
1225
1226 private void decorateWithEndToEndHeaders(final ClassicHttpResponse r) {
1227 r.setHeader("Allow","GET");
1228 r.setHeader("Content-Encoding","gzip");
1229 r.setHeader("Content-Language","en");
1230 r.setHeader("Content-Length", "128");
1231 r.setHeader("Content-Location","http://foo.example.com/other");
1232 r.setHeader("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
1233 r.setHeader("Content-Type", "text/html;charset=utf-8");
1234 r.setHeader("Expires", DateUtils.formatStandardDate(Instant.now().plusSeconds(10)));
1235 r.setHeader("Last-Modified", DateUtils.formatStandardDate(Instant.now().minusSeconds(10)));
1236 r.setHeader("Location", "http://foo.example.com/other2");
1237 r.setHeader("Retry-After","180");
1238 }
1239
1240 @Test
1241 public void testResponseIncludesCacheEntryEndToEndHeadersForResponseAfter304Validation() throws Exception {
1242 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1243 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1244 resp1.setHeader("Cache-Control","max-age=3600");
1245 resp1.setHeader("ETag","\"etag\"");
1246 decorateWithEndToEndHeaders(resp1);
1247
1248 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1249 req2.setHeader("Cache-Control", "max-age=0, max-stale=0");
1250 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1251 resp2.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
1252 resp2.setHeader("Server", "MockServer/1.0");
1253
1254 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1255
1256 execute(req1);
1257
1258 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(req2), Mockito.any())).thenReturn(resp2);
1259 final ClassicHttpResponse result = execute(req2);
1260
1261 final String[] endToEndHeaders = {
1262 "Cache-Control", "ETag", "Allow", "Content-Encoding",
1263 "Content-Language", "Content-Length", "Content-Location",
1264 "Content-MD5", "Content-Type", "Expires", "Last-Modified",
1265 "Location", "Retry-After"
1266 };
1267 for(final String h : endToEndHeaders) {
1268 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp1, h),
1269 HttpTestUtils.getCanonicalHeaderValue(result, h));
1270 }
1271 }
1272
1273 @Test
1274 public void testUpdatedEndToEndHeadersFrom304ArePassedOnResponseAndUpdatedInCacheEntry() throws Exception {
1275
1276 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1277 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1278 resp1.setHeader("Cache-Control","max-age=3600");
1279 resp1.setHeader("ETag","\"etag\"");
1280 decorateWithEndToEndHeaders(resp1);
1281
1282 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1283 req2.setHeader("Cache-Control", "max-age=0, max-stale=0");
1284 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1285 resp2.setHeader("Cache-Control", "max-age=1800");
1286 resp2.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
1287 resp2.setHeader("Server", "MockServer/1.0");
1288 resp2.setHeader("Allow", "GET,HEAD");
1289 resp2.setHeader("Content-Language", "en,en-us");
1290 resp2.setHeader("Content-Location", "http://foo.example.com/new");
1291 resp2.setHeader("Content-Type","text/html");
1292 resp2.setHeader("Expires", DateUtils.formatStandardDate(Instant.now().plusSeconds(5)));
1293 resp2.setHeader("Location", "http://foo.example.com/new2");
1294 resp2.setHeader("Retry-After","120");
1295
1296 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1297
1298 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1299
1300 execute(req1);
1301
1302 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1303 final ClassicHttpResponse result1 = execute(req2);
1304 final ClassicHttpResponse result2 = execute(req3);
1305
1306 final String[] endToEndHeaders = {
1307 "Date", "Cache-Control", "Allow", "Content-Language",
1308 "Content-Location", "Content-Type", "Expires", "Location",
1309 "Retry-After"
1310 };
1311 for(final String h : endToEndHeaders) {
1312 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
1313 HttpTestUtils.getCanonicalHeaderValue(result1, h));
1314 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
1315 HttpTestUtils.getCanonicalHeaderValue(result2, h));
1316 }
1317 }
1318
1319 @Test
1320 public void testMultiHeadersAreSuccessfullyReplacedOn304Validation() throws Exception {
1321 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1322 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1323 resp1.addHeader("Cache-Control","max-age=3600");
1324 resp1.addHeader("Cache-Control","public");
1325 resp1.setHeader("ETag","\"etag\"");
1326
1327 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1328 req2.setHeader("Cache-Control", "max-age=0, max-stale=0");
1329 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1330 resp2.setHeader("Cache-Control", "max-age=1800");
1331
1332 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1333
1334 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1335
1336 execute(req1);
1337
1338 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1339
1340 final ClassicHttpResponse result1 = execute(req2);
1341 final ClassicHttpResponse result2 = execute(req3);
1342
1343 final String h = "Cache-Control";
1344 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
1345 HttpTestUtils.getCanonicalHeaderValue(result1, h));
1346 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
1347 HttpTestUtils.getCanonicalHeaderValue(result2, h));
1348 }
1349
1350 @Test
1351 public void testCannotUseVariantCacheEntryIfNotAllSelectingRequestHeadersMatch() throws Exception {
1352
1353 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1354 req1.setHeader("Accept-Encoding","gzip");
1355
1356 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1357 resp1.setHeader("ETag","\"etag1\"");
1358 resp1.setHeader("Cache-Control","max-age=3600");
1359 resp1.setHeader("Vary","Accept-Encoding");
1360
1361 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1362
1363 execute(req1);
1364
1365 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1366 req2.removeHeaders("Accept-Encoding");
1367
1368 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1369 resp2.setHeader("ETag","\"etag1\"");
1370 resp2.setHeader("Cache-Control","max-age=3600");
1371
1372
1373 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1374
1375 execute(req2);
1376
1377 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1378 }
1379
1380 @Test
1381 public void testCannotServeFromCacheForVaryStar() throws Exception {
1382 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1383
1384 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1385 resp1.setHeader("ETag","\"etag1\"");
1386 resp1.setHeader("Cache-Control","max-age=3600");
1387 resp1.setHeader("Vary","*");
1388
1389 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1390
1391 execute(req1);
1392
1393 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1394
1395 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1396 resp2.setHeader("ETag","\"etag1\"");
1397 resp2.setHeader("Cache-Control","max-age=3600");
1398
1399
1400 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1401
1402 execute(req2);
1403
1404 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1405 }
1406
1407 @Test
1408 public void testNonMatchingVariantCannotBeServedFromCacheUnlessConditionallyValidated() throws Exception {
1409
1410 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1411 req1.setHeader("User-Agent","MyBrowser/1.0");
1412
1413 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1414 resp1.setHeader("ETag","\"etag1\"");
1415 resp1.setHeader("Cache-Control","max-age=3600");
1416 resp1.setHeader("Vary","User-Agent");
1417 resp1.setHeader("Content-Type","application/octet-stream");
1418
1419 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1420 req2.setHeader("User-Agent","MyBrowser/1.5");
1421
1422 final ClassicHttpResponse resp200 = HttpTestUtils.make200Response();
1423 resp200.setHeader("ETag","\"etag1\"");
1424 resp200.setHeader("Vary","User-Agent");
1425
1426 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1427
1428 execute(req1);
1429
1430 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(req2), Mockito.any())).thenReturn(resp200);
1431
1432 final ClassicHttpResponse result = execute(req2);
1433
1434 Assertions.assertEquals(HttpStatus.SC_OK, result.getCode());
1435
1436 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1437
1438 Assertions.assertTrue(HttpTestUtils.semanticallyTransparent(resp200, result));
1439 }
1440
1441 protected void testUnsafeOperationInvalidatesCacheForThatUri(
1442 final ClassicHttpRequest unsafeReq) throws Exception {
1443 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1444 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1445 resp1.setHeader("Cache-Control","public, max-age=3600");
1446
1447 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1448
1449 execute(req1);
1450
1451 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
1452
1453 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1454
1455 execute(unsafeReq);
1456
1457 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1458 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
1459 resp3.setHeader("Cache-Control","public, max-age=3600");
1460
1461
1462 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
1463
1464 execute(req3);
1465 }
1466
1467 protected ClassicHttpRequest makeRequestWithBody(final String method, final String requestUri) {
1468 final ClassicHttpRequest req = new BasicClassicHttpRequest(method, requestUri);
1469 final int nbytes = 128;
1470 req.setEntity(HttpTestUtils.makeBody(nbytes));
1471 req.setHeader("Content-Length", Long.toString(nbytes));
1472 return req;
1473 }
1474
1475 @Test
1476 public void testPutToUriInvalidatesCacheForThatUri() throws Exception {
1477 final ClassicHttpRequest req = makeRequestWithBody("PUT","/");
1478 testUnsafeOperationInvalidatesCacheForThatUri(req);
1479 }
1480
1481 @Test
1482 public void testDeleteToUriInvalidatesCacheForThatUri() throws Exception {
1483 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE","/");
1484 testUnsafeOperationInvalidatesCacheForThatUri(req);
1485 }
1486
1487 @Test
1488 public void testPostToUriInvalidatesCacheForThatUri() throws Exception {
1489 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
1490 testUnsafeOperationInvalidatesCacheForThatUri(req);
1491 }
1492
1493 protected void testUnsafeMethodInvalidatesCacheForHeaderUri(
1494 final ClassicHttpRequest unsafeReq) throws Exception {
1495 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/content");
1496 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1497 resp1.setHeader("Cache-Control","public, max-age=3600");
1498
1499 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1500
1501 execute(req1);
1502
1503 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
1504
1505 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1506
1507 execute(unsafeReq);
1508
1509 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/content");
1510 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
1511 resp3.setHeader("Cache-Control","public, max-age=3600");
1512
1513
1514 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
1515
1516 execute(req3);
1517 }
1518
1519 protected void testUnsafeMethodInvalidatesCacheForUriInContentLocationHeader(
1520 final ClassicHttpRequest unsafeReq) throws Exception {
1521 unsafeReq.setHeader("Content-Location","http://foo.example.com/content");
1522 testUnsafeMethodInvalidatesCacheForHeaderUri(unsafeReq);
1523 }
1524
1525 protected void testUnsafeMethodInvalidatesCacheForRelativeUriInContentLocationHeader(
1526 final ClassicHttpRequest unsafeReq) throws Exception {
1527 unsafeReq.setHeader("Content-Location","/content");
1528 testUnsafeMethodInvalidatesCacheForHeaderUri(unsafeReq);
1529 }
1530
1531 protected void testUnsafeMethodInvalidatesCacheForUriInLocationHeader(
1532 final ClassicHttpRequest unsafeReq) throws Exception {
1533 unsafeReq.setHeader("Location","http://foo.example.com/content");
1534 testUnsafeMethodInvalidatesCacheForHeaderUri(unsafeReq);
1535 }
1536
1537 @Test
1538 public void testPutInvalidatesCacheForThatUriInContentLocationHeader() throws Exception {
1539 final ClassicHttpRequest req2 = makeRequestWithBody("PUT","/");
1540 testUnsafeMethodInvalidatesCacheForUriInContentLocationHeader(req2);
1541 }
1542
1543 @Test
1544 public void testPutInvalidatesCacheForThatUriInLocationHeader() throws Exception {
1545 final ClassicHttpRequest req = makeRequestWithBody("PUT","/");
1546 testUnsafeMethodInvalidatesCacheForUriInLocationHeader(req);
1547 }
1548
1549 @Test
1550 public void testPutInvalidatesCacheForThatUriInRelativeContentLocationHeader() throws Exception {
1551 final ClassicHttpRequest req = makeRequestWithBody("PUT","/");
1552 testUnsafeMethodInvalidatesCacheForRelativeUriInContentLocationHeader(req);
1553 }
1554
1555 @Test
1556 public void testDeleteInvalidatesCacheForThatUriInContentLocationHeader() throws Exception {
1557 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
1558 testUnsafeMethodInvalidatesCacheForUriInContentLocationHeader(req);
1559 }
1560
1561 @Test
1562 public void testDeleteInvalidatesCacheForThatUriInRelativeContentLocationHeader() throws Exception {
1563 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
1564 testUnsafeMethodInvalidatesCacheForRelativeUriInContentLocationHeader(req);
1565 }
1566
1567 @Test
1568 public void testDeleteInvalidatesCacheForThatUriInLocationHeader() throws Exception {
1569 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
1570 testUnsafeMethodInvalidatesCacheForUriInLocationHeader(req);
1571 }
1572
1573 @Test
1574 public void testPostInvalidatesCacheForThatUriInContentLocationHeader() throws Exception {
1575 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
1576 testUnsafeMethodInvalidatesCacheForUriInContentLocationHeader(req);
1577 }
1578
1579 @Test
1580 public void testPostInvalidatesCacheForThatUriInLocationHeader() throws Exception {
1581 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
1582 testUnsafeMethodInvalidatesCacheForUriInLocationHeader(req);
1583 }
1584
1585 @Test
1586 public void testPostInvalidatesCacheForRelativeUriInContentLocationHeader() throws Exception {
1587 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
1588 testUnsafeMethodInvalidatesCacheForRelativeUriInContentLocationHeader(req);
1589 }
1590
1591 private void testRequestIsWrittenThroughToOrigin(final ClassicHttpRequest req) throws Exception {
1592 final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
1593 final ClassicHttpRequest wrapper = req;
1594 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(wrapper), Mockito.any())).thenReturn(resp);
1595
1596 execute(wrapper);
1597 }
1598
1599 @Test
1600 public void testOPTIONSRequestsAreWrittenThroughToOrigin() throws Exception {
1601 final ClassicHttpRequest req = new BasicClassicHttpRequest("OPTIONS","*");
1602 testRequestIsWrittenThroughToOrigin(req);
1603 }
1604
1605 @Test
1606 public void testPOSTRequestsAreWrittenThroughToOrigin() throws Exception {
1607 final ClassicHttpRequest req = new BasicClassicHttpRequest("POST","/");
1608 req.setEntity(HttpTestUtils.makeBody(128));
1609 req.setHeader("Content-Length","128");
1610 testRequestIsWrittenThroughToOrigin(req);
1611 }
1612
1613 @Test
1614 public void testPUTRequestsAreWrittenThroughToOrigin() throws Exception {
1615 final ClassicHttpRequest req = new BasicClassicHttpRequest("PUT","/");
1616 req.setEntity(HttpTestUtils.makeBody(128));
1617 req.setHeader("Content-Length","128");
1618 testRequestIsWrittenThroughToOrigin(req);
1619 }
1620
1621 @Test
1622 public void testDELETERequestsAreWrittenThroughToOrigin() throws Exception {
1623 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
1624 testRequestIsWrittenThroughToOrigin(req);
1625 }
1626
1627 @Test
1628 public void testTRACERequestsAreWrittenThroughToOrigin() throws Exception {
1629 final ClassicHttpRequest req = new BasicClassicHttpRequest("TRACE","/");
1630 testRequestIsWrittenThroughToOrigin(req);
1631 }
1632
1633 @Test
1634 public void testCONNECTRequestsAreWrittenThroughToOrigin() throws Exception {
1635 final ClassicHttpRequest req = new BasicClassicHttpRequest("CONNECT","/");
1636 testRequestIsWrittenThroughToOrigin(req);
1637 }
1638
1639 @Test
1640 public void testUnknownMethodRequestsAreWrittenThroughToOrigin() throws Exception {
1641 final ClassicHttpRequest req = new BasicClassicHttpRequest("UNKNOWN","/");
1642 testRequestIsWrittenThroughToOrigin(req);
1643 }
1644
1645 @Test
1646 public void testTransmitsAgeHeaderIfIncomingAgeHeaderTooBig() throws Exception {
1647 final String reallyOldAge = "1" + Long.MAX_VALUE;
1648 originResponse.setHeader("Age",reallyOldAge);
1649
1650 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1651
1652 final ClassicHttpResponse result = execute(request);
1653
1654 Assertions.assertEquals(reallyOldAge,
1655 result.getFirstHeader("Age").getValue());
1656 }
1657
1658 @Test
1659 public void testDoesNotModifyAllowHeaderWithUnknownMethods() throws Exception {
1660 final String allowHeaderValue = "GET, HEAD, FOOBAR";
1661 originResponse.setHeader("Allow",allowHeaderValue);
1662 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1663 final ClassicHttpResponse result = execute(request);
1664 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(originResponse,"Allow"),
1665 HttpTestUtils.getCanonicalHeaderValue(result, "Allow"));
1666 }
1667
1668 protected void testSharedCacheRevalidatesAuthorizedResponse(
1669 final ClassicHttpResponse authorizedResponse, final int minTimes, final int maxTimes) throws Exception {
1670 if (config.isSharedCache()) {
1671 final String authorization = StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Q=";
1672 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1673 req1.setHeader("Authorization",authorization);
1674
1675 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1676 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1677 resp2.setHeader("Cache-Control","max-age=3600");
1678
1679 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(authorizedResponse);
1680
1681 execute(req1);
1682
1683 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1684
1685 execute(req2);
1686
1687 Mockito.verify(mockExecChain, Mockito.atLeast(1 + minTimes)).proceed(Mockito.any(), Mockito.any());
1688 Mockito.verify(mockExecChain, Mockito.atMost(1 + maxTimes)).proceed(Mockito.any(), Mockito.any());
1689 }
1690 }
1691
1692 @Test
1693 public void testSharedCacheMustNotNormallyCacheAuthorizedResponses() throws Exception {
1694 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
1695 resp.setHeader("Cache-Control","max-age=3600");
1696 resp.setHeader("ETag","\"etag\"");
1697 testSharedCacheRevalidatesAuthorizedResponse(resp, 1, 1);
1698 }
1699
1700 @Test
1701 public void testSharedCacheMayCacheAuthorizedResponsesWithSMaxAgeHeader() throws Exception {
1702 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
1703 resp.setHeader("Cache-Control","s-maxage=3600");
1704 resp.setHeader("ETag","\"etag\"");
1705 testSharedCacheRevalidatesAuthorizedResponse(resp, 0, 1);
1706 }
1707
1708 @Test
1709 public void testSharedCacheMustRevalidateAuthorizedResponsesWhenSMaxAgeIsZero() throws Exception {
1710 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
1711 resp.setHeader("Cache-Control","s-maxage=0");
1712 resp.setHeader("ETag","\"etag\"");
1713 testSharedCacheRevalidatesAuthorizedResponse(resp, 1, 1);
1714 }
1715
1716 @Test
1717 public void testSharedCacheMayCacheAuthorizedResponsesWithMustRevalidate() throws Exception {
1718 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
1719 resp.setHeader("Cache-Control","must-revalidate");
1720 resp.setHeader("ETag","\"etag\"");
1721 testSharedCacheRevalidatesAuthorizedResponse(resp, 0, 1);
1722 }
1723
1724 @Test
1725 public void testSharedCacheMayCacheAuthorizedResponsesWithCacheControlPublic() throws Exception {
1726 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
1727 resp.setHeader("Cache-Control","public");
1728 testSharedCacheRevalidatesAuthorizedResponse(resp, 0, 1);
1729 }
1730
1731 protected void testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponse(
1732 final ClassicHttpResponse authorizedResponse) throws Exception {
1733 if (config.isSharedCache()) {
1734 final String authorization1 = StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Q=";
1735 final String authorization2 = StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Qy";
1736
1737 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1738 req1.setHeader("Authorization",authorization1);
1739
1740 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1741 req2.setHeader("Authorization",authorization2);
1742
1743 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1744
1745 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(authorizedResponse);
1746
1747 execute(req1);
1748
1749 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1750
1751 execute(req2);
1752
1753 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
1754 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
1755
1756 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
1757 Assertions.assertEquals(2, allRequests.size());
1758
1759 final ClassicHttpRequest captured = allRequests.get(1);
1760 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(req2, "Authorization"),
1761 HttpTestUtils.getCanonicalHeaderValue(captured, "Authorization"));
1762 }
1763 }
1764
1765 @Test
1766 public void testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponsesWithSMaxAge() throws Exception {
1767 final Instant now = Instant.now();
1768 final Instant tenSecondsAgo = now.minusSeconds(10);
1769 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1770 resp1.setHeader("Date",DateUtils.formatStandardDate(tenSecondsAgo));
1771 resp1.setHeader("ETag","\"etag\"");
1772 resp1.setHeader("Cache-Control","s-maxage=5");
1773
1774 testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponse(resp1);
1775 }
1776
1777 @Test
1778 public void testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponsesWithMustRevalidate() throws Exception {
1779 final Instant now = Instant.now();
1780 final Instant tenSecondsAgo = now.minusSeconds(10);
1781 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1782 resp1.setHeader("Date",DateUtils.formatStandardDate(tenSecondsAgo));
1783 resp1.setHeader("ETag","\"etag\"");
1784 resp1.setHeader("Cache-Control","maxage=5, must-revalidate");
1785
1786 testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponse(resp1);
1787 }
1788
1789 protected void testCacheIsNotUsedWhenRespondingToRequest(final ClassicHttpRequest req) throws Exception {
1790 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1791 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1792 resp1.setHeader("Etag","\"etag\"");
1793 resp1.setHeader("Cache-Control","max-age=3600");
1794
1795 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1796
1797 execute(req1);
1798
1799 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1800 resp2.setHeader("Etag","\"etag2\"");
1801 resp2.setHeader("Cache-Control","max-age=1200");
1802
1803 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1804
1805 final ClassicHttpResponse result = execute(req);
1806
1807 Assertions.assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
1808
1809 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
1810 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
1811
1812 final ClassicHttpRequest captured = reqCapture.getValue();
1813 Assertions.assertTrue(HttpTestUtils.equivalent(req, captured));
1814 }
1815
1816 @Test
1817 public void testCacheIsNotUsedWhenRespondingToRequestWithCacheControlNoCache() throws Exception {
1818 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
1819 req.setHeader("Cache-Control","no-cache");
1820 testCacheIsNotUsedWhenRespondingToRequest(req);
1821 }
1822
1823 protected void testStaleCacheResponseMustBeRevalidatedWithOrigin(
1824 final ClassicHttpResponse staleResponse) throws Exception {
1825 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1826
1827 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1828 req2.setHeader("Cache-Control","max-stale=3600");
1829 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1830 resp2.setHeader("ETag","\"etag2\"");
1831 resp2.setHeader("Cache-Control","max-age=5, must-revalidate");
1832
1833
1834 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(staleResponse);
1835
1836 execute(req1);
1837
1838 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1839
1840 execute(req2);
1841
1842 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
1843 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
1844
1845 final ClassicHttpRequest reval = reqCapture.getValue();
1846 boolean foundMaxAge0 = false;
1847 final Iterator<HeaderElement> it = MessageSupport.iterate(reval, HttpHeaders.CACHE_CONTROL);
1848 while (it.hasNext()) {
1849 final HeaderElement elt = it.next();
1850 if ("max-age".equalsIgnoreCase(elt.getName())
1851 && "0".equals(elt.getValue())) {
1852 foundMaxAge0 = true;
1853 }
1854 }
1855 Assertions.assertTrue(foundMaxAge0);
1856 }
1857
1858 @Test
1859 public void testStaleEntryWithMustRevalidateIsNotUsedWithoutRevalidatingWithOrigin() throws Exception {
1860 final ClassicHttpResponse response = HttpTestUtils.make200Response();
1861 final Instant now = Instant.now();
1862 final Instant tenSecondsAgo = now.minusSeconds(10);
1863 response.setHeader("Date",DateUtils.formatStandardDate(tenSecondsAgo));
1864 response.setHeader("ETag","\"etag1\"");
1865 response.setHeader("Cache-Control","max-age=5, must-revalidate");
1866
1867 testStaleCacheResponseMustBeRevalidatedWithOrigin(response);
1868 }
1869
1870 protected void testGenerates504IfCannotRevalidateStaleResponse(
1871 final ClassicHttpResponse staleResponse) throws Exception {
1872 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1873
1874 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1875
1876 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(staleResponse);
1877
1878 execute(req1);
1879
1880 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new SocketTimeoutException());
1881
1882 final ClassicHttpResponse result = execute(req2);
1883
1884 Assertions.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT,
1885 result.getCode());
1886 }
1887
1888 @Test
1889 public void testGenerates504IfCannotRevalidateAMustRevalidateEntry() throws Exception {
1890 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1891 final Instant now = Instant.now();
1892 final Instant tenSecondsAgo = now.minusSeconds(10);
1893 resp1.setHeader("ETag","\"etag\"");
1894 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1895 resp1.setHeader("Cache-Control","max-age=5,must-revalidate");
1896
1897 testGenerates504IfCannotRevalidateStaleResponse(resp1);
1898 }
1899
1900 @Test
1901 public void testStaleEntryWithProxyRevalidateOnSharedCacheIsNotUsedWithoutRevalidatingWithOrigin() throws Exception {
1902 if (config.isSharedCache()) {
1903 final ClassicHttpResponse response = HttpTestUtils.make200Response();
1904 final Instant now = Instant.now();
1905 final Instant tenSecondsAgo = now.minusSeconds(10);
1906 response.setHeader("Date",DateUtils.formatStandardDate(tenSecondsAgo));
1907 response.setHeader("ETag","\"etag1\"");
1908 response.setHeader("Cache-Control","max-age=5, proxy-revalidate");
1909
1910 testStaleCacheResponseMustBeRevalidatedWithOrigin(response);
1911 }
1912 }
1913
1914 @Test
1915 public void testGenerates504IfSharedCacheCannotRevalidateAProxyRevalidateEntry() throws Exception {
1916 if (config.isSharedCache()) {
1917 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1918 final Instant now = Instant.now();
1919 final Instant tenSecondsAgo = now.minusSeconds(10);
1920 resp1.setHeader("ETag","\"etag\"");
1921 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1922 resp1.setHeader("Cache-Control","max-age=5,proxy-revalidate");
1923
1924 testGenerates504IfCannotRevalidateStaleResponse(resp1);
1925 }
1926 }
1927
1928 @Test
1929 public void testCacheControlPrivateIsNotCacheableBySharedCache() throws Exception {
1930 if (config.isSharedCache()) {
1931 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1932 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1933 resp1.setHeader("Cache-Control", "private,max-age=3600");
1934
1935 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1936
1937 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1938 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1939
1940 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1941
1942 execute(req1);
1943 execute(req2);
1944 }
1945 }
1946
1947 @Test
1948 public void testCacheControlPrivateOnFieldIsNotReturnedBySharedCache() throws Exception {
1949 if (config.isSharedCache()) {
1950 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1951 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1952 resp1.setHeader("X-Personal", "stuff");
1953 resp1.setHeader("Cache-Control", "private=\"X-Personal\",s-maxage=3600");
1954
1955 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1956
1957 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1958 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1959
1960
1961 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1962
1963 execute(req1);
1964 final ClassicHttpResponse result = execute(req2);
1965 Assertions.assertNull(result.getFirstHeader("X-Personal"));
1966
1967 Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
1968 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
1969 }
1970 }
1971
1972 @Test
1973 public void testNoCacheCannotSatisfyASubsequentRequestWithoutRevalidation() throws Exception {
1974 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1975 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1976 resp1.setHeader("ETag","\"etag\"");
1977 resp1.setHeader("Cache-Control","no-cache");
1978
1979 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1980
1981 execute(req1);
1982
1983 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1984 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1985
1986
1987 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1988
1989 execute(req2);
1990
1991 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1992 }
1993
1994 @Test
1995 public void testNoCacheCannotSatisfyASubsequentRequestWithoutRevalidationEvenWithContraryIndications() throws Exception {
1996 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1997 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1998 resp1.setHeader("ETag","\"etag\"");
1999 resp1.setHeader("Cache-Control","no-cache,s-maxage=3600");
2000
2001 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2002
2003 execute(req1);
2004
2005 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2006 req2.setHeader("Cache-Control","max-stale=7200");
2007 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
2008
2009
2010 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
2011
2012 execute(req2);
2013 }
2014
2015 @Test
2016 public void testNoCacheOnFieldIsNotReturnedWithoutRevalidation() throws Exception {
2017 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2018 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2019 resp1.setHeader("ETag","\"etag\"");
2020 resp1.setHeader("X-Stuff","things");
2021 resp1.setHeader("Cache-Control","no-cache=\"X-Stuff\", max-age=3600");
2022
2023 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2024
2025 execute(req1);
2026
2027 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2028 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
2029 resp2.setHeader("ETag","\"etag\"");
2030 resp2.setHeader("X-Stuff","things");
2031 resp2.setHeader("Cache-Control","no-cache=\"X-Stuff\",max-age=3600");
2032
2033 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
2034
2035 final ClassicHttpResponse result = execute(req2);
2036
2037 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
2038 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(reqCapture.capture(), Mockito.any());
2039
2040 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
2041 if (allRequests.isEmpty()) {
2042 Assertions.assertNull(result.getFirstHeader("X-Stuff"));
2043 }
2044 }
2045
2046 @Test
2047 public void testNoStoreOnRequestIsNotStoredInCache() throws Exception {
2048 request.setHeader("Cache-Control","no-store");
2049 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2050
2051 execute(request);
2052
2053 Mockito.verifyNoInteractions(mockCache);
2054 }
2055
2056 @Test
2057 public void testNoStoreOnRequestIsNotStoredInCacheEvenIfResponseMarkedCacheable() throws Exception {
2058 request.setHeader("Cache-Control","no-store");
2059 originResponse.setHeader("Cache-Control","max-age=3600");
2060 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2061
2062 execute(request);
2063
2064 Mockito.verifyNoInteractions(mockCache);
2065 }
2066
2067 @Test
2068 public void testNoStoreOnResponseIsNotStoredInCache() throws Exception {
2069 originResponse.setHeader("Cache-Control","no-store");
2070 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2071
2072 execute(request);
2073
2074 Mockito.verifyNoInteractions(mockCache);
2075 }
2076
2077 @Test
2078 public void testNoStoreOnResponseIsNotStoredInCacheEvenWithContraryIndicators() throws Exception {
2079 originResponse.setHeader("Cache-Control","no-store,max-age=3600");
2080 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2081
2082 execute(request);
2083
2084 Mockito.verifyNoInteractions(mockCache);
2085 }
2086
2087 @Test
2088 public void testOrderOfMultipleContentEncodingHeaderValuesIsPreserved() throws Exception {
2089 originResponse.addHeader("Content-Encoding","gzip");
2090 originResponse.addHeader("Content-Encoding","deflate");
2091 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2092
2093 final ClassicHttpResponse result = execute(request);
2094 int total_encodings = 0;
2095 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.CONTENT_ENCODING);
2096 while (it.hasNext()) {
2097 final HeaderElement elt = it.next();
2098 switch(total_encodings) {
2099 case 0:
2100 Assertions.assertEquals("gzip", elt.getName());
2101 break;
2102 case 1:
2103 Assertions.assertEquals("deflate", elt.getName());
2104 break;
2105 default:
2106 Assertions.fail("too many encodings");
2107 }
2108 total_encodings++;
2109 }
2110 Assertions.assertEquals(2, total_encodings);
2111 }
2112
2113 @Test
2114 public void testOrderOfMultipleParametersInContentEncodingHeaderIsPreserved() throws Exception {
2115 originResponse.addHeader("Content-Encoding","gzip,deflate");
2116 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2117
2118 final ClassicHttpResponse result = execute(request);
2119 int total_encodings = 0;
2120 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.CONTENT_ENCODING);
2121 while (it.hasNext()) {
2122 final HeaderElement elt = it.next();
2123 switch(total_encodings) {
2124 case 0:
2125 Assertions.assertEquals("gzip", elt.getName());
2126 break;
2127 case 1:
2128 Assertions.assertEquals("deflate", elt.getName());
2129 break;
2130 default:
2131 Assertions.fail("too many encodings");
2132 }
2133 total_encodings++;
2134 }
2135 Assertions.assertEquals(2, total_encodings);
2136 }
2137
2138 @Test
2139 public void testCacheDoesNotAssumeContentLocationHeaderIndicatesAnotherCacheableResource() throws Exception {
2140 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/foo");
2141 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2142 resp1.setHeader("Cache-Control","public,max-age=3600");
2143 resp1.setHeader("Etag","\"etag\"");
2144 resp1.setHeader("Content-Location","http://foo.example.com/bar");
2145
2146 execute(req1);
2147
2148 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/bar");
2149 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
2150 resp2.setHeader("Cache-Control","public,max-age=3600");
2151 resp2.setHeader("Etag","\"etag\"");
2152
2153 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2154 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
2155
2156 execute(req2);
2157 }
2158
2159 @Test
2160 public void testCachedResponsesWithMissingDateHeadersShouldBeAssignedOne() throws Exception {
2161 originResponse.removeHeaders("Date");
2162 originResponse.setHeader("Cache-Control","public");
2163 originResponse.setHeader("ETag","\"etag\"");
2164
2165 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2166
2167 final ClassicHttpResponse result = execute(request);
2168 Assertions.assertNotNull(result.getFirstHeader("Date"));
2169 }
2170
2171 private void testInvalidExpiresHeaderIsTreatedAsStale(
2172 final String expiresHeader) throws Exception {
2173 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2174 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2175 resp1.setHeader("Cache-Control","public");
2176 resp1.setHeader("ETag","\"etag\"");
2177 resp1.setHeader("Expires", expiresHeader);
2178
2179 execute(req1);
2180
2181 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2182 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
2183
2184 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2185
2186 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
2187
2188 execute(req2);
2189 }
2190
2191 @Test
2192 public void testMalformedExpiresHeaderIsTreatedAsStale() throws Exception {
2193 testInvalidExpiresHeaderIsTreatedAsStale("garbage");
2194 }
2195
2196 @Test
2197 public void testExpiresZeroHeaderIsTreatedAsStale() throws Exception {
2198 testInvalidExpiresHeaderIsTreatedAsStale("0");
2199 }
2200
2201 @Test
2202 public void testExpiresHeaderEqualToDateHeaderIsTreatedAsStale() throws Exception {
2203 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2204 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2205 resp1.setHeader("Cache-Control","public");
2206 resp1.setHeader("ETag","\"etag\"");
2207 resp1.setHeader("Expires", resp1.getFirstHeader("Date").getValue());
2208
2209 execute(req1);
2210
2211 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2212 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
2213
2214 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2215
2216 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
2217
2218 execute(req2);
2219 }
2220
2221 @Test
2222 public void testDoesNotModifyServerResponseHeader() throws Exception {
2223 final String server = "MockServer/1.0";
2224 originResponse.setHeader("Server", server);
2225
2226 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2227
2228 final ClassicHttpResponse result = execute(request);
2229 Assertions.assertEquals(server, result.getFirstHeader("Server").getValue());
2230 }
2231
2232 }