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.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"}) // test code
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 }