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.junit.jupiter.api.Assertions.assertEquals;
30 import static org.junit.jupiter.api.Assertions.assertFalse;
31 import static org.junit.jupiter.api.Assertions.assertNotEquals;
32 import static org.junit.jupiter.api.Assertions.assertTrue;
33
34 import java.io.IOException;
35 import java.time.Instant;
36 import java.time.temporal.ChronoUnit;
37 import java.util.Iterator;
38
39 import org.apache.hc.client5.http.HttpRoute;
40 import org.apache.hc.client5.http.auth.StandardAuthScheme;
41 import org.apache.hc.client5.http.cache.HttpCacheContext;
42 import org.apache.hc.client5.http.classic.ExecChain;
43 import org.apache.hc.client5.http.classic.ExecRuntime;
44 import org.apache.hc.client5.http.utils.DateUtils;
45 import org.apache.hc.core5.http.ClassicHttpRequest;
46 import org.apache.hc.core5.http.ClassicHttpResponse;
47 import org.apache.hc.core5.http.Header;
48 import org.apache.hc.core5.http.HeaderElement;
49 import org.apache.hc.core5.http.HttpEntity;
50 import org.apache.hc.core5.http.HttpException;
51 import org.apache.hc.core5.http.HttpHeaders;
52 import org.apache.hc.core5.http.HttpHost;
53 import org.apache.hc.core5.http.HttpStatus;
54 import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
55 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
56 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
57 import org.apache.hc.core5.http.message.MessageSupport;
58 import org.hamcrest.MatcherAssert;
59 import org.junit.jupiter.api.BeforeEach;
60 import org.junit.jupiter.api.Test;
61 import org.mockito.ArgumentCaptor;
62 import org.mockito.Mock;
63 import org.mockito.Mockito;
64 import org.mockito.MockitoAnnotations;
65
66
67
68
69
70
71 class TestProtocolRecommendations {
72
73 static final int MAX_BYTES = 1024;
74 static final int MAX_ENTRIES = 100;
75 static final int ENTITY_LENGTH = 128;
76
77 HttpHost host;
78 HttpRoute route;
79 HttpEntity body;
80 HttpCacheContext context;
81 @Mock
82 ExecChain mockExecChain;
83 @Mock
84 ExecRuntime mockExecRuntime;
85 ClassicHttpRequest request;
86 ClassicHttpResponse originResponse;
87 CacheConfig config;
88 CachingExec impl;
89 HttpCache cache;
90 Instant now;
91 Instant tenSecondsAgo;
92 Instant twoMinutesAgo;
93
94 @BeforeEach
95 void setUp() throws Exception {
96 MockitoAnnotations.openMocks(this);
97 host = new HttpHost("foo.example.com", 80);
98
99 route = new HttpRoute(host);
100
101 body = HttpTestUtils.makeBody(ENTITY_LENGTH);
102
103 request = new BasicClassicHttpRequest("GET", "/foo");
104
105 context = HttpCacheContext.create();
106
107 originResponse = HttpTestUtils.make200Response();
108
109 config = CacheConfig.custom()
110 .setMaxCacheEntries(MAX_ENTRIES)
111 .setMaxObjectSize(MAX_BYTES)
112 .build();
113
114 cache = new BasicHttpCache(config);
115 impl = new CachingExec(cache, null, config);
116
117 now = Instant.now();
118 tenSecondsAgo = now.minus(10, ChronoUnit.SECONDS);
119 twoMinutesAgo = now.minus(1, ChronoUnit.MINUTES);
120
121 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
122 }
123
124 public ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
125 return impl.execute(
126 ClassicRequestBuilder.copy(request).build(),
127 new ExecChain.Scope("test", route, request, mockExecRuntime, context),
128 mockExecChain);
129 }
130
131 private void cacheGenerated304ForValidatorShouldNotContainEntityHeader(
132 final String headerName, final String headerValue, final String validatorHeader,
133 final String validator, final String conditionalHeader) throws Exception {
134 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
135 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
136 resp1.setHeader("Cache-Control","max-age=3600");
137 resp1.setHeader(validatorHeader, validator);
138 resp1.setHeader(headerName, headerValue);
139 resp1.setHeader("ETag", "\"etag\"");
140
141 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
142
143 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
144 req2.setHeader(conditionalHeader, validator);
145
146 execute(req1);
147 final ClassicHttpResponse result = execute(req2);
148
149 assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
150 assertFalse(result.containsHeader(headerName));
151 }
152
153 private void cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
154 final String headerName, final String headerValue) throws Exception {
155 cacheGenerated304ForValidatorShouldNotContainEntityHeader(headerName,
156 headerValue, "ETag", "\"etag\"", "If-None-Match");
157 }
158
159 private void cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
160 final String headerName, final String headerValue) throws Exception {
161 cacheGenerated304ForValidatorShouldNotContainEntityHeader(headerName,
162 headerValue, "Last-Modified", DateUtils.formatStandardDate(twoMinutesAgo),
163 "If-Modified-Since");
164 }
165
166 @Test
167 void cacheGenerated304ForStrongEtagValidatorShouldNotContainAllow() throws Exception {
168 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
169 "Allow", "GET,HEAD");
170 }
171
172 @Test
173 void cacheGenerated304ForStrongDateValidatorShouldNotContainAllow() throws Exception {
174 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
175 "Allow", "GET,HEAD");
176 }
177
178 @Test
179 void cacheGenerated304ForStrongEtagValidatorShouldNotContainContentEncoding() throws Exception {
180 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
181 "Content-Encoding", "gzip");
182 }
183
184 @Test
185 void cacheGenerated304ForStrongDateValidatorShouldNotContainContentEncoding() throws Exception {
186 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
187 "Content-Encoding", "gzip");
188 }
189
190 @Test
191 void cacheGenerated304ForStrongEtagValidatorShouldNotContainContentLanguage() throws Exception {
192 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
193 "Content-Language", "en");
194 }
195
196 @Test
197 void cacheGenerated304ForStrongDateValidatorShouldNotContainContentLanguage() throws Exception {
198 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
199 "Content-Language", "en");
200 }
201
202 @Test
203 void cacheGenerated304ForStrongValidatorShouldNotContainContentLength() throws Exception {
204 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
205 "Content-Length", "128");
206 }
207
208 @Test
209 void cacheGenerated304ForStrongDateValidatorShouldNotContainContentLength() throws Exception {
210 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
211 "Content-Length", "128");
212 }
213
214 @Test
215 void cacheGenerated304ForStrongValidatorShouldNotContainContentMD5() throws Exception {
216 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
217 "Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
218 }
219
220 @Test
221 void cacheGenerated304ForStrongDateValidatorShouldNotContainContentMD5() throws Exception {
222 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
223 "Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
224 }
225
226 @Test
227 void cacheGenerated304ForStrongEtagValidatorShouldNotContainContentType() throws Exception {
228 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
229 "Content-Type", "text/html");
230 }
231
232 @Test
233 void cacheGenerated304ForStrongDateValidatorShouldNotContainContentType() throws Exception {
234 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
235 "Content-Type", "text/html");
236 }
237
238 @Test
239 void cacheGenerated304ForStrongEtagValidatorShouldNotContainLastModified() throws Exception {
240 cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
241 "Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo));
242 }
243
244 @Test
245 void cacheGenerated304ForStrongDateValidatorShouldNotContainLastModified() throws Exception {
246 cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
247 "Last-Modified", DateUtils.formatStandardDate(twoMinutesAgo));
248 }
249
250 private ClassicHttpRequest requestToPopulateStaleCacheEntry() throws Exception {
251 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
252 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
253 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
254 resp1.setHeader("Cache-Control","public,max-age=5");
255 resp1.setHeader("Etag","\"etag\"");
256
257 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
258 return req1;
259 }
260
261 private void testDoesNotReturnStaleResponseOnError(final ClassicHttpRequest req2) throws Exception {
262 final ClassicHttpRequest req1 = requestToPopulateStaleCacheEntry();
263
264 execute(req1);
265
266 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new IOException());
267
268 ClassicHttpResponse result = null;
269 try {
270 result = execute(req2);
271 } catch (final IOException acceptable) {
272 }
273
274 if (result != null) {
275 assertNotEquals(HttpStatus.SC_OK, result.getCode());
276 }
277 }
278
279 @Test
280 void testDoesNotReturnStaleResponseIfClientExplicitlyRequestsFirstHandOneWithCacheControl() throws Exception {
281 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
282 req.setHeader("Cache-Control","no-cache");
283 testDoesNotReturnStaleResponseOnError(req);
284 }
285
286 @Test
287 void testDoesNotReturnStaleResponseIfClientExplicitlyRequestsFreshWithMaxAge() throws Exception {
288 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
289 req.setHeader("Cache-Control","max-age=0");
290 testDoesNotReturnStaleResponseOnError(req);
291 }
292
293 @Test
294 void testDoesNotReturnStaleResponseIfClientExplicitlySpecifiesLargerMaxAge() throws Exception {
295 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
296 req.setHeader("Cache-Control","max-age=20");
297 testDoesNotReturnStaleResponseOnError(req);
298 }
299
300
301 @Test
302 void testDoesNotReturnStaleResponseIfClientExplicitlyRequestsFreshWithMinFresh() throws Exception {
303 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
304 req.setHeader("Cache-Control","min-fresh=2");
305
306 testDoesNotReturnStaleResponseOnError(req);
307 }
308
309 @Test
310 void testDoesNotReturnStaleResponseIfClientExplicitlyRequestsFreshWithMaxStale() throws Exception {
311 final ClassicHttpRequest req = new BasicClassicHttpRequest("GET", "/");
312 req.setHeader("Cache-Control","max-stale=2");
313
314 testDoesNotReturnStaleResponseOnError(req);
315 }
316
317 @Test
318 void testMayReturnStaleResponseIfClientExplicitlySpecifiesAcceptableMaxStale() throws Exception {
319 final ClassicHttpRequest req1 = requestToPopulateStaleCacheEntry();
320 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
321 req2.setHeader("Cache-Control","max-stale=20");
322
323 execute(req1);
324
325 final ClassicHttpResponse result = execute(req2);
326
327 assertEquals(HttpStatus.SC_OK, result.getCode());
328
329 Mockito.verify(mockExecChain, Mockito.atMost(1)).proceed(Mockito.any(), Mockito.any());
330 }
331
332 private void testDoesNotModifyHeaderOnResponses(final String headerName) throws Exception {
333 final String headerValue = HttpTestUtils
334 .getCanonicalHeaderValue(originResponse, headerName);
335 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
336
337 final ClassicHttpResponse result = execute(request);
338
339 MatcherAssert.assertThat(result, ContainsHeaderMatcher.contains(headerName, headerValue));
340 }
341
342 private void testDoesNotModifyHeaderOnRequests(final String headerName) throws Exception {
343 final String headerValue = HttpTestUtils.getCanonicalHeaderValue(request, headerName);
344 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
345
346 execute(request);
347
348 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
349 Mockito.verify(mockExecChain).proceed(reqCapture.capture(), Mockito.any());
350
351 assertEquals(headerValue, HttpTestUtils.getCanonicalHeaderValue(reqCapture.getValue(), headerName));
352 }
353
354 @Test
355 void testDoesNotModifyAcceptRangesOnResponses() throws Exception {
356 final String headerName = "Accept-Ranges";
357 originResponse.setHeader(headerName,"bytes");
358 testDoesNotModifyHeaderOnResponses(headerName);
359 }
360
361 @Test
362 void testDoesNotModifyAuthorizationOnRequests() throws Exception {
363 request.setHeader("Authorization", StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Q=");
364 testDoesNotModifyHeaderOnRequests("Authorization");
365 }
366
367 @Test
368 void testDoesNotModifyContentLengthOnRequests() throws Exception {
369 final ClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
370 post.setEntity(HttpTestUtils.makeBody(128));
371 post.setHeader("Content-Length","128");
372 request = post;
373 testDoesNotModifyHeaderOnRequests("Content-Length");
374 }
375
376 @Test
377 void testDoesNotModifyContentLengthOnResponses() throws Exception {
378 originResponse.setEntity(HttpTestUtils.makeBody(128));
379 originResponse.setHeader("Content-Length","128");
380 testDoesNotModifyHeaderOnResponses("Content-Length");
381 }
382
383 @Test
384 void testDoesNotModifyContentMD5OnRequests() throws Exception {
385 final ClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
386 post.setEntity(HttpTestUtils.makeBody(128));
387 post.setHeader("Content-Length","128");
388 post.setHeader("Content-MD5","Q2hlY2sgSW50ZWdyaXR5IQ==");
389 request = post;
390 testDoesNotModifyHeaderOnRequests("Content-MD5");
391 }
392
393 @Test
394 void testDoesNotModifyContentMD5OnResponses() throws Exception {
395 originResponse.setEntity(HttpTestUtils.makeBody(128));
396 originResponse.setHeader("Content-MD5","Q2hlY2sgSW50ZWdyaXR5IQ==");
397 testDoesNotModifyHeaderOnResponses("Content-MD5");
398 }
399
400 @Test
401 void testDoesNotModifyContentRangeOnRequests() throws Exception {
402 final ClassicHttpRequest put = new BasicClassicHttpRequest("PUT", "/");
403 put.setEntity(HttpTestUtils.makeBody(128));
404 put.setHeader("Content-Length","128");
405 put.setHeader("Content-Range","bytes 0-127/256");
406 request = put;
407 testDoesNotModifyHeaderOnRequests("Content-Range");
408 }
409
410 @Test
411 void testDoesNotModifyContentRangeOnResponses() throws Exception {
412 request.setHeader("Range","bytes=0-128");
413 originResponse.setCode(HttpStatus.SC_PARTIAL_CONTENT);
414 originResponse.setReasonPhrase("Partial Content");
415 originResponse.setEntity(HttpTestUtils.makeBody(128));
416 originResponse.setHeader("Content-Range","bytes 0-127/256");
417 testDoesNotModifyHeaderOnResponses("Content-Range");
418 }
419
420 @Test
421 void testDoesNotModifyContentTypeOnRequests() throws Exception {
422 final ClassicHttpRequest post = new BasicClassicHttpRequest("POST", "/");
423 post.setEntity(HttpTestUtils.makeBody(128));
424 post.setHeader("Content-Length","128");
425 post.setHeader("Content-Type","application/octet-stream");
426 request = post;
427 testDoesNotModifyHeaderOnRequests("Content-Type");
428 }
429
430 @Test
431 void testDoesNotModifyContentTypeOnResponses() throws Exception {
432 originResponse.setHeader("Content-Type","application/octet-stream");
433 testDoesNotModifyHeaderOnResponses("Content-Type");
434 }
435
436 @Test
437 void testDoesNotModifyDateOnRequests() throws Exception {
438 request.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
439 testDoesNotModifyHeaderOnRequests("Date");
440 }
441
442 @Test
443 void testDoesNotModifyDateOnResponses() throws Exception {
444 originResponse.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
445 testDoesNotModifyHeaderOnResponses("Date");
446 }
447
448 @Test
449 void testDoesNotModifyETagOnResponses() throws Exception {
450 originResponse.setHeader("ETag", "\"random-etag\"");
451 testDoesNotModifyHeaderOnResponses("ETag");
452 }
453
454 @Test
455 void testDoesNotModifyExpiresOnResponses() throws Exception {
456 originResponse.setHeader("Expires", DateUtils.formatStandardDate(Instant.now()));
457 testDoesNotModifyHeaderOnResponses("Expires");
458 }
459
460 @Test
461 void testDoesNotModifyFromOnRequests() throws Exception {
462 request.setHeader("From", "foo@example.com");
463 testDoesNotModifyHeaderOnRequests("From");
464 }
465
466 @Test
467 void testDoesNotModifyIfMatchOnRequests() throws Exception {
468 request = new BasicClassicHttpRequest("DELETE", "/");
469 request.setHeader("If-Match", "\"etag\"");
470 testDoesNotModifyHeaderOnRequests("If-Match");
471 }
472
473 @Test
474 void testDoesNotModifyIfModifiedSinceOnRequests() throws Exception {
475 request.setHeader("If-Modified-Since", DateUtils.formatStandardDate(Instant.now()));
476 testDoesNotModifyHeaderOnRequests("If-Modified-Since");
477 }
478
479 @Test
480 void testDoesNotModifyIfNoneMatchOnRequests() throws Exception {
481 request.setHeader("If-None-Match", "\"etag\"");
482 testDoesNotModifyHeaderOnRequests("If-None-Match");
483 }
484
485 @Test
486 void testDoesNotModifyIfRangeOnRequests() throws Exception {
487 request.setHeader("Range","bytes=0-128");
488 request.setHeader("If-Range", "\"etag\"");
489 testDoesNotModifyHeaderOnRequests("If-Range");
490 }
491
492 @Test
493 void testDoesNotModifyIfUnmodifiedSinceOnRequests() throws Exception {
494 request = new BasicClassicHttpRequest("DELETE", "/");
495 request.setHeader("If-Unmodified-Since", DateUtils.formatStandardDate(Instant.now()));
496 testDoesNotModifyHeaderOnRequests("If-Unmodified-Since");
497 }
498
499 @Test
500 void testDoesNotModifyLastModifiedOnResponses() throws Exception {
501 originResponse.setHeader("Last-Modified", DateUtils.formatStandardDate(Instant.now()));
502 testDoesNotModifyHeaderOnResponses("Last-Modified");
503 }
504
505 @Test
506 void testDoesNotModifyLocationOnResponses() throws Exception {
507 originResponse.setCode(HttpStatus.SC_TEMPORARY_REDIRECT);
508 originResponse.setReasonPhrase("Temporary Redirect");
509 originResponse.setHeader("Location", "http://foo.example.com/bar");
510 testDoesNotModifyHeaderOnResponses("Location");
511 }
512
513 @Test
514 void testDoesNotModifyRangeOnRequests() throws Exception {
515 request.setHeader("Range", "bytes=0-128");
516 testDoesNotModifyHeaderOnRequests("Range");
517 }
518
519 @Test
520 void testDoesNotModifyRefererOnRequests() throws Exception {
521 request.setHeader("Referer", "http://foo.example.com/bar");
522 testDoesNotModifyHeaderOnRequests("Referer");
523 }
524
525 @Test
526 void testDoesNotModifyRetryAfterOnResponses() throws Exception {
527 originResponse.setCode(HttpStatus.SC_SERVICE_UNAVAILABLE);
528 originResponse.setReasonPhrase("Service Unavailable");
529 originResponse.setHeader("Retry-After", "120");
530 testDoesNotModifyHeaderOnResponses("Retry-After");
531 }
532
533 @Test
534 void testDoesNotModifyServerOnResponses() throws Exception {
535 originResponse.setHeader("Server", "SomeServer/1.0");
536 testDoesNotModifyHeaderOnResponses("Server");
537 }
538
539 @Test
540 void testDoesNotModifyUserAgentOnRequests() throws Exception {
541 request.setHeader("User-Agent", "MyClient/1.0");
542 testDoesNotModifyHeaderOnRequests("User-Agent");
543 }
544
545 @Test
546 void testDoesNotModifyVaryOnResponses() throws Exception {
547 request.setHeader("Accept-Encoding","identity");
548 originResponse.setHeader("Vary", "Accept-Encoding");
549 testDoesNotModifyHeaderOnResponses("Vary");
550 }
551
552 @Test
553 void testDoesNotModifyExtensionHeaderOnRequests() throws Exception {
554 request.setHeader("X-Extension","x-value");
555 testDoesNotModifyHeaderOnRequests("X-Extension");
556 }
557
558 @Test
559 void testDoesNotModifyExtensionHeaderOnResponses() throws Exception {
560 originResponse.setHeader("X-Extension", "x-value");
561 testDoesNotModifyHeaderOnResponses("X-Extension");
562 }
563
564 @Test
565 void testUsesLastModifiedDateForCacheConditionalRequests() throws Exception {
566 final Instant twentySecondsAgo = now.plusSeconds(20);
567 final String lmDate = DateUtils.formatStandardDate(twentySecondsAgo);
568
569 final ClassicHttpRequest req1 =
570 new BasicClassicHttpRequest("GET", "/");
571 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
572 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
573 resp1.setHeader("Last-Modified", lmDate);
574 resp1.setHeader("Cache-Control","max-age=5");
575
576 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
577 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
578
579 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
580
581 execute(req1);
582
583 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
584
585 execute(req2);
586
587 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
588 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
589
590 final ClassicHttpRequest captured = reqCapture.getValue();
591 MatcherAssert.assertThat(captured, ContainsHeaderMatcher.contains("If-Modified-Since", lmDate));
592 }
593
594 @Test
595 void testUsesBothLastModifiedAndETagForConditionalRequestsIfAvailable() throws Exception {
596 final Instant twentySecondsAgo = now.plusSeconds(20);
597 final String lmDate = DateUtils.formatStandardDate(twentySecondsAgo);
598 final String etag = "\"etag\"";
599
600 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
601 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
602 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
603 resp1.setHeader("Last-Modified", lmDate);
604 resp1.setHeader("Cache-Control","max-age=5");
605 resp1.setHeader("ETag", etag);
606
607 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
608 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
609
610 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
611
612 execute(req1);
613
614 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
615
616 execute(req2);
617
618 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
619 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(reqCapture.capture(), Mockito.any());
620
621 final ClassicHttpRequest captured = reqCapture.getValue();
622
623 MatcherAssert.assertThat(captured, ContainsHeaderMatcher.contains("If-Modified-Since", lmDate));
624 MatcherAssert.assertThat(captured, ContainsHeaderMatcher.contains("If-None-Match", etag));
625 }
626
627 @Test
628 void testRevalidatesCachedResponseWithExpirationInThePast() throws Exception {
629 final Instant oneSecondAgo = now.minusSeconds(1);
630 final Instant oneSecondFromNow = now.plusSeconds(1);
631 final Instant twoSecondsFromNow = now.plusSeconds(2);
632 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
633 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
634 resp1.setHeader("ETag","\"etag\"");
635 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
636 resp1.setHeader("Expires",DateUtils.formatStandardDate(oneSecondAgo));
637 resp1.setHeader("Cache-Control", "public");
638
639 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
640
641 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
642 final ClassicHttpRequest revalidate = new BasicClassicHttpRequest("GET", "/");
643 revalidate.setHeader("If-None-Match","\"etag\"");
644
645 final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
646 resp2.setHeader("Date", DateUtils.formatStandardDate(twoSecondsFromNow));
647 resp2.setHeader("Expires", DateUtils.formatStandardDate(oneSecondFromNow));
648 resp2.setHeader("ETag","\"etag\"");
649
650 Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(revalidate), Mockito.any())).thenReturn(resp2);
651
652 execute(req1);
653 final ClassicHttpResponse result = execute(req2);
654
655 assertEquals(HttpStatus.SC_OK, result.getCode());
656 }
657
658 @Test
659 void testRetriesValidationThatResultsInAnOlderDated304Response() throws Exception {
660 final Instant elevenSecondsAgo = now.minusSeconds(11);
661 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
662 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
663 resp1.setHeader("ETag","\"etag\"");
664 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
665 resp1.setHeader("Cache-Control","max-age=5");
666
667 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
668 final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
669 resp2.setHeader("ETag","\"etag\"");
670 resp2.setHeader("Date", DateUtils.formatStandardDate(elevenSecondsAgo));
671
672 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
673
674 execute(req1);
675
676 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
677
678 execute(req2);
679
680 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
681 Mockito.verify(mockExecChain, Mockito.times(3)).proceed(reqCapture.capture(), Mockito.any());
682
683 final ClassicHttpRequest captured = reqCapture.getValue();
684 boolean hasMaxAge0 = false;
685 boolean hasNoCache = false;
686 final Iterator<HeaderElement> it = MessageSupport.iterate(captured, HttpHeaders.CACHE_CONTROL);
687 while (it.hasNext()) {
688 final HeaderElement elt = it.next();
689 if ("max-age".equals(elt.getName())) {
690 try {
691 final int maxage = Integer.parseInt(elt.getValue());
692 if (maxage == 0) {
693 hasMaxAge0 = true;
694 }
695 } catch (final NumberFormatException nfe) {
696
697 }
698 } else if ("no-cache".equals(elt.getName())) {
699 hasNoCache = true;
700 }
701 }
702 assertTrue(hasMaxAge0 || hasNoCache);
703 assertFalse(captured.containsHeader("If-None-Match"));
704 assertFalse(captured.containsHeader("If-Modified-Since"));
705 assertFalse(captured.containsHeader("If-Range"));
706 assertFalse(captured.containsHeader("If-Match"));
707 assertFalse(captured.containsHeader("If-Unmodified-Since"));
708 }
709
710 @Test
711 void testSendsAllVariantEtagsInConditionalRequest() throws Exception {
712 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET","/");
713 req1.setHeader("User-Agent","agent1");
714 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
715 resp1.setHeader("Cache-Control","max-age=3600");
716 resp1.setHeader("Vary","User-Agent");
717 resp1.setHeader("Etag","\"etag1\"");
718
719 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET","/");
720 req2.setHeader("User-Agent","agent2");
721 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
722 resp2.setHeader("Cache-Control","max-age=3600");
723 resp2.setHeader("Vary","User-Agent");
724 resp2.setHeader("Etag","\"etag2\"");
725
726 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET","/");
727 req3.setHeader("User-Agent","agent3");
728 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
729
730 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
731
732 execute(req1);
733
734 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
735
736 execute(req2);
737
738 Mockito.when(mockExecChain.proceed(Mockito.any(),Mockito.any())).thenReturn(resp3);
739
740 execute(req3);
741
742 final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
743 Mockito.verify(mockExecChain, Mockito.times(3)).proceed(reqCapture.capture(), Mockito.any());
744
745 final ClassicHttpRequest captured = reqCapture.getValue();
746 boolean foundEtag1 = false;
747 boolean foundEtag2 = false;
748 for(final Header h : captured.getHeaders("If-None-Match")) {
749 for(final String etag : h.getValue().split(",")) {
750 if ("\"etag1\"".equals(etag.trim())) {
751 foundEtag1 = true;
752 }
753 if ("\"etag2\"".equals(etag.trim())) {
754 foundEtag2 = true;
755 }
756 }
757 }
758 assertTrue(foundEtag1 && foundEtag2);
759 }
760
761 @Test
762 void testResponseToExistingVariantsUpdatesEntry() throws Exception {
763
764 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
765 req1.setHeader("User-Agent", "agent1");
766
767 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
768 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
769 resp1.setHeader("Vary", "User-Agent");
770 resp1.setHeader("Cache-Control", "max-age=3600");
771 resp1.setHeader("ETag", "\"etag1\"");
772
773 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
774 req2.setHeader("User-Agent", "agent2");
775
776 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
777 resp2.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
778 resp2.setHeader("Vary", "User-Agent");
779 resp2.setHeader("Cache-Control", "max-age=3600");
780 resp2.setHeader("ETag", "\"etag2\"");
781
782 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
783 req3.setHeader("User-Agent", "agent3");
784
785 final ClassicHttpResponse resp3 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
786 resp3.setHeader("Date", DateUtils.formatStandardDate(now));
787 resp3.setHeader("ETag", "\"etag1\"");
788
789 final ClassicHttpRequest req4 = new BasicClassicHttpRequest("GET", "/");
790 req4.setHeader("User-Agent", "agent1");
791
792 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
793 execute(req1);
794
795 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
796 execute(req2);
797
798 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
799 final ClassicHttpResponse result1 = execute(req3);
800
801 final ClassicHttpResponse result2 = execute(req4);
802
803 assertEquals(HttpStatus.SC_OK, result1.getCode());
804 MatcherAssert.assertThat(result1, ContainsHeaderMatcher.contains("ETag", "\"etag1\""));
805 MatcherAssert.assertThat(result1, ContainsHeaderMatcher.contains("Date", DateUtils.formatStandardDate(now)));
806 MatcherAssert.assertThat(result2, ContainsHeaderMatcher.contains("Date", DateUtils.formatStandardDate(now)));
807 }
808
809 @Test
810 void testResponseToExistingVariantsIsCachedForFutureResponses() throws Exception {
811
812 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
813 req1.setHeader("User-Agent", "agent1");
814
815 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
816 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
817 resp1.setHeader("Vary", "User-Agent");
818 resp1.setHeader("Cache-Control", "max-age=3600");
819 resp1.setHeader("ETag", "\"etag1\"");
820
821 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
822
823 execute(req1);
824
825 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
826 req2.setHeader("User-Agent", "agent2");
827
828 final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
829 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
830 resp2.setHeader("ETag", "\"etag1\"");
831
832 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
833
834 execute(req2);
835
836 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
837 req3.setHeader("User-Agent", "agent2");
838
839 execute(req3);
840
841 Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
842 }
843
844 @Test
845 void shouldInvalidateNonvariantCacheEntryForUnknownMethod() throws Exception {
846 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
847 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
848 resp1.setHeader("Cache-Control","max-age=3600");
849
850 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
851
852 execute(req1);
853
854 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("FROB", "/");
855 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
856 resp2.setHeader("Cache-Control","max-age=3600");
857
858 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
859
860 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
861 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
862 resp3.setHeader("ETag", "\"etag\"");
863
864 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
865
866 execute(req2);
867 final ClassicHttpResponse result = execute(req3);
868
869 assertTrue(HttpTestUtils.semanticallyTransparent(resp3, result));
870 }
871
872 @Test
873 void shouldInvalidateAllVariantsForUnknownMethod() throws Exception {
874 final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
875 req1.setHeader("User-Agent", "agent1");
876 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
877 resp1.setHeader("Cache-Control","max-age=3600");
878 resp1.setHeader("Vary", "User-Agent");
879
880 final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
881 req2.setHeader("User-Agent", "agent2");
882 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
883 resp2.setHeader("Cache-Control","max-age=3600");
884 resp2.setHeader("Vary", "User-Agent");
885
886 final ClassicHttpRequest req3 = new BasicClassicHttpRequest("FROB", "/");
887 req3.setHeader("User-Agent", "agent3");
888 final ClassicHttpResponse resp3 = HttpTestUtils.make200Response();
889 resp3.setHeader("Cache-Control","max-age=3600");
890
891 final ClassicHttpRequest req4 = new BasicClassicHttpRequest("GET", "/");
892 req4.setHeader("User-Agent", "agent1");
893 final ClassicHttpResponse resp4 = HttpTestUtils.make200Response();
894 resp4.setHeader("ETag", "\"etag1\"");
895
896 final ClassicHttpRequest req5 = new BasicClassicHttpRequest("GET", "/");
897 req5.setHeader("User-Agent", "agent2");
898 final ClassicHttpResponse resp5 = HttpTestUtils.make200Response();
899 resp5.setHeader("ETag", "\"etag2\"");
900
901 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
902
903 execute(req1);
904
905 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
906
907 execute(req2);
908
909 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp3);
910
911 execute(req3);
912
913 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp4);
914
915 final ClassicHttpResponse result4 = execute(req4);
916
917 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp5);
918
919 final ClassicHttpResponse result5 = execute(req5);
920
921 assertTrue(HttpTestUtils.semanticallyTransparent(resp4, result4));
922 assertTrue(HttpTestUtils.semanticallyTransparent(resp5, result5));
923 }
924
925 @Test
926 void cacheShouldUpdateWithNewCacheableResponse() throws Exception {
927 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
928 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
929 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
930 resp1.setHeader("Cache-Control", "max-age=3600");
931 resp1.setHeader("ETag", "\"etag1\"");
932
933 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
934
935 execute(req1);
936
937 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
938 req2.setHeader("Cache-Control", "max-age=0");
939 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
940 resp2.setHeader("Date", DateUtils.formatStandardDate(now));
941 resp2.setHeader("Cache-Control", "max-age=3600");
942 resp2.setHeader("ETag", "\"etag2\"");
943
944 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
945
946 final ClassicHttpRequest req3 = HttpTestUtils.makeDefaultRequest();
947
948 execute(req2);
949 final ClassicHttpResponse result = execute(req3);
950
951 assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
952 }
953
954 @Test
955 void expiresEqualToDateWithNoCacheControlIsNotCacheable() throws Exception {
956 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
957 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
958 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
959 resp1.setHeader("Expires", DateUtils.formatStandardDate(now));
960 resp1.removeHeaders("Cache-Control");
961
962 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
963
964 execute(req1);
965
966 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
967 req2.setHeader("Cache-Control", "max-stale=1000");
968 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
969 resp2.setHeader("ETag", "\"etag2\"");
970
971 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
972
973 final ClassicHttpResponse result = execute(req2);
974
975 assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
976 }
977
978 @Test
979 void expiresPriorToDateWithNoCacheControlIsNotCacheable() throws Exception {
980 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
981 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
982 resp1.setHeader("Date", DateUtils.formatStandardDate(now));
983 resp1.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsAgo));
984 resp1.removeHeaders("Cache-Control");
985
986 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
987
988 execute(req1);
989
990 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
991 req2.setHeader("Cache-Control", "max-stale=1000");
992 final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
993 resp2.setHeader("ETag", "\"etag2\"");
994
995 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
996
997 final ClassicHttpResponse result = execute(req2);
998
999 assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
1000 }
1001
1002 @Test
1003 void cacheMissResultsIn504WithOnlyIfCached() throws Exception {
1004 final ClassicHttpRequest req = HttpTestUtils.makeDefaultRequest();
1005 req.setHeader("Cache-Control", "only-if-cached");
1006
1007 final ClassicHttpResponse result = execute(req);
1008
1009 assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, result.getCode());
1010 }
1011
1012 @Test
1013 void cacheHitOkWithOnlyIfCached() throws Exception {
1014 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1015 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1016 resp1.setHeader("Cache-Control","max-age=3600");
1017
1018 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1019
1020 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
1021 req2.setHeader("Cache-Control", "only-if-cached");
1022
1023 execute(req1);
1024 final ClassicHttpResponse result = execute(req2);
1025
1026 assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result));
1027 }
1028
1029 @Test
1030 void returns504ForStaleEntryWithOnlyIfCached() throws Exception {
1031 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1032 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1033 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1034 resp1.setHeader("Cache-Control","max-age=5");
1035
1036 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1037
1038 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
1039 req2.setHeader("Cache-Control", "only-if-cached");
1040
1041 execute(req1);
1042 final ClassicHttpResponse result = execute(req2);
1043
1044 assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, result.getCode());
1045 }
1046
1047 @Test
1048 void returnsStaleCacheEntryWithOnlyIfCachedAndMaxStale() throws Exception {
1049
1050 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1051 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1052 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1053 resp1.setHeader("Cache-Control","max-age=5");
1054
1055 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1056
1057 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
1058 req2.setHeader("Cache-Control", "max-stale=20, only-if-cached");
1059
1060 execute(req1);
1061 final ClassicHttpResponse result = execute(req2);
1062
1063 assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result));
1064 }
1065
1066 @Test
1067 void issues304EvenWithWeakETag() throws Exception {
1068 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
1069 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
1070 resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
1071 resp1.setHeader("Cache-Control", "max-age=300");
1072 resp1.setHeader("ETag","W/\"weak-sauce\"");
1073
1074 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
1075
1076 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
1077 req2.setHeader("If-None-Match","W/\"weak-sauce\"");
1078
1079 execute(req1);
1080 final ClassicHttpResponse result = execute(req2);
1081
1082 assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
1083 }
1084
1085 }