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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 void testNotModifiedOfNonCachedEntityShouldRevalidateWithUnconditionalGET() throws Exception {
467
468
469 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
470 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
471 resp1.setHeader("ETag", "\"etag1\"");
472 resp1.setHeader("Cache-Control", "max-age=3600");
473
474
475 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
476 req2.setHeader("Cache-Control", "max-age=0,max-stale=0");
477
478
479 final ClassicHttpRequest unconditionalValidation = new BasicClassicHttpRequest("GET", "/");
480
481 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
482 resp1.setHeader("ETag", "\"etag2\"");
483 resp1.setHeader("Cache-Control", "max-age=3600");
484
485 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
486
487
488
489 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(unconditionalValidation), Mockito.any())).thenReturn(resp2);
490
491 execute(req1);
492 execute(req2);
493
494 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
495 }
496
497 @Test
498 void testCacheEntryIsUpdatedWithNewFieldValuesIn304Response() throws Exception {
499
500 final Instant now = Instant.now();
501 final Instant inFiveSeconds = now.plusSeconds(5);
502
503 final ClassicHttpRequest initialRequest = new BasicClassicHttpRequest("GET", "/");
504
505 final ClassicHttpResponse cachedResponse = HttpTestUtils.make200Response();
506 cachedResponse.setHeader("Cache-Control", "max-age=3600");
507 cachedResponse.setHeader("ETag", "\"etag\"");
508
509 final ClassicHttpRequest secondRequest = new BasicClassicHttpRequest("GET", "/");
510 secondRequest.setHeader("Cache-Control", "max-age=0,max-stale=0");
511
512 final ClassicHttpRequest conditionalValidationRequest = new BasicClassicHttpRequest("GET", "/");
513 conditionalValidationRequest.setHeader("If-None-Match", "\"etag\"");
514
515
516 final ClassicHttpResponse conditionalResponse = HttpTestUtils.make304Response();
517 conditionalResponse.setHeader("Date", DateUtils.formatStandardDate(inFiveSeconds));
518 conditionalResponse.setHeader("Server", "MockUtils/1.0");
519 conditionalResponse.setHeader("ETag", "\"etag\"");
520 conditionalResponse.setHeader("X-Extra", "junk");
521
522
523 final ClassicHttpResponse unconditionalResponse = HttpTestUtils.make200Response();
524 unconditionalResponse.setHeader("Date", DateUtils.formatStandardDate(inFiveSeconds));
525 unconditionalResponse.setHeader("ETag", "\"etag\"");
526
527 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(cachedResponse);
528 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(conditionalValidationRequest), Mockito.any())).thenReturn(conditionalResponse);
529
530 execute(initialRequest);
531 final ClassicHttpResponse result = execute(secondRequest);
532
533 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
534
535 Assertions.assertEquals(DateUtils.formatStandardDate(inFiveSeconds), result.getFirstHeader("Date").getValue());
536 Assertions.assertEquals("junk", result.getFirstHeader("X-Extra").getValue());
537 }
538
539 @Test
540 void testMustReturnACacheEntryIfItCanRevalidateIt() throws Exception {
541
542 final Instant now = Instant.now();
543 final Instant tenSecondsAgo = now.minusSeconds(10);
544 final Instant nineSecondsAgo = now.minusSeconds(9);
545 final Instant eightSecondsAgo = now.minusSeconds(8);
546
547 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo,
548 Method.GET, "/thing", null,
549 200, new Header[] {
550 new BasicHeader("Date", DateUtils.formatStandardDate(nineSecondsAgo)),
551 new BasicHeader("ETag", "\"etag\"")
552 }, HttpTestUtils.makeNullResource());
553
554 impl = new CachingExec(mockCache, null, config);
555
556 request = new BasicClassicHttpRequest("GET", "/thing");
557
558 final ClassicHttpRequest validate = new BasicClassicHttpRequest("GET", "/thing");
559 validate.setHeader("If-None-Match", "\"etag\"");
560
561 final ClassicHttpResponse notModified = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
562 notModified.setHeader("Date", DateUtils.formatStandardDate(now));
563 notModified.setHeader("ETag", "\"etag\"");
564
565 Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
566 new CacheMatch(new CacheHit("key", entry), null));
567 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(validate), Mockito.any())).thenReturn(notModified);
568 final HttpCacheEntry updated = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo,
569 Method.GET, "/thing", null,
570 200, new Header[] {
571 new BasicHeader("Date", DateUtils.formatStandardDate(now)),
572 new BasicHeader("ETag", "\"etag\"")
573 }, HttpTestUtils.makeNullResource());
574 Mockito.when(mockCache.update(
575 Mockito.any(),
576 Mockito.any(),
577 Mockito.any(),
578 Mockito.any(),
579 Mockito.any(),
580 Mockito.any()))
581 .thenReturn(new CacheHit("key", updated));
582
583 execute(request);
584
585 Mockito.verify(mockCache).update(
586 Mockito.any(),
587 Mockito.eq(host),
588 RequestEquivalent.eq(request),
589 ResponseEquivalent.eq(notModified),
590 Mockito.any(),
591 Mockito.any());
592 }
593
594 @Test
595 void testMustReturnAFreshEnoughCacheEntryIfItHasIt() throws Exception {
596
597 final Instant now = Instant.now();
598 final Instant tenSecondsAgo = now.minusSeconds(10);
599 final Instant nineSecondsAgo = now.plusSeconds(9);
600 final Instant eightSecondsAgo = now.plusSeconds(8);
601
602 final Header[] hdrs = new Header[] {
603 new BasicHeader("Date", DateUtils.formatStandardDate(nineSecondsAgo)),
604 new BasicHeader("Cache-Control", "max-age=3600"),
605 new BasicHeader("Content-Length", "128")
606 };
607
608 final byte[] bytes = new byte[128];
609 new Random().nextBytes(bytes);
610
611 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
612
613 impl = new CachingExec(mockCache, null, config);
614 request = new BasicClassicHttpRequest("GET", "/thing");
615
616 Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
617 new CacheMatch(new CacheHit("key", entry), null));
618
619 final ClassicHttpResponse result = execute(request);
620
621 Assertions.assertEquals(200, result.getCode());
622 }
623
624 @Test
625 void testAgeHeaderPopulatedFromCacheEntryCurrentAge() throws Exception {
626
627 final Instant now = Instant.now();
628 final Instant tenSecondsAgo = now.minusSeconds(10);
629 final Instant nineSecondsAgo = now.minusSeconds(9);
630 final Instant eightSecondsAgo = now.minusSeconds(8);
631
632 final Header[] hdrs = new Header[] {
633 new BasicHeader("Date", DateUtils.formatStandardDate(nineSecondsAgo)),
634 new BasicHeader("Cache-Control", "max-age=3600"),
635 new BasicHeader("Content-Length", "128")
636 };
637
638 final byte[] bytes = new byte[128];
639 new Random().nextBytes(bytes);
640
641 final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
642
643 impl = new CachingExec(mockCache, null, config);
644 request = new BasicClassicHttpRequest("GET", "/");
645
646 Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
647 new CacheMatch(new CacheHit("key", entry), null));
648
649 final ClassicHttpResponse result = execute(request);
650
651 Assertions.assertEquals(200, result.getCode());
652
653
654
655
656
657
658 assertThat(result, ContainsHeaderMatcher.contains("Age", "10"));
659 }
660
661 @Test
662 void testKeepsMostRecentDateHeaderForFreshResponse() throws Exception {
663
664 final Instant now = Instant.now();
665 final Instant inFiveSecond = now.plusSeconds(5);
666
667
668 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
669
670 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
671 resp1.setHeader("Date", DateUtils.formatStandardDate(inFiveSecond));
672 resp1.setHeader("ETag", "\"etag1\"");
673 resp1.setHeader("Cache-Control", "max-age=3600");
674 resp1.setHeader("Content-Length", "128");
675
676
677 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
678 req2.setHeader("Cache-Control", "no-cache");
679
680 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
681 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
682 resp2.setHeader("ETag", "\"etag2\"");
683 resp2.setHeader("Cache-Control", "max-age=3600");
684 resp2.setHeader("Content-Length", "128");
685
686 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
687
688 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
689
690 execute(req1);
691
692 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
693
694 execute(req2);
695 final ClassicHttpResponse result = execute(req3);
696 Assertions.assertEquals("\"etag1\"", result.getFirstHeader("ETag").getValue());
697 }
698
699 @Test
700 void testValidationMustUseETagIfProvidedByOriginServer() throws Exception {
701
702 final Instant now = Instant.now();
703 final Instant tenSecondsAgo = now.minusSeconds(10);
704
705 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
706 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
707 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
708 resp1.setHeader("Cache-Control", "max-age=3600");
709 resp1.setHeader("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
710 resp1.setHeader("ETag", "W/\"etag\"");
711
712 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
713 req2.setHeader("Cache-Control", "max-age=0,max-stale=0");
714
715 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
716
717 execute(req1);
718 execute(req2);
719
720 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
721 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
722
723 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
724 Assertions.assertEquals(2, allRequests.size());
725 final ClassicHttpRequest validation = allRequests.get(1);
726 boolean foundETag = false;
727 final Iterator<HeaderElement> it = MessageSupport.iterate(validation, HttpHeaders.IF_MATCH);
728 while (it.hasNext()) {
729 final HeaderElement elt = it.next();
730 if ("W/\"etag\"".equals(elt.getName())) {
731 foundETag = true;
732 }
733 }
734 final Iterator<HeaderElement> it2 = MessageSupport.iterate(validation, HttpHeaders.IF_NONE_MATCH);
735 while (it2.hasNext()) {
736 final HeaderElement elt = it2.next();
737 if ("W/\"etag\"".equals(elt.getName())) {
738 foundETag = true;
739 }
740 }
741 Assertions.assertTrue(foundETag);
742 }
743
744 @Test
745 void testConditionalRequestWhereNotAllValidatorsMatchCannotBeServedFromCache() throws Exception {
746 final Instant now = Instant.now();
747 final Instant tenSecondsAgo = now.minusSeconds(10);
748 final Instant twentySecondsAgo = now.plusSeconds(20);
749
750 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
751 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
752 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
753 resp1.setHeader("Cache-Control", "max-age=3600");
754 resp1.setHeader("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
755 resp1.setHeader("ETag", "W/\"etag\"");
756
757 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
758 req2.setHeader("If-None-Match", "W/\"etag\"");
759 req2.setHeader("If-Modified-Since", DateUtils.formatStandardDate(twentySecondsAgo));
760
761
762 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
763
764 execute(req1);
765 final ClassicHttpResponse result = execute(req2);
766
767 Assertions.assertNotEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
768 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
769 }
770
771 @Test
772 void testConditionalRequestWhereAllValidatorsMatchMayBeServedFromCache() throws Exception {
773 final Instant now = Instant.now();
774 final Instant tenSecondsAgo = now.minusSeconds(10);
775
776 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
777 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
778 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
779 resp1.setHeader("Cache-Control", "max-age=3600");
780 resp1.setHeader("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
781 resp1.setHeader("ETag", "W/\"etag\"");
782
783 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
784 req2.setHeader("If-None-Match", "W/\"etag\"");
785 req2.setHeader("If-Modified-Since", DateUtils.formatStandardDate(tenSecondsAgo));
786
787
788 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
789
790 execute(req1);
791 execute(req2);
792
793 Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
794 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
795 }
796
797 @Test
798 void testCacheWithoutSupportForRangeAndContentRangeHeadersDoesNotCacheA206Response() throws Exception {
799 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
800 req.setHeader("Range", "bytes=0-50");
801
802 final ClassicHttpResponse resp = new BasicClassicHttpResponse(206, "Partial Content");
803 resp.setHeader("Content-Range", "bytes 0-50/128");
804 resp.setHeader("ETag", "\"etag\"");
805 resp.setHeader("Cache-Control", "max-age=3600");
806
807 Mockito.when(mockExecChain.proceed(Mockito.any(),Mockito.any())).thenReturn(resp);
808
809 execute(req);
810
811 Mockito.verifyNoInteractions(mockCache);
812 }
813
814 @Test
815 void test302ResponseWithoutExplicitCacheabilityIsNotReturnedFromCache() throws Exception {
816 originResponse = new BasicClassicHttpResponse(302, "Temporary Redirect");
817 originResponse.setHeader("Location", "http://foo.example.com/other");
818 originResponse.removeHeaders("Expires");
819 originResponse.removeHeaders("Cache-Control");
820
821 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
822
823 execute(request);
824 execute(request);
825
826 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
827 }
828
829 private void testDoesNotModifyHeaderFromOrigin(final String header, final String value) throws Exception {
830 originResponse = HttpTestUtils.make200Response();
831 originResponse.setHeader(header, value);
832
833 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
834
835 final ClassicHttpResponse result = execute(request);
836
837 Assertions.assertEquals(value, result.getFirstHeader(header).getValue());
838 }
839
840 @Test
841 void testDoesNotModifyContentLocationHeaderFromOrigin() throws Exception {
842
843 final String url = "http://foo.example.com/other";
844 testDoesNotModifyHeaderFromOrigin("Content-Location", url);
845 }
846
847 @Test
848 void testDoesNotModifyContentMD5HeaderFromOrigin() throws Exception {
849 testDoesNotModifyHeaderFromOrigin("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
850 }
851
852 @Test
853 void testDoesNotModifyEtagHeaderFromOrigin() throws Exception {
854 testDoesNotModifyHeaderFromOrigin("Etag", "\"the-etag\"");
855 }
856
857 @Test
858 void testDoesNotModifyLastModifiedHeaderFromOrigin() throws Exception {
859 final String lm = DateUtils.formatStandardDate(Instant.now());
860 testDoesNotModifyHeaderFromOrigin("Last-Modified", lm);
861 }
862
863 private void testDoesNotAddHeaderToOriginResponse(final String header) throws Exception {
864 originResponse.removeHeaders(header);
865
866 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
867
868 final ClassicHttpResponse result = execute(request);
869
870 Assertions.assertNull(result.getFirstHeader(header));
871 }
872
873 @Test
874 void testDoesNotAddContentLocationToOriginResponse() throws Exception {
875 testDoesNotAddHeaderToOriginResponse("Content-Location");
876 }
877
878 @Test
879 void testDoesNotAddContentMD5ToOriginResponse() throws Exception {
880 testDoesNotAddHeaderToOriginResponse("Content-MD5");
881 }
882
883 @Test
884 void testDoesNotAddEtagToOriginResponse() throws Exception {
885 testDoesNotAddHeaderToOriginResponse("ETag");
886 }
887
888 @Test
889 void testDoesNotAddLastModifiedToOriginResponse() throws Exception {
890 testDoesNotAddHeaderToOriginResponse("Last-Modified");
891 }
892
893 private void testDoesNotModifyHeaderFromOriginOnCacheHit(final String header, final String value) throws Exception {
894
895 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
896 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
897
898 originResponse = HttpTestUtils.make200Response();
899 originResponse.setHeader("Cache-Control", "max-age=3600");
900 originResponse.setHeader(header, value);
901
902 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
903
904 execute(req1);
905 final ClassicHttpResponse result = execute(req2);
906
907 Assertions.assertEquals(value, result.getFirstHeader(header).getValue());
908 }
909
910 @Test
911 void testDoesNotModifyContentLocationFromOriginOnCacheHit() throws Exception {
912 final String url = "http://foo.example.com/other";
913 testDoesNotModifyHeaderFromOriginOnCacheHit("Content-Location", url);
914 }
915
916 @Test
917 void testDoesNotModifyContentMD5FromOriginOnCacheHit() throws Exception {
918 testDoesNotModifyHeaderFromOriginOnCacheHit("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
919 }
920
921 @Test
922 void testDoesNotModifyEtagFromOriginOnCacheHit() throws Exception {
923 testDoesNotModifyHeaderFromOriginOnCacheHit("Etag", "\"the-etag\"");
924 }
925
926 @Test
927 void testDoesNotModifyLastModifiedFromOriginOnCacheHit() throws Exception {
928 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
929 testDoesNotModifyHeaderFromOriginOnCacheHit("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
930 }
931
932 private void testDoesNotAddHeaderOnCacheHit(final String header) throws Exception {
933
934 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
935 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
936
937 originResponse.addHeader("Cache-Control", "max-age=3600");
938 originResponse.removeHeaders(header);
939
940 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
941
942 execute(req1);
943 final ClassicHttpResponse result = execute(req2);
944
945 Assertions.assertNull(result.getFirstHeader(header));
946 }
947
948 @Test
949 void testDoesNotAddContentLocationHeaderOnCacheHit() throws Exception {
950 testDoesNotAddHeaderOnCacheHit("Content-Location");
951 }
952
953 @Test
954 void testDoesNotAddContentMD5HeaderOnCacheHit() throws Exception {
955 testDoesNotAddHeaderOnCacheHit("Content-MD5");
956 }
957
958 @Test
959 void testDoesNotAddETagHeaderOnCacheHit() throws Exception {
960 testDoesNotAddHeaderOnCacheHit("ETag");
961 }
962
963 @Test
964 void testDoesNotAddLastModifiedHeaderOnCacheHit() throws Exception {
965 testDoesNotAddHeaderOnCacheHit("Last-Modified");
966 }
967
968 private void testDoesNotModifyHeaderOnRequest(final String header, final String value) throws Exception {
969 final BasicClassicHttpRequest req = new BasicClassicHttpRequest("POST","/");
970 req.setEntity(HttpTestUtils.makeBody(128));
971 req.setHeader("Content-Length","128");
972 req.setHeader(header,value);
973
974 execute(req);
975
976 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
977 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
978
979 final ClassicHttpRequest captured = reqCapture.getValue();
980 Assertions.assertEquals(value, captured.getFirstHeader(header).getValue());
981 }
982
983 @Test
984 void testDoesNotModifyContentLocationHeaderOnRequest() throws Exception {
985 final String url = "http://foo.example.com/other";
986 testDoesNotModifyHeaderOnRequest("Content-Location",url);
987 }
988
989 @Test
990 void testDoesNotModifyContentMD5HeaderOnRequest() throws Exception {
991 testDoesNotModifyHeaderOnRequest("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
992 }
993
994 @Test
995 void testDoesNotModifyETagHeaderOnRequest() throws Exception {
996 testDoesNotModifyHeaderOnRequest("ETag","\"etag\"");
997 }
998
999 @Test
1000 void testDoesNotModifyLastModifiedHeaderOnRequest() throws Exception {
1001 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
1002 testDoesNotModifyHeaderOnRequest("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
1003 }
1004
1005 private void testDoesNotAddHeaderToRequestIfNotPresent(final String header) throws Exception {
1006 final BasicClassicHttpRequest req = new BasicClassicHttpRequest("POST","/");
1007 req.setEntity(HttpTestUtils.makeBody(128));
1008 req.setHeader("Content-Length","128");
1009 req.removeHeaders(header);
1010
1011 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1012
1013 execute(req);
1014
1015 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
1016 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
1017
1018 final ClassicHttpRequest captured = reqCapture.getValue();
1019 Assertions.assertNull(captured.getFirstHeader(header));
1020 }
1021
1022 @Test
1023 void testDoesNotAddContentLocationToRequestIfNotPresent() throws Exception {
1024 testDoesNotAddHeaderToRequestIfNotPresent("Content-Location");
1025 }
1026
1027 @Test
1028 void testDoesNotAddContentMD5ToRequestIfNotPresent() throws Exception {
1029 testDoesNotAddHeaderToRequestIfNotPresent("Content-MD5");
1030 }
1031
1032 @Test
1033 void testDoesNotAddETagToRequestIfNotPresent() throws Exception {
1034 testDoesNotAddHeaderToRequestIfNotPresent("ETag");
1035 }
1036
1037 @Test
1038 void testDoesNotAddLastModifiedToRequestIfNotPresent() throws Exception {
1039 testDoesNotAddHeaderToRequestIfNotPresent("Last-Modified");
1040 }
1041
1042 @Test
1043 void testDoesNotModifyExpiresHeaderFromOrigin() throws Exception {
1044 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
1045 testDoesNotModifyHeaderFromOrigin("Expires", DateUtils.formatStandardDate(tenSecondsAgo));
1046 }
1047
1048 @Test
1049 void testDoesNotModifyExpiresHeaderFromOriginOnCacheHit() throws Exception {
1050 final Instant inTenSeconds = Instant.now().plusSeconds(10);
1051 testDoesNotModifyHeaderFromOriginOnCacheHit("Expires", DateUtils.formatStandardDate(inTenSeconds));
1052 }
1053
1054 @Test
1055 void testExpiresHeaderMatchesDateIfAddedToOriginResponse() throws Exception {
1056 originResponse.removeHeaders("Expires");
1057
1058 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1059
1060 final ClassicHttpResponse result = execute(request);
1061
1062 final Header expHdr = result.getFirstHeader("Expires");
1063 if (expHdr != null) {
1064 Assertions.assertEquals(result.getFirstHeader("Date").getValue(),
1065 expHdr.getValue());
1066 }
1067 }
1068
1069 private void testDoesNotModifyHeaderFromOriginResponseWithNoTransform(final String header, final String value) throws Exception {
1070 originResponse.addHeader("Cache-Control","no-transform");
1071 originResponse.setHeader(header, value);
1072
1073 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1074
1075 final ClassicHttpResponse result = execute(request);
1076
1077 Assertions.assertEquals(value, result.getFirstHeader(header).getValue());
1078 }
1079
1080 @Test
1081 void testDoesNotModifyContentEncodingHeaderFromOriginResponseWithNoTransform() throws Exception {
1082 testDoesNotModifyHeaderFromOriginResponseWithNoTransform("Content-Encoding","gzip");
1083 }
1084
1085 @Test
1086 void testDoesNotModifyContentRangeHeaderFromOriginResponseWithNoTransform() throws Exception {
1087 request.setHeader("If-Range","\"etag\"");
1088 request.setHeader("Range","bytes=0-49");
1089
1090 originResponse = new BasicClassicHttpResponse(206, "Partial Content");
1091 originResponse.setEntity(HttpTestUtils.makeBody(50));
1092 testDoesNotModifyHeaderFromOriginResponseWithNoTransform("Content-Range","bytes 0-49/128");
1093 }
1094
1095 @Test
1096 void testDoesNotModifyContentTypeHeaderFromOriginResponseWithNoTransform() throws Exception {
1097 testDoesNotModifyHeaderFromOriginResponseWithNoTransform("Content-Type","text/html;charset=utf-8");
1098 }
1099
1100 private void testDoesNotModifyHeaderOnCachedResponseWithNoTransform(final String header, final String value) throws Exception {
1101 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1102 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1103
1104 originResponse.addHeader("Cache-Control","max-age=3600, no-transform");
1105 originResponse.setHeader(header, value);
1106
1107 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1108
1109 execute(req1);
1110 final ClassicHttpResponse result = execute(req2);
1111
1112 Assertions.assertEquals(value, result.getFirstHeader(header).getValue());
1113 }
1114
1115 @Test
1116 void testDoesNotModifyContentEncodingHeaderOnCachedResponseWithNoTransform() throws Exception {
1117 testDoesNotModifyHeaderOnCachedResponseWithNoTransform("Content-Encoding","gzip");
1118 }
1119
1120 @Test
1121 void testDoesNotModifyContentTypeHeaderOnCachedResponseWithNoTransform() throws Exception {
1122 testDoesNotModifyHeaderOnCachedResponseWithNoTransform("Content-Type","text/html;charset=utf-8");
1123 }
1124
1125 @Test
1126 void testDoesNotAddContentEncodingHeaderToOriginResponseWithNoTransformIfNotPresent() throws Exception {
1127 originResponse.addHeader("Cache-Control","no-transform");
1128 testDoesNotAddHeaderToOriginResponse("Content-Encoding");
1129 }
1130
1131 @Test
1132 void testDoesNotAddContentRangeHeaderToOriginResponseWithNoTransformIfNotPresent() throws Exception {
1133 originResponse.addHeader("Cache-Control","no-transform");
1134 testDoesNotAddHeaderToOriginResponse("Content-Range");
1135 }
1136
1137 @Test
1138 void testDoesNotAddContentTypeHeaderToOriginResponseWithNoTransformIfNotPresent() throws Exception {
1139 originResponse.addHeader("Cache-Control","no-transform");
1140 testDoesNotAddHeaderToOriginResponse("Content-Type");
1141 }
1142
1143
1144 @Test
1145 void testDoesNotAddContentEncodingHeaderToCachedResponseWithNoTransformIfNotPresent() throws Exception {
1146 originResponse.addHeader("Cache-Control","no-transform");
1147 testDoesNotAddHeaderOnCacheHit("Content-Encoding");
1148 }
1149
1150 @Test
1151 void testDoesNotAddContentRangeHeaderToCachedResponseWithNoTransformIfNotPresent() throws Exception {
1152 originResponse.addHeader("Cache-Control","no-transform");
1153 testDoesNotAddHeaderOnCacheHit("Content-Range");
1154 }
1155
1156 @Test
1157 void testDoesNotAddContentTypeHeaderToCachedResponseWithNoTransformIfNotPresent() throws Exception {
1158 originResponse.addHeader("Cache-Control","no-transform");
1159 testDoesNotAddHeaderOnCacheHit("Content-Type");
1160 }
1161
1162
1163 @Test
1164 void testDoesNotAddContentEncodingToRequestIfNotPresent() throws Exception {
1165 testDoesNotAddHeaderToRequestIfNotPresent("Content-Encoding");
1166 }
1167
1168 @Test
1169 void testDoesNotAddContentRangeToRequestIfNotPresent() throws Exception {
1170 testDoesNotAddHeaderToRequestIfNotPresent("Content-Range");
1171 }
1172
1173 @Test
1174 void testDoesNotAddContentTypeToRequestIfNotPresent() throws Exception {
1175 testDoesNotAddHeaderToRequestIfNotPresent("Content-Type");
1176 }
1177
1178 @Test
1179 void testDoesNotAddContentEncodingHeaderToRequestIfNotPresent() throws Exception {
1180 testDoesNotAddHeaderToRequestIfNotPresent("Content-Encoding");
1181 }
1182
1183 @Test
1184 void testDoesNotAddContentRangeHeaderToRequestIfNotPresent() throws Exception {
1185 testDoesNotAddHeaderToRequestIfNotPresent("Content-Range");
1186 }
1187
1188 @Test
1189 void testDoesNotAddContentTypeHeaderToRequestIfNotPresent() throws Exception {
1190 testDoesNotAddHeaderToRequestIfNotPresent("Content-Type");
1191 }
1192
1193 @Test
1194 void testCachedEntityBodyIsUsedForResponseAfter304Validation() throws Exception {
1195 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1196 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1197 resp1.setHeader("Cache-Control","max-age=3600");
1198 resp1.setHeader("ETag","\"etag\"");
1199
1200 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1201 req2.setHeader("Cache-Control","max-age=0, max-stale=0");
1202 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1203
1204 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1205
1206 execute(req1);
1207
1208 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1209
1210 final ClassicHttpResponse result = execute(req2);
1211
1212 try (final InputStream i1 = resp1.getEntity().getContent();
1213 final InputStream i2 = result.getEntity().getContent()) {
1214 int b1, b2;
1215 while((b1 = i1.read()) != -1) {
1216 b2 = i2.read();
1217 Assertions.assertEquals(b1, b2);
1218 }
1219 b2 = i2.read();
1220 Assertions.assertEquals(-1, b2);
1221 }
1222 }
1223
1224 private void decorateWithEndToEndHeaders(final ClassicHttpResponse r) {
1225 r.setHeader("Allow","GET");
1226 r.setHeader("Content-Encoding","gzip");
1227 r.setHeader("Content-Language","en");
1228 r.setHeader("Content-Length", "128");
1229 r.setHeader("Content-Location","http://foo.example.com/other");
1230 r.setHeader("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
1231 r.setHeader("Content-Type", "text/html;charset=utf-8");
1232 r.setHeader("Expires", DateUtils.formatStandardDate(Instant.now().plusSeconds(10)));
1233 r.setHeader("Last-Modified", DateUtils.formatStandardDate(Instant.now().minusSeconds(10)));
1234 r.setHeader("Location", "http://foo.example.com/other2");
1235 r.setHeader("Retry-After","180");
1236 }
1237
1238 @Test
1239 void testResponseIncludesCacheEntryEndToEndHeadersForResponseAfter304Validation() throws Exception {
1240 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1241 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1242 resp1.setHeader("Cache-Control","max-age=3600");
1243 resp1.setHeader("ETag","\"etag\"");
1244 decorateWithEndToEndHeaders(resp1);
1245
1246 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1247 req2.setHeader("Cache-Control", "max-age=0, max-stale=0");
1248 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1249 resp2.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
1250 resp2.setHeader("Server", "MockServer/1.0");
1251
1252 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1253
1254 execute(req1);
1255
1256 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(req2), Mockito.any())).thenReturn(resp2);
1257 final ClassicHttpResponse result = execute(req2);
1258
1259 final String[] endToEndHeaders = {
1260 "Cache-Control", "ETag", "Allow", "Content-Encoding",
1261 "Content-Language", "Content-Length", "Content-Location",
1262 "Content-MD5", "Content-Type", "Expires", "Last-Modified",
1263 "Location", "Retry-After"
1264 };
1265 for(final String h : endToEndHeaders) {
1266 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp1, h),
1267 HttpTestUtils.getCanonicalHeaderValue(result, h));
1268 }
1269 }
1270
1271 @Test
1272 void testUpdatedEndToEndHeadersFrom304ArePassedOnResponseAndUpdatedInCacheEntry() throws Exception {
1273
1274 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1275 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1276 resp1.setHeader("Cache-Control","max-age=3600");
1277 resp1.setHeader("ETag","\"etag\"");
1278 decorateWithEndToEndHeaders(resp1);
1279
1280 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1281 req2.setHeader("Cache-Control", "max-age=0, max-stale=0");
1282 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1283 resp2.setHeader("Cache-Control", "max-age=1800");
1284 resp2.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
1285 resp2.setHeader("Server", "MockServer/1.0");
1286 resp2.setHeader("Allow", "GET,HEAD");
1287 resp2.setHeader("Content-Language", "en,en-us");
1288 resp2.setHeader("Content-Location", "http://foo.example.com/new");
1289 resp2.setHeader("Content-Type","text/html");
1290 resp2.setHeader("Expires", DateUtils.formatStandardDate(Instant.now().plusSeconds(5)));
1291 resp2.setHeader("Location", "http://foo.example.com/new2");
1292 resp2.setHeader("Retry-After","120");
1293
1294 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1295
1296 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1297
1298 execute(req1);
1299
1300 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1301 final ClassicHttpResponse result1 = execute(req2);
1302 final ClassicHttpResponse result2 = execute(req3);
1303
1304 final String[] endToEndHeaders = {
1305 "Date", "Cache-Control", "Allow", "Content-Language",
1306 "Content-Location", "Content-Type", "Expires", "Location",
1307 "Retry-After"
1308 };
1309 for(final String h : endToEndHeaders) {
1310 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
1311 HttpTestUtils.getCanonicalHeaderValue(result1, h));
1312 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
1313 HttpTestUtils.getCanonicalHeaderValue(result2, h));
1314 }
1315 }
1316
1317 @Test
1318 void testMultiHeadersAreSuccessfullyReplacedOn304Validation() throws Exception {
1319 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1320 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1321 resp1.addHeader("Cache-Control","max-age=3600");
1322 resp1.addHeader("Cache-Control","public");
1323 resp1.setHeader("ETag","\"etag\"");
1324
1325 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1326 req2.setHeader("Cache-Control", "max-age=0, max-stale=0");
1327 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
1328 resp2.setHeader("Cache-Control", "max-age=1800");
1329
1330 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1331
1332 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1333
1334 execute(req1);
1335
1336 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1337
1338 final ClassicHttpResponse result1 = execute(req2);
1339 final ClassicHttpResponse result2 = execute(req3);
1340
1341 final String h = "Cache-Control";
1342 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
1343 HttpTestUtils.getCanonicalHeaderValue(result1, h));
1344 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
1345 HttpTestUtils.getCanonicalHeaderValue(result2, h));
1346 }
1347
1348 @Test
1349 void testCannotUseVariantCacheEntryIfNotAllSelectingRequestHeadersMatch() throws Exception {
1350
1351 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1352 req1.setHeader("Accept-Encoding","gzip");
1353
1354 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1355 resp1.setHeader("ETag","\"etag1\"");
1356 resp1.setHeader("Cache-Control","max-age=3600");
1357 resp1.setHeader("Vary","Accept-Encoding");
1358
1359 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1360
1361 execute(req1);
1362
1363 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1364 req2.removeHeaders("Accept-Encoding");
1365
1366 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1367 resp2.setHeader("ETag","\"etag1\"");
1368 resp2.setHeader("Cache-Control","max-age=3600");
1369
1370
1371 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1372
1373 execute(req2);
1374
1375 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1376 }
1377
1378 @Test
1379 void testCannotServeFromCacheForVaryStar() throws Exception {
1380 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1381
1382 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1383 resp1.setHeader("ETag","\"etag1\"");
1384 resp1.setHeader("Cache-Control","max-age=3600");
1385 resp1.setHeader("Vary","*");
1386
1387 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1388
1389 execute(req1);
1390
1391 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1392
1393 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1394 resp2.setHeader("ETag","\"etag1\"");
1395 resp2.setHeader("Cache-Control","max-age=3600");
1396
1397
1398 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1399
1400 execute(req2);
1401
1402 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1403 }
1404
1405 @Test
1406 void testNonMatchingVariantCannotBeServedFromCacheUnlessConditionallyValidated() throws Exception {
1407
1408 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1409 req1.setHeader("User-Agent","MyBrowser/1.0");
1410
1411 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1412 resp1.setHeader("ETag","\"etag1\"");
1413 resp1.setHeader("Cache-Control","max-age=3600");
1414 resp1.setHeader("Vary","User-Agent");
1415 resp1.setHeader("Content-Type","application/octet-stream");
1416
1417 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1418 req2.setHeader("User-Agent","MyBrowser/1.5");
1419
1420 final ClassicHttpResponse resp200 = HttpTestUtils.make200Response();
1421 resp200.setHeader("ETag","\"etag1\"");
1422 resp200.setHeader("Vary","User-Agent");
1423
1424 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1425
1426 execute(req1);
1427
1428 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(req2), Mockito.any())).thenReturn(resp200);
1429
1430 final ClassicHttpResponse result = execute(req2);
1431
1432 Assertions.assertEquals(HttpStatus.SC_OK, result.getCode());
1433
1434 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1435
1436 Assertions.assertTrue(HttpTestUtils.semanticallyTransparent(resp200, result));
1437 }
1438
1439 protected void testUnsafeOperationInvalidatesCacheForThatUri(
1440 final ClassicHttpRequest unsafeReq) throws Exception {
1441 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1442 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1443 resp1.setHeader("Cache-Control","public, max-age=3600");
1444
1445 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1446
1447 execute(req1);
1448
1449 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
1450
1451 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1452
1453 execute(unsafeReq);
1454
1455 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
1456 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
1457 resp3.setHeader("Cache-Control","public, max-age=3600");
1458
1459
1460 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
1461
1462 execute(req3);
1463 }
1464
1465 protected ClassicHttpRequest makeRequestWithBody(final String method, final String requestUri) {
1466 final ClassicHttpRequest req = new BasicClassicHttpRequest(method, requestUri);
1467 final int nbytes = 128;
1468 req.setEntity(HttpTestUtils.makeBody(nbytes));
1469 req.setHeader("Content-Length", Long.toString(nbytes));
1470 return req;
1471 }
1472
1473 @Test
1474 void testPutToUriInvalidatesCacheForThatUri() throws Exception {
1475 final ClassicHttpRequest req = makeRequestWithBody("PUT","/");
1476 testUnsafeOperationInvalidatesCacheForThatUri(req);
1477 }
1478
1479 @Test
1480 void testDeleteToUriInvalidatesCacheForThatUri() throws Exception {
1481 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE","/");
1482 testUnsafeOperationInvalidatesCacheForThatUri(req);
1483 }
1484
1485 @Test
1486 void testPostToUriInvalidatesCacheForThatUri() throws Exception {
1487 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
1488 testUnsafeOperationInvalidatesCacheForThatUri(req);
1489 }
1490
1491 protected void testUnsafeMethodInvalidatesCacheForHeaderUri(
1492 final ClassicHttpRequest unsafeReq) throws Exception {
1493 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/content");
1494 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1495 resp1.setHeader("Cache-Control","public, max-age=3600");
1496
1497 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1498
1499 execute(req1);
1500
1501 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
1502
1503 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1504
1505 execute(unsafeReq);
1506
1507 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/content");
1508 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
1509 resp3.setHeader("Cache-Control","public, max-age=3600");
1510
1511
1512 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
1513
1514 execute(req3);
1515 }
1516
1517 protected void testUnsafeMethodInvalidatesCacheForUriInContentLocationHeader(
1518 final ClassicHttpRequest unsafeReq) throws Exception {
1519 unsafeReq.setHeader("Content-Location","http://foo.example.com/content");
1520 testUnsafeMethodInvalidatesCacheForHeaderUri(unsafeReq);
1521 }
1522
1523 protected void testUnsafeMethodInvalidatesCacheForRelativeUriInContentLocationHeader(
1524 final ClassicHttpRequest unsafeReq) throws Exception {
1525 unsafeReq.setHeader("Content-Location","/content");
1526 testUnsafeMethodInvalidatesCacheForHeaderUri(unsafeReq);
1527 }
1528
1529 protected void testUnsafeMethodInvalidatesCacheForUriInLocationHeader(
1530 final ClassicHttpRequest unsafeReq) throws Exception {
1531 unsafeReq.setHeader("Location","http://foo.example.com/content");
1532 testUnsafeMethodInvalidatesCacheForHeaderUri(unsafeReq);
1533 }
1534
1535 @Test
1536 void testPutInvalidatesCacheForThatUriInContentLocationHeader() throws Exception {
1537 final ClassicHttpRequest req2 = makeRequestWithBody("PUT","/");
1538 testUnsafeMethodInvalidatesCacheForUriInContentLocationHeader(req2);
1539 }
1540
1541 @Test
1542 void testPutInvalidatesCacheForThatUriInLocationHeader() throws Exception {
1543 final ClassicHttpRequest req = makeRequestWithBody("PUT","/");
1544 testUnsafeMethodInvalidatesCacheForUriInLocationHeader(req);
1545 }
1546
1547 @Test
1548 void testPutInvalidatesCacheForThatUriInRelativeContentLocationHeader() throws Exception {
1549 final ClassicHttpRequest req = makeRequestWithBody("PUT","/");
1550 testUnsafeMethodInvalidatesCacheForRelativeUriInContentLocationHeader(req);
1551 }
1552
1553 @Test
1554 void testDeleteInvalidatesCacheForThatUriInContentLocationHeader() throws Exception {
1555 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
1556 testUnsafeMethodInvalidatesCacheForUriInContentLocationHeader(req);
1557 }
1558
1559 @Test
1560 void testDeleteInvalidatesCacheForThatUriInRelativeContentLocationHeader() throws Exception {
1561 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
1562 testUnsafeMethodInvalidatesCacheForRelativeUriInContentLocationHeader(req);
1563 }
1564
1565 @Test
1566 void testDeleteInvalidatesCacheForThatUriInLocationHeader() throws Exception {
1567 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
1568 testUnsafeMethodInvalidatesCacheForUriInLocationHeader(req);
1569 }
1570
1571 @Test
1572 void testPostInvalidatesCacheForThatUriInContentLocationHeader() throws Exception {
1573 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
1574 testUnsafeMethodInvalidatesCacheForUriInContentLocationHeader(req);
1575 }
1576
1577 @Test
1578 void testPostInvalidatesCacheForThatUriInLocationHeader() throws Exception {
1579 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
1580 testUnsafeMethodInvalidatesCacheForUriInLocationHeader(req);
1581 }
1582
1583 @Test
1584 void testPostInvalidatesCacheForRelativeUriInContentLocationHeader() throws Exception {
1585 final ClassicHttpRequest req = makeRequestWithBody("POST","/");
1586 testUnsafeMethodInvalidatesCacheForRelativeUriInContentLocationHeader(req);
1587 }
1588
1589 private void testRequestIsWrittenThroughToOrigin(final ClassicHttpRequest req) throws Exception {
1590 final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_NO_CONTENT, "No Content");
1591 final ClassicHttpRequest wrapper = req;
1592 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(wrapper), Mockito.any())).thenReturn(resp);
1593
1594 execute(wrapper);
1595 }
1596
1597 @Test
1598 void testOPTIONSRequestsAreWrittenThroughToOrigin() throws Exception {
1599 final ClassicHttpRequest req = new BasicClassicHttpRequest("OPTIONS","*");
1600 testRequestIsWrittenThroughToOrigin(req);
1601 }
1602
1603 @Test
1604 void testPOSTRequestsAreWrittenThroughToOrigin() throws Exception {
1605 final ClassicHttpRequest req = new BasicClassicHttpRequest("POST","/");
1606 req.setEntity(HttpTestUtils.makeBody(128));
1607 req.setHeader("Content-Length","128");
1608 testRequestIsWrittenThroughToOrigin(req);
1609 }
1610
1611 @Test
1612 void testPUTRequestsAreWrittenThroughToOrigin() throws Exception {
1613 final ClassicHttpRequest req = new BasicClassicHttpRequest("PUT","/");
1614 req.setEntity(HttpTestUtils.makeBody(128));
1615 req.setHeader("Content-Length","128");
1616 testRequestIsWrittenThroughToOrigin(req);
1617 }
1618
1619 @Test
1620 void testDELETERequestsAreWrittenThroughToOrigin() throws Exception {
1621 final ClassicHttpRequest req = new BasicClassicHttpRequest("DELETE", "/");
1622 testRequestIsWrittenThroughToOrigin(req);
1623 }
1624
1625 @Test
1626 void testTRACERequestsAreWrittenThroughToOrigin() throws Exception {
1627 final ClassicHttpRequest req = new BasicClassicHttpRequest("TRACE","/");
1628 testRequestIsWrittenThroughToOrigin(req);
1629 }
1630
1631 @Test
1632 void testCONNECTRequestsAreWrittenThroughToOrigin() throws Exception {
1633 final ClassicHttpRequest req = new BasicClassicHttpRequest("CONNECT","/");
1634 testRequestIsWrittenThroughToOrigin(req);
1635 }
1636
1637 @Test
1638 void testUnknownMethodRequestsAreWrittenThroughToOrigin() throws Exception {
1639 final ClassicHttpRequest req = new BasicClassicHttpRequest("UNKNOWN","/");
1640 testRequestIsWrittenThroughToOrigin(req);
1641 }
1642
1643 @Test
1644 void testTransmitsAgeHeaderIfIncomingAgeHeaderTooBig() throws Exception {
1645 final String reallyOldAge = "1" + Long.MAX_VALUE;
1646 originResponse.setHeader("Age",reallyOldAge);
1647
1648 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1649
1650 final ClassicHttpResponse result = execute(request);
1651
1652 Assertions.assertEquals(reallyOldAge,
1653 result.getFirstHeader("Age").getValue());
1654 }
1655
1656 @Test
1657 void testDoesNotModifyAllowHeaderWithUnknownMethods() throws Exception {
1658 final String allowHeaderValue = "GET, HEAD, FOOBAR";
1659 originResponse.setHeader("Allow",allowHeaderValue);
1660 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
1661 final ClassicHttpResponse result = execute(request);
1662 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(originResponse,"Allow"),
1663 HttpTestUtils.getCanonicalHeaderValue(result, "Allow"));
1664 }
1665
1666 protected void testSharedCacheRevalidatesAuthorizedResponse(
1667 final ClassicHttpResponse authorizedResponse, final int minTimes, final int maxTimes) throws Exception {
1668 if (config.isSharedCache()) {
1669 final String authorization = StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Q=";
1670 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1671 req1.setHeader("Authorization",authorization);
1672
1673 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1674 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1675 resp2.setHeader("Cache-Control","max-age=3600");
1676
1677 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(authorizedResponse);
1678
1679 execute(req1);
1680
1681 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1682
1683 execute(req2);
1684
1685 Mockito.verify(mockExecChain, Mockito.atLeast(1 + minTimes)).proceed(Mockito.any(), Mockito.any());
1686 Mockito.verify(mockExecChain, Mockito.atMost(1 + maxTimes)).proceed(Mockito.any(), Mockito.any());
1687 }
1688 }
1689
1690 @Test
1691 void testSharedCacheMustNotNormallyCacheAuthorizedResponses() throws Exception {
1692 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
1693 resp.setHeader("Cache-Control","max-age=3600");
1694 resp.setHeader("ETag","\"etag\"");
1695 testSharedCacheRevalidatesAuthorizedResponse(resp, 1, 1);
1696 }
1697
1698 @Test
1699 void testSharedCacheMayCacheAuthorizedResponsesWithSMaxAgeHeader() throws Exception {
1700 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
1701 resp.setHeader("Cache-Control","s-maxage=3600");
1702 resp.setHeader("ETag","\"etag\"");
1703 testSharedCacheRevalidatesAuthorizedResponse(resp, 0, 1);
1704 }
1705
1706 @Test
1707 void testSharedCacheMustRevalidateAuthorizedResponsesWhenSMaxAgeIsZero() throws Exception {
1708 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
1709 resp.setHeader("Cache-Control","s-maxage=0");
1710 resp.setHeader("ETag","\"etag\"");
1711 testSharedCacheRevalidatesAuthorizedResponse(resp, 1, 1);
1712 }
1713
1714 @Test
1715 void testSharedCacheMayCacheAuthorizedResponsesWithMustRevalidate() throws Exception {
1716 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
1717 resp.setHeader("Cache-Control","must-revalidate");
1718 resp.setHeader("ETag","\"etag\"");
1719 testSharedCacheRevalidatesAuthorizedResponse(resp, 0, 1);
1720 }
1721
1722 @Test
1723 void testSharedCacheMayCacheAuthorizedResponsesWithCacheControlPublic() throws Exception {
1724 final ClassicHttpResponse resp = HttpTestUtils.make200Response();
1725 resp.setHeader("Cache-Control","public");
1726 testSharedCacheRevalidatesAuthorizedResponse(resp, 0, 1);
1727 }
1728
1729 protected void testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponse(
1730 final ClassicHttpResponse authorizedResponse) throws Exception {
1731 if (config.isSharedCache()) {
1732 final String authorization1 = StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Q=";
1733 final String authorization2 = StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Qy";
1734
1735 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1736 req1.setHeader("Authorization",authorization1);
1737
1738 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1739 req2.setHeader("Authorization",authorization2);
1740
1741 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1742
1743 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(authorizedResponse);
1744
1745 execute(req1);
1746
1747 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1748
1749 execute(req2);
1750
1751 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
1752 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
1753
1754 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
1755 Assertions.assertEquals(2, allRequests.size());
1756
1757 final ClassicHttpRequest captured = allRequests.get(1);
1758 Assertions.assertEquals(HttpTestUtils.getCanonicalHeaderValue(req2, "Authorization"),
1759 HttpTestUtils.getCanonicalHeaderValue(captured, "Authorization"));
1760 }
1761 }
1762
1763 @Test
1764 void testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponsesWithSMaxAge() throws Exception {
1765 final Instant now = Instant.now();
1766 final Instant tenSecondsAgo = now.minusSeconds(10);
1767 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1768 resp1.setHeader("Date",DateUtils.formatStandardDate(tenSecondsAgo));
1769 resp1.setHeader("ETag","\"etag\"");
1770 resp1.setHeader("Cache-Control","s-maxage=5");
1771
1772 testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponse(resp1);
1773 }
1774
1775 @Test
1776 void testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponsesWithMustRevalidate() throws Exception {
1777 final Instant now = Instant.now();
1778 final Instant tenSecondsAgo = now.minusSeconds(10);
1779 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1780 resp1.setHeader("Date",DateUtils.formatStandardDate(tenSecondsAgo));
1781 resp1.setHeader("ETag","\"etag\"");
1782 resp1.setHeader("Cache-Control","maxage=5, must-revalidate");
1783
1784 testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponse(resp1);
1785 }
1786
1787 protected void testCacheIsNotUsedWhenRespondingToRequest(final ClassicHttpRequest req) throws Exception {
1788 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1789 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1790 resp1.setHeader("Etag","\"etag\"");
1791 resp1.setHeader("Cache-Control","max-age=3600");
1792
1793 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1794
1795 execute(req1);
1796
1797 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1798 resp2.setHeader("Etag","\"etag2\"");
1799 resp2.setHeader("Cache-Control","max-age=1200");
1800
1801 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1802
1803 final ClassicHttpResponse result = execute(req);
1804
1805 Assertions.assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
1806
1807 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
1808 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
1809
1810 final ClassicHttpRequest captured = reqCapture.getValue();
1811 Assertions.assertTrue(HttpTestUtils.equivalent(req, captured));
1812 }
1813
1814 @Test
1815 void testCacheIsNotUsedWhenRespondingToRequestWithCacheControlNoCache() throws Exception {
1816 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
1817 req.setHeader("Cache-Control","no-cache");
1818 testCacheIsNotUsedWhenRespondingToRequest(req);
1819 }
1820
1821 protected void testStaleCacheResponseMustBeRevalidatedWithOrigin(
1822 final ClassicHttpResponse staleResponse) throws Exception {
1823 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1824
1825 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1826 req2.setHeader("Cache-Control","max-stale=3600");
1827 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1828 resp2.setHeader("ETag","\"etag2\"");
1829 resp2.setHeader("Cache-Control","max-age=5, must-revalidate");
1830
1831
1832 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(staleResponse);
1833
1834 execute(req1);
1835
1836 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1837
1838 execute(req2);
1839
1840 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
1841 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
1842
1843 final ClassicHttpRequest reval = reqCapture.getValue();
1844 boolean foundMaxAge0 = false;
1845 final Iterator<HeaderElement> it = MessageSupport.iterate(reval, HttpHeaders.CACHE_CONTROL);
1846 while (it.hasNext()) {
1847 final HeaderElement elt = it.next();
1848 if ("max-age".equalsIgnoreCase(elt.getName())
1849 && "0".equals(elt.getValue())) {
1850 foundMaxAge0 = true;
1851 }
1852 }
1853 Assertions.assertTrue(foundMaxAge0);
1854 }
1855
1856 @Test
1857 void testStaleEntryWithMustRevalidateIsNotUsedWithoutRevalidatingWithOrigin() throws Exception {
1858 final ClassicHttpResponse response = HttpTestUtils.make200Response();
1859 final Instant now = Instant.now();
1860 final Instant tenSecondsAgo = now.minusSeconds(10);
1861 response.setHeader("Date",DateUtils.formatStandardDate(tenSecondsAgo));
1862 response.setHeader("ETag","\"etag1\"");
1863 response.setHeader("Cache-Control","max-age=5, must-revalidate");
1864
1865 testStaleCacheResponseMustBeRevalidatedWithOrigin(response);
1866 }
1867
1868 protected void testGenerates504IfCannotRevalidateStaleResponse(
1869 final ClassicHttpResponse staleResponse) throws Exception {
1870 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1871
1872 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1873
1874 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(staleResponse);
1875
1876 execute(req1);
1877
1878 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new SocketTimeoutException());
1879
1880 final ClassicHttpResponse result = execute(req2);
1881
1882 Assertions.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT,
1883 result.getCode());
1884 }
1885
1886 @Test
1887 void testGenerates504IfCannotRevalidateAMustRevalidateEntry() throws Exception {
1888 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1889 final Instant now = Instant.now();
1890 final Instant tenSecondsAgo = now.minusSeconds(10);
1891 resp1.setHeader("ETag","\"etag\"");
1892 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1893 resp1.setHeader("Cache-Control","max-age=5,must-revalidate");
1894
1895 testGenerates504IfCannotRevalidateStaleResponse(resp1);
1896 }
1897
1898 @Test
1899 void testStaleEntryWithProxyRevalidateOnSharedCacheIsNotUsedWithoutRevalidatingWithOrigin() throws Exception {
1900 if (config.isSharedCache()) {
1901 final ClassicHttpResponse response = HttpTestUtils.make200Response();
1902 final Instant now = Instant.now();
1903 final Instant tenSecondsAgo = now.minusSeconds(10);
1904 response.setHeader("Date",DateUtils.formatStandardDate(tenSecondsAgo));
1905 response.setHeader("ETag","\"etag1\"");
1906 response.setHeader("Cache-Control","max-age=5, proxy-revalidate");
1907
1908 testStaleCacheResponseMustBeRevalidatedWithOrigin(response);
1909 }
1910 }
1911
1912 @Test
1913 void testGenerates504IfSharedCacheCannotRevalidateAProxyRevalidateEntry() throws Exception {
1914 if (config.isSharedCache()) {
1915 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1916 final Instant now = Instant.now();
1917 final Instant tenSecondsAgo = now.minusSeconds(10);
1918 resp1.setHeader("ETag","\"etag\"");
1919 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1920 resp1.setHeader("Cache-Control","max-age=5,proxy-revalidate");
1921
1922 testGenerates504IfCannotRevalidateStaleResponse(resp1);
1923 }
1924 }
1925
1926 @Test
1927 void testCacheControlPrivateIsNotCacheableBySharedCache() throws Exception {
1928 if (config.isSharedCache()) {
1929 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1930 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1931 resp1.setHeader("Cache-Control", "private,max-age=3600");
1932
1933 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1934
1935 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1936 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1937
1938 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1939
1940 execute(req1);
1941 execute(req2);
1942 }
1943 }
1944
1945 @Test
1946 void testCacheControlPrivateOnFieldIsNotReturnedBySharedCache() throws Exception {
1947 if (config.isSharedCache()) {
1948 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1949 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1950 resp1.setHeader("X-Personal", "stuff");
1951 resp1.setHeader("Cache-Control", "private=\"X-Personal\",s-maxage=3600");
1952
1953 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1954
1955 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1956 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1957
1958
1959 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1960
1961 execute(req1);
1962 final ClassicHttpResponse result = execute(req2);
1963 Assertions.assertNull(result.getFirstHeader("X-Personal"));
1964
1965 Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
1966 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
1967 }
1968 }
1969
1970 @Test
1971 void testNoCacheCannotSatisfyASubsequentRequestWithoutRevalidation() throws Exception {
1972 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1973 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1974 resp1.setHeader("ETag","\"etag\"");
1975 resp1.setHeader("Cache-Control","no-cache");
1976
1977 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1978
1979 execute(req1);
1980
1981 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
1982 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
1983
1984
1985 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
1986
1987 execute(req2);
1988
1989 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
1990 }
1991
1992 @Test
1993 void testNoCacheCannotSatisfyASubsequentRequestWithoutRevalidationEvenWithContraryIndications() throws Exception {
1994 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
1995 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1996 resp1.setHeader("ETag","\"etag\"");
1997 resp1.setHeader("Cache-Control","no-cache,s-maxage=3600");
1998
1999 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2000
2001 execute(req1);
2002
2003 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2004 req2.setHeader("Cache-Control","max-stale=7200");
2005 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
2006
2007
2008 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
2009
2010 execute(req2);
2011 }
2012
2013 @Test
2014 void testNoCacheOnFieldIsNotReturnedWithoutRevalidation() throws Exception {
2015 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2016 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2017 resp1.setHeader("ETag","\"etag\"");
2018 resp1.setHeader("X-Stuff","things");
2019 resp1.setHeader("Cache-Control","no-cache=\"X-Stuff\", max-age=3600");
2020
2021 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2022
2023 execute(req1);
2024
2025 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2026 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
2027 resp2.setHeader("ETag","\"etag\"");
2028 resp2.setHeader("X-Stuff","things");
2029 resp2.setHeader("Cache-Control","no-cache=\"X-Stuff\",max-age=3600");
2030
2031 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
2032
2033 final ClassicHttpResponse result = execute(req2);
2034
2035 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
2036 Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(reqCapture.capture(), Mockito.any());
2037
2038 final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
2039 if (allRequests.isEmpty()) {
2040 Assertions.assertNull(result.getFirstHeader("X-Stuff"));
2041 }
2042 }
2043
2044 @Test
2045 void testNoStoreOnRequestIsNotStoredInCache() throws Exception {
2046 request.setHeader("Cache-Control","no-store");
2047 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2048
2049 execute(request);
2050
2051 Mockito.verifyNoInteractions(mockCache);
2052 }
2053
2054 @Test
2055 void testNoStoreOnRequestIsNotStoredInCacheEvenIfResponseMarkedCacheable() throws Exception {
2056 request.setHeader("Cache-Control","no-store");
2057 originResponse.setHeader("Cache-Control","max-age=3600");
2058 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2059
2060 execute(request);
2061
2062 Mockito.verifyNoInteractions(mockCache);
2063 }
2064
2065 @Test
2066 void testNoStoreOnResponseIsNotStoredInCache() throws Exception {
2067 originResponse.setHeader("Cache-Control","no-store");
2068 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2069
2070 execute(request);
2071
2072 Mockito.verifyNoInteractions(mockCache);
2073 }
2074
2075 @Test
2076 void testNoStoreOnResponseIsNotStoredInCacheEvenWithContraryIndicators() throws Exception {
2077 originResponse.setHeader("Cache-Control","no-store,max-age=3600");
2078 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2079
2080 execute(request);
2081
2082 Mockito.verifyNoInteractions(mockCache);
2083 }
2084
2085 @Test
2086 void testOrderOfMultipleContentEncodingHeaderValuesIsPreserved() throws Exception {
2087 originResponse.addHeader("Content-Encoding","gzip");
2088 originResponse.addHeader("Content-Encoding","deflate");
2089 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2090
2091 final ClassicHttpResponse result = execute(request);
2092 int total_encodings = 0;
2093 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.CONTENT_ENCODING);
2094 while (it.hasNext()) {
2095 final HeaderElement elt = it.next();
2096 switch(total_encodings) {
2097 case 0:
2098 Assertions.assertEquals("gzip", elt.getName());
2099 break;
2100 case 1:
2101 Assertions.assertEquals("deflate", elt.getName());
2102 break;
2103 default:
2104 Assertions.fail("too many encodings");
2105 }
2106 total_encodings++;
2107 }
2108 Assertions.assertEquals(2, total_encodings);
2109 }
2110
2111 @Test
2112 void testOrderOfMultipleParametersInContentEncodingHeaderIsPreserved() throws Exception {
2113 originResponse.addHeader("Content-Encoding","gzip,deflate");
2114 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2115
2116 final ClassicHttpResponse result = execute(request);
2117 int total_encodings = 0;
2118 final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.CONTENT_ENCODING);
2119 while (it.hasNext()) {
2120 final HeaderElement elt = it.next();
2121 switch(total_encodings) {
2122 case 0:
2123 Assertions.assertEquals("gzip", elt.getName());
2124 break;
2125 case 1:
2126 Assertions.assertEquals("deflate", elt.getName());
2127 break;
2128 default:
2129 Assertions.fail("too many encodings");
2130 }
2131 total_encodings++;
2132 }
2133 Assertions.assertEquals(2, total_encodings);
2134 }
2135
2136 @Test
2137 void testCacheDoesNotAssumeContentLocationHeaderIndicatesAnotherCacheableResource() throws Exception {
2138 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/foo");
2139 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2140 resp1.setHeader("Cache-Control","public,max-age=3600");
2141 resp1.setHeader("Etag","\"etag\"");
2142 resp1.setHeader("Content-Location","http://foo.example.com/bar");
2143
2144 execute(req1);
2145
2146 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/bar");
2147 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
2148 resp2.setHeader("Cache-Control","public,max-age=3600");
2149 resp2.setHeader("Etag","\"etag\"");
2150
2151 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2152 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
2153
2154 execute(req2);
2155 }
2156
2157 @Test
2158 void testCachedResponsesWithMissingDateHeadersShouldBeAssignedOne() throws Exception {
2159 originResponse.removeHeaders("Date");
2160 originResponse.setHeader("Cache-Control","public");
2161 originResponse.setHeader("ETag","\"etag\"");
2162
2163 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2164
2165 final ClassicHttpResponse result = execute(request);
2166 Assertions.assertNotNull(result.getFirstHeader("Date"));
2167 }
2168
2169 private void testInvalidExpiresHeaderIsTreatedAsStale(
2170 final String expiresHeader) throws Exception {
2171 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2172 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2173 resp1.setHeader("Cache-Control","public");
2174 resp1.setHeader("ETag","\"etag\"");
2175 resp1.setHeader("Expires", expiresHeader);
2176
2177 execute(req1);
2178
2179 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2180 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
2181
2182 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2183
2184 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
2185
2186 execute(req2);
2187 }
2188
2189 @Test
2190 void testMalformedExpiresHeaderIsTreatedAsStale() throws Exception {
2191 testInvalidExpiresHeaderIsTreatedAsStale("garbage");
2192 }
2193
2194 @Test
2195 void testExpiresZeroHeaderIsTreatedAsStale() throws Exception {
2196 testInvalidExpiresHeaderIsTreatedAsStale("0");
2197 }
2198
2199 @Test
2200 void testExpiresHeaderEqualToDateHeaderIsTreatedAsStale() throws Exception {
2201 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
2202 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
2203 resp1.setHeader("Cache-Control","public");
2204 resp1.setHeader("ETag","\"etag\"");
2205 resp1.setHeader("Expires", resp1.getFirstHeader("Date").getValue());
2206
2207 execute(req1);
2208
2209 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
2210 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
2211
2212 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
2213
2214 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
2215
2216 execute(req2);
2217 }
2218
2219 @Test
2220 void testDoesNotModifyServerResponseHeader() throws Exception {
2221 final String server = "MockServer/1.0";
2222 originResponse.setHeader("Server", server);
2223
2224 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
2225
2226 final ClassicHttpResponse result = execute(request);
2227 Assertions.assertEquals(server, result.getFirstHeader("Server").getValue());
2228 }
2229
2230 }