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.classic;
28
29 import java.io.ByteArrayInputStream;
30 import java.io.ByteArrayOutputStream;
31 import java.io.IOException;
32
33 import org.apache.hc.client5.http.HttpRequestRetryStrategy;
34 import org.apache.hc.client5.http.HttpRoute;
35 import org.apache.hc.client5.http.classic.ExecChain;
36 import org.apache.hc.client5.http.classic.ExecRuntime;
37 import org.apache.hc.client5.http.classic.methods.HttpGet;
38 import org.apache.hc.client5.http.classic.methods.HttpPost;
39 import org.apache.hc.client5.http.config.RequestConfig;
40 import org.apache.hc.client5.http.entity.EntityBuilder;
41 import org.apache.hc.client5.http.protocol.HttpClientContext;
42 import org.apache.hc.core5.http.ClassicHttpRequest;
43 import org.apache.hc.core5.http.ClassicHttpResponse;
44 import org.apache.hc.core5.http.Header;
45 import org.apache.hc.core5.http.HttpHost;
46 import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
47 import org.apache.hc.core5.util.TimeValue;
48 import org.apache.hc.core5.util.Timeout;
49 import org.junit.jupiter.api.Assertions;
50 import org.junit.jupiter.api.BeforeEach;
51 import org.junit.jupiter.api.Test;
52 import org.mockito.Mock;
53 import org.mockito.Mockito;
54 import org.mockito.MockitoAnnotations;
55
56 @SuppressWarnings({"boxing","static-access"})
57 public class TestHttpRequestRetryExec {
58
59 @Mock
60 private HttpRequestRetryStrategy retryStrategy;
61 @Mock
62 private ExecChain chain;
63 @Mock
64 private ExecRuntime endpoint;
65 @Mock
66 private TimeValue nextInterval;
67
68 private HttpRequestRetryExec retryExec;
69 private HttpHost target;
70
71 @BeforeEach
72 public void setup() throws Exception {
73 MockitoAnnotations.openMocks(this);
74 retryExec = new HttpRequestRetryExec(retryStrategy);
75 target = new HttpHost("localhost", 80);
76 }
77
78
79 @Test
80 public void testFundamentals1() throws Exception {
81 final HttpRoute route = new HttpRoute(target);
82 final HttpGet request = new HttpGet("/test");
83 final HttpClientContext context = HttpClientContext.create();
84
85 final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
86
87 Mockito.when(chain.proceed(
88 Mockito.same(request),
89 Mockito.any())).thenReturn(response);
90 Mockito.when(retryStrategy.retryRequest(
91 Mockito.any(),
92 Mockito.anyInt(),
93 Mockito.any())).thenReturn(Boolean.TRUE, Boolean.FALSE);
94 Mockito.when(retryStrategy.getRetryInterval(
95 Mockito.any(),
96 Mockito.anyInt(),
97 Mockito.any())).thenReturn(TimeValue.ZERO_MILLISECONDS);
98
99 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
100 retryExec.execute(request, scope, chain);
101
102 Mockito.verify(chain, Mockito.times(2)).proceed(
103 Mockito.any(),
104 Mockito.same(scope));
105 Mockito.verify(response, Mockito.times(1)).close();
106 }
107
108 @Test
109 public void testRetrySleepOnIOException() throws Exception {
110 final HttpRoute route = new HttpRoute(target);
111 final HttpGet request = new HttpGet("/test");
112 final HttpClientContext context = HttpClientContext.create();
113
114 final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
115
116 Mockito.when(chain.proceed(
117 Mockito.same(request),
118 Mockito.any())).thenThrow(new IOException("Ka-boom"));
119 Mockito.when(retryStrategy.retryRequest(
120 Mockito.any(),
121 Mockito.any(),
122 Mockito.anyInt(),
123 Mockito.any())).thenReturn(Boolean.TRUE, Boolean.FALSE);
124 Mockito.when(retryStrategy.getRetryInterval(
125 Mockito.any(),
126 Mockito.any(),
127 Mockito.anyInt(),
128 Mockito.any())).thenReturn(nextInterval);
129 Mockito.when(nextInterval.getDuration()).thenReturn(100L);
130 Mockito.when(nextInterval.compareTo(Mockito.any())).thenReturn(-1);
131
132 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
133 retryExec.execute(request, scope, chain);
134
135 Mockito.verify(chain, Mockito.times(2)).proceed(
136 Mockito.any(),
137 Mockito.same(scope));
138 Mockito.verify(nextInterval, Mockito.times(1)).sleep();
139 }
140
141 @Test
142 public void testRetryIntervalGreaterResponseTimeout() throws Exception {
143 final HttpRoute route = new HttpRoute(target);
144 final HttpGet request = new HttpGet("/test");
145 final HttpClientContext context = HttpClientContext.create();
146 context.setRequestConfig(RequestConfig.custom()
147 .setResponseTimeout(Timeout.ofSeconds(3))
148 .build());
149
150 final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
151
152 Mockito.when(chain.proceed(
153 Mockito.same(request),
154 Mockito.any())).thenReturn(response);
155 Mockito.when(retryStrategy.retryRequest(
156 Mockito.any(),
157 Mockito.anyInt(),
158 Mockito.any())).thenReturn(Boolean.TRUE, Boolean.FALSE);
159 Mockito.when(retryStrategy.getRetryInterval(
160 Mockito.any(),
161 Mockito.anyInt(),
162 Mockito.any())).thenReturn(TimeValue.ofSeconds(5));
163
164 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
165 retryExec.execute(request, scope, chain);
166
167 Mockito.verify(chain, Mockito.times(1)).proceed(
168 Mockito.any(),
169 Mockito.same(scope));
170 Mockito.verify(response, Mockito.times(0)).close();
171 }
172
173 @Test
174 public void testRetryIntervalResponseTimeoutNull() throws Exception {
175 final HttpRoute route = new HttpRoute(target);
176 final HttpGet request = new HttpGet("/test");
177 final HttpClientContext context = HttpClientContext.create();
178 context.setRequestConfig(RequestConfig.custom()
179 .setResponseTimeout(null)
180 .build());
181
182 final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
183
184 Mockito.when(chain.proceed(
185 Mockito.same(request),
186 Mockito.any())).thenReturn(response);
187 Mockito.when(retryStrategy.retryRequest(
188 Mockito.any(),
189 Mockito.anyInt(),
190 Mockito.any())).thenReturn(Boolean.TRUE, Boolean.FALSE);
191 Mockito.when(retryStrategy.getRetryInterval(
192 Mockito.any(),
193 Mockito.anyInt(),
194 Mockito.any())).thenReturn(TimeValue.ofSeconds(1));
195
196 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
197 retryExec.execute(request, scope, chain);
198
199 Mockito.verify(chain, Mockito.times(2)).proceed(
200 Mockito.any(),
201 Mockito.same(scope));
202 Mockito.verify(response, Mockito.times(1)).close();
203 }
204
205 @Test
206 public void testStrategyRuntimeException() throws Exception {
207 final HttpRoute route = new HttpRoute(target);
208 final ClassicHttpRequest request = new HttpGet("/test");
209 final HttpClientContext context = HttpClientContext.create();
210
211 final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
212 Mockito.when(chain.proceed(
213 Mockito.any(),
214 Mockito.any())).thenReturn(response);
215 Mockito.doThrow(new RuntimeException("Ooopsie")).when(retryStrategy).retryRequest(
216 Mockito.any(),
217 Mockito.anyInt(),
218 Mockito.any());
219 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
220 Assertions.assertThrows(RuntimeException.class, () ->
221 retryExec.execute(request, scope, chain));
222 Mockito.verify(response).close();
223 }
224
225 @Test
226 public void testNonRepeatableEntityResponseReturnedImmediately() throws Exception {
227 final HttpRoute route = new HttpRoute(target);
228
229 final HttpPost request = new HttpPost("/test");
230 request.setEntity(EntityBuilder.create()
231 .setStream(new ByteArrayInputStream(new byte[]{}))
232 .build());
233 final HttpClientContext context = HttpClientContext.create();
234
235 final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
236 Mockito.when(chain.proceed(
237 Mockito.any(),
238 Mockito.any())).thenReturn(response);
239
240 final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
241 final ClassicHttpResponse finalResponse = retryExec.execute(request, scope, chain);
242
243 Assertions.assertSame(response, finalResponse);
244 Mockito.verify(response, Mockito.times(0)).close();
245 }
246
247 @Test
248 public void testFundamentals2() throws Exception {
249 final HttpRoute route = new HttpRoute(target);
250 final HttpGet originalRequest = new HttpGet("/test");
251 originalRequest.addHeader("header", "this");
252 originalRequest.addHeader("header", "that");
253 final HttpClientContext context = HttpClientContext.create();
254
255 Mockito.when(chain.proceed(
256 Mockito.any(),
257 Mockito.any())).thenAnswer(invocationOnMock -> {
258 final Object[] args = invocationOnMock.getArguments();
259 final ClassicHttpRequest wrapper = (ClassicHttpRequest) args[0];
260 final Header[] headers = wrapper.getHeaders();
261 Assertions.assertEquals(2, headers.length);
262 Assertions.assertEquals("this", headers[0].getValue());
263 Assertions.assertEquals("that", headers[1].getValue());
264 wrapper.addHeader("Cookie", "monster");
265 throw new IOException("Ka-boom");
266 });
267 Mockito.when(retryStrategy.retryRequest(
268 Mockito.any(),
269 Mockito.any(),
270 Mockito.eq(1),
271 Mockito.any())).thenReturn(Boolean.TRUE);
272 final ExecChain.Scope scope = new ExecChain.Scope("test", route, originalRequest, endpoint, context);
273 final ClassicHttpRequest request = ClassicRequestBuilder.copy(originalRequest).build();
274 Assertions.assertThrows(IOException.class, () ->
275 retryExec.execute(request, scope, chain));
276 Mockito.verify(chain, Mockito.times(2)).proceed(
277 Mockito.any(),
278 Mockito.same(scope));
279 }
280
281
282 @Test
283 public void testAbortedRequest() throws Exception {
284 final HttpRoute route = new HttpRoute(target);
285 final HttpGet originalRequest = new HttpGet("/test");
286 final HttpClientContext context = HttpClientContext.create();
287
288 Mockito.when(chain.proceed(
289 Mockito.any(),
290 Mockito.any())).thenThrow(new IOException("Ka-boom"));
291 Mockito.when(endpoint.isExecutionAborted()).thenReturn(true);
292
293 final ExecChain.Scope scope = new ExecChain.Scope("test", route, originalRequest, endpoint, context);
294 final ClassicHttpRequest request = ClassicRequestBuilder.copy(originalRequest).build();
295 Assertions.assertThrows(IOException.class, () ->
296 retryExec.execute(request, scope, chain));
297 Mockito.verify(chain, Mockito.times(1)).proceed(
298 Mockito.same(request),
299 Mockito.same(scope));
300 Mockito.verify(retryStrategy, Mockito.never()).retryRequest(
301 Mockito.any(),
302 Mockito.any(),
303 Mockito.anyInt(),
304 Mockito.any());
305 }
306
307 @Test
308 public void testNonRepeatableRequest() throws Exception {
309 final HttpRoute route = new HttpRoute(target);
310 final HttpPost originalRequest = new HttpPost("/test");
311 originalRequest.setEntity(EntityBuilder.create()
312 .setStream(new ByteArrayInputStream(new byte[]{}))
313 .build());
314 final HttpClientContext context = HttpClientContext.create();
315
316 Mockito.when(chain.proceed(
317 Mockito.any(),
318 Mockito.any())).thenAnswer(invocationOnMock -> {
319 final Object[] args = invocationOnMock.getArguments();
320 final ClassicHttpRequest req = (ClassicHttpRequest) args[0];
321 req.getEntity().writeTo(new ByteArrayOutputStream());
322 throw new IOException("Ka-boom");
323 });
324 final ExecChain.Scope scope = new ExecChain.Scope("test", route, originalRequest, endpoint, context);
325 final ClassicHttpRequest request = ClassicRequestBuilder.copy(originalRequest).build();
326 Assertions.assertThrows(IOException.class, () ->
327 retryExec.execute(request, scope, chain));
328 Mockito.verify(chain, Mockito.times(1)).proceed(
329 Mockito.same(request),
330 Mockito.same(scope));
331 }
332
333 }