View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
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"}) // test code
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 }