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
30 import static org.junit.jupiter.api.Assertions.assertEquals;
31 import static org.junit.jupiter.api.Assertions.assertTrue;
32
33 import java.io.ByteArrayInputStream;
34 import java.io.IOException;
35 import java.time.Instant;
36 import java.util.concurrent.ScheduledExecutorService;
37 import java.util.concurrent.ScheduledThreadPoolExecutor;
38
39 import org.apache.hc.client5.http.HttpRoute;
40 import org.apache.hc.client5.http.cache.HttpCacheContext;
41 import org.apache.hc.client5.http.classic.ExecChain;
42 import org.apache.hc.client5.http.classic.ExecRuntime;
43 import org.apache.hc.core5.http.ClassicHttpRequest;
44 import org.apache.hc.core5.http.ClassicHttpResponse;
45 import org.apache.hc.core5.http.HttpEntity;
46 import org.apache.hc.core5.http.HttpException;
47 import org.apache.hc.core5.http.HttpHost;
48 import org.apache.hc.core5.http.HttpStatus;
49 import org.apache.hc.core5.http.io.entity.InputStreamEntity;
50 import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
51 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
52 import org.junit.jupiter.api.AfterEach;
53 import org.junit.jupiter.api.BeforeEach;
54 import org.junit.jupiter.api.Test;
55 import org.mockito.Mock;
56 import org.mockito.Mockito;
57 import org.mockito.MockitoAnnotations;
58
59
60
61
62
63
64 public class TestRFC5861Compliance {
65
66 static final int MAX_BYTES = 1024;
67 static final int MAX_ENTRIES = 100;
68 static final int ENTITY_LENGTH = 128;
69
70 HttpHost host;
71 HttpRoute route;
72 HttpEntity body;
73 HttpCacheContext context;
74 @Mock
75 ExecChain mockExecChain;
76 @Mock
77 ExecRuntime mockExecRuntime;
78 ClassicHttpRequest request;
79 ClassicHttpResponse originResponse;
80 CacheConfig config;
81 CachingExec impl;
82 HttpCache cache;
83 ScheduledExecutorService executorService;
84
85 @BeforeEach
86 public void setUp() throws Exception {
87 MockitoAnnotations.openMocks(this);
88
89 host = new HttpHost("foo.example.com", 80);
90
91 route = new HttpRoute(host);
92
93 body = HttpTestUtils.makeBody(ENTITY_LENGTH);
94
95 request = new BasicClassicHttpRequest("GET", "/foo");
96
97 context = HttpCacheContext.create();
98
99 originResponse = HttpTestUtils.make200Response();
100
101 config = CacheConfig.custom()
102 .setMaxCacheEntries(MAX_ENTRIES)
103 .setMaxObjectSize(MAX_BYTES)
104 .build();
105
106 cache = new BasicHttpCache(config);
107 impl = new CachingExec(cache, null, config);
108
109 executorService = new ScheduledThreadPoolExecutor(1);
110
111 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
112 Mockito.when(mockExecRuntime.fork(null)).thenReturn(mockExecRuntime);
113 }
114
115 @AfterEach
116 public void cleanup() {
117 executorService.shutdownNow();
118 }
119
120 public ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
121 return impl.execute(
122 ClassicRequestBuilder.copy(request).build(),
123 new ExecChain.Scope("test", route, request, mockExecRuntime, context),
124 mockExecChain);
125 }
126
127 @Test
128 public void testConsumesErrorResponseWhenServingStale()
129 throws Exception{
130 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
131 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
132 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
133 "public, max-age=5, stale-if-error=60");
134
135 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
136 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
137 final byte[] body101 = HttpTestUtils.makeRandomBytes(101);
138 final ByteArrayInputStream buf = new ByteArrayInputStream(body101);
139 final ConsumableInputStream cis = new ConsumableInputStream(buf);
140 final HttpEntity entity = new InputStreamEntity(cis, 101, null);
141 resp2.setEntity(entity);
142
143 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
144
145 execute(req1);
146
147 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
148
149 execute(req2);
150
151 assertTrue(cis.wasClosed());
152 }
153
154 @Test
155 public void testStaleIfErrorInResponseYieldsToMustRevalidate()
156 throws Exception{
157 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
158 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
159 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
160 "public, max-age=5, stale-if-error=60, must-revalidate");
161
162 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
163 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
164
165 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
166
167 execute(req1);
168
169 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
170
171 final ClassicHttpResponse result = execute(req2);
172
173 assertTrue(HttpStatus.SC_OK != result.getCode());
174 }
175
176 @Test
177 public void testStaleIfErrorInResponseYieldsToProxyRevalidateForSharedCache()
178 throws Exception{
179 assertTrue(config.isSharedCache());
180 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
181 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
182 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
183 "public, max-age=5, stale-if-error=60, proxy-revalidate");
184
185 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
186 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
187
188 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
189
190 execute(req1);
191
192 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
193
194 final ClassicHttpResponse result = execute(req2);
195
196 assertTrue(HttpStatus.SC_OK != result.getCode());
197 }
198
199 @Test
200 public void testStaleIfErrorInResponseYieldsToExplicitFreshnessRequest()
201 throws Exception{
202 final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
203 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
204 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
205 "public, max-age=5, stale-if-error=60");
206
207 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
208 req2.setHeader("Cache-Control","min-fresh=2");
209 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
210
211 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
212
213 execute(req1);
214
215 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
216
217 final ClassicHttpResponse result = execute(req2);
218
219 assertTrue(HttpStatus.SC_OK != result.getCode());
220 }
221
222 @Test
223 public void testStaleIfErrorInResponseIsFalseReturnsError()
224 throws Exception{
225 final Instant now = Instant.now();
226 final Instant tenSecondsAgo = now.minusSeconds(10);
227 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
228 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
229 "public, max-age=5, stale-if-error=2");
230
231 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
232 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
233
234 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
235
236 execute(req1);
237
238 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
239
240 final ClassicHttpResponse result = execute(req2);
241
242 assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
243 result.getCode());
244 }
245
246 @Test
247 public void testStaleIfErrorInRequestIsFalseReturnsError()
248 throws Exception{
249 final Instant now = Instant.now();
250 final Instant tenSecondsAgo = now.minusSeconds(10);
251 final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
252 final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
253 "public, max-age=5");
254
255 final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
256 req2.setHeader("Cache-Control","stale-if-error=2");
257 final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
258
259 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
260
261 execute(req1);
262
263 Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
264
265 final ClassicHttpResponse result = execute(req2);
266
267 assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, result.getCode());
268 }
269
270 }