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