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.IOException;
31  import java.io.InterruptedIOException;
32  
33  import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
34  import org.apache.hc.client5.http.HttpRoute;
35  import org.apache.hc.client5.http.UserTokenHandler;
36  import org.apache.hc.client5.http.classic.ExecChain;
37  import org.apache.hc.client5.http.classic.ExecRuntime;
38  import org.apache.hc.client5.http.classic.methods.HttpGet;
39  import org.apache.hc.client5.http.entity.EntityBuilder;
40  import org.apache.hc.client5.http.impl.ConnectionShutdownException;
41  import org.apache.hc.client5.http.io.HttpClientConnectionManager;
42  import org.apache.hc.client5.http.protocol.HttpClientContext;
43  import org.apache.hc.core5.http.ClassicHttpRequest;
44  import org.apache.hc.core5.http.ClassicHttpResponse;
45  import org.apache.hc.core5.http.ConnectionReuseStrategy;
46  import org.apache.hc.core5.http.HttpException;
47  import org.apache.hc.core5.http.HttpHost;
48  import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
49  import org.apache.hc.core5.util.TimeValue;
50  import org.junit.Assert;
51  import org.junit.Before;
52  import org.junit.Test;
53  import org.mockito.Mock;
54  import org.mockito.Mockito;
55  import org.mockito.MockitoAnnotations;
56  import org.mockito.invocation.InvocationOnMock;
57  import org.mockito.stubbing.Answer;
58  
59  @SuppressWarnings({"boxing","static-access"}) // test code
60  public class TestMainClientExec {
61  
62      @Mock
63      private HttpClientConnectionManager connectionManager;
64      @Mock
65      private ConnectionReuseStrategy reuseStrategy;
66      @Mock
67      private ConnectionKeepAliveStrategy keepAliveStrategy;
68      @Mock
69      private UserTokenHandler userTokenHandler;
70      @Mock
71      private ExecRuntime endpoint;
72  
73      private MainClientExec mainClientExec;
74      private HttpHost target;
75  
76      @Before
77      public void setup() throws Exception {
78          MockitoAnnotations.initMocks(this);
79          mainClientExec = new MainClientExec(connectionManager, reuseStrategy, keepAliveStrategy, userTokenHandler);
80          target = new HttpHost("foo", 80);
81      }
82  
83      @Test
84      public void testExecRequestNonPersistentConnection() throws Exception {
85          final HttpRoute route = new HttpRoute(target);
86          final HttpClientContext context = new HttpClientContext();
87          final ClassicHttpRequest request = new HttpGet("http://bar/test");
88          final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
89          response.setEntity(EntityBuilder.create()
90                  .setStream(new ByteArrayInputStream(new byte[]{}))
91                  .build());
92  
93          Mockito.when(endpoint.isEndpointAcquired()).thenReturn(false);
94          Mockito.when(endpoint.execute(
95                  Mockito.anyString(),
96                  Mockito.same(request),
97                  Mockito.<HttpClientContext>any())).thenReturn(response);
98          Mockito.when(reuseStrategy.keepAlive(
99                  Mockito.same(request),
100                 Mockito.same(response),
101                 Mockito.<HttpClientContext>any())).thenReturn(false);
102 
103         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
104         final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
105         Mockito.verify(endpoint).execute("test", request, context);
106         Mockito.verify(endpoint, Mockito.times(1)).markConnectionNonReusable();
107         Mockito.verify(endpoint, Mockito.never()).releaseEndpoint();
108 
109         Assert.assertNull(context.getUserToken());
110         Assert.assertNotNull(finalResponse);
111         Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
112     }
113 
114     @Test
115     public void testExecRequestNonPersistentConnectionNoResponseEntity() throws Exception {
116         final HttpRoute route = new HttpRoute(target);
117         final HttpClientContext context = new HttpClientContext();
118         final ClassicHttpRequest request = new HttpGet("http://bar/test");
119         final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
120         response.setEntity(null);
121 
122         Mockito.when(endpoint.isEndpointAcquired()).thenReturn(false);
123         Mockito.when(endpoint.execute(
124                 Mockito.anyString(),
125                 Mockito.same(request),
126                 Mockito.<HttpClientContext>any())).thenReturn(response);
127         Mockito.when(reuseStrategy.keepAlive(
128                 Mockito.same(request),
129                 Mockito.same(response),
130                 Mockito.<HttpClientContext>any())).thenReturn(false);
131 
132         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
133         final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
134 
135         Mockito.verify(endpoint).execute("test", request, context);
136         Mockito.verify(endpoint).markConnectionNonReusable();
137         Mockito.verify(endpoint).releaseEndpoint();
138 
139         Assert.assertNotNull(finalResponse);
140         Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
141     }
142 
143     @Test
144     public void testExecRequestPersistentConnection() throws Exception {
145         final HttpRoute route = new HttpRoute(target);
146         final HttpClientContext context = new HttpClientContext();
147         final ClassicHttpRequest request = new HttpGet("http://bar/test");
148         final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
149         // The entity is streaming
150         response.setEntity(EntityBuilder.create()
151                 .setStream(new ByteArrayInputStream(new byte[]{}))
152                 .build());
153 
154         final ConnectionState connectionState = new ConnectionState();
155         Mockito.doAnswer(connectionState.connectAnswer()).when(endpoint).connectEndpoint(Mockito.<HttpClientContext>any());
156         Mockito.when(endpoint.isEndpointConnected()).thenAnswer(connectionState.isConnectedAnswer());
157         Mockito.when(endpoint.execute(
158                 Mockito.anyString(),
159                 Mockito.same(request),
160                 Mockito.<HttpClientContext>any())).thenReturn(response);
161         Mockito.when(reuseStrategy.keepAlive(
162                 Mockito.same(request),
163                 Mockito.same(response),
164                 Mockito.<HttpClientContext>any())).thenReturn(true);
165         Mockito.when(keepAliveStrategy.getKeepAliveDuration(
166                 Mockito.same(response),
167                 Mockito.<HttpClientContext>any())).thenReturn(TimeValue.ofMilliseconds(678L));
168 
169         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
170         final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
171 
172         Mockito.verify(endpoint).execute("test", request, context);
173         Mockito.verify(endpoint).markConnectionReusable(null, TimeValue.ofMilliseconds(678L));
174         Mockito.verify(endpoint, Mockito.never()).releaseEndpoint();
175 
176         Assert.assertNotNull(finalResponse);
177         Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
178     }
179 
180     @Test
181     public void testExecRequestPersistentConnectionNoResponseEntity() throws Exception {
182         final HttpRoute route = new HttpRoute(target);
183         final HttpClientContext context = new HttpClientContext();
184         final ClassicHttpRequest request = new HttpGet("http://bar/test");
185         final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
186 
187         final ConnectionState connectionState = new ConnectionState();
188         Mockito.doAnswer(connectionState.connectAnswer()).when(endpoint).connectEndpoint(Mockito.<HttpClientContext>any());
189         Mockito.when(endpoint.isEndpointConnected()).thenAnswer(connectionState.isConnectedAnswer());
190         Mockito.when(endpoint.execute(
191                 Mockito.anyString(),
192                 Mockito.same(request),
193                 Mockito.<HttpClientContext>any())).thenReturn(response);
194         Mockito.when(reuseStrategy.keepAlive(
195                 Mockito.same(request),
196                 Mockito.same(response),
197                 Mockito.<HttpClientContext>any())).thenReturn(true);
198         Mockito.when(keepAliveStrategy.getKeepAliveDuration(
199                 Mockito.same(response),
200                 Mockito.<HttpClientContext>any())).thenReturn(TimeValue.ofMilliseconds(678L));
201 
202         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
203         final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
204 
205         Mockito.verify(endpoint).execute("test", request, context);
206         Mockito.verify(endpoint).releaseEndpoint();
207 
208         Assert.assertNotNull(finalResponse);
209         Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
210     }
211 
212     @Test
213     public void testExecRequestConnectionRelease() throws Exception {
214         final HttpRoute route = new HttpRoute(target);
215         final HttpClientContext context = new HttpClientContext();
216         final ClassicHttpRequest request = new HttpGet("http://bar/test");
217         final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
218         // The entity is streaming
219         response.setEntity(EntityBuilder.create()
220                 .setStream(new ByteArrayInputStream(new byte[]{}))
221                 .build());
222 
223         final ConnectionState connectionState = new ConnectionState();
224         Mockito.doAnswer(connectionState.connectAnswer()).when(endpoint).connectEndpoint(Mockito.<HttpClientContext>any());
225         Mockito.when(endpoint.isEndpointConnected()).thenAnswer(connectionState.isConnectedAnswer());
226         Mockito.when(endpoint.execute(
227                 Mockito.anyString(),
228                 Mockito.same(request),
229                 Mockito.<HttpClientContext>any())).thenReturn(response);
230         Mockito.when(reuseStrategy.keepAlive(
231                 Mockito.same(request),
232                 Mockito.same(response),
233                 Mockito.<HttpClientContext>any())).thenReturn(Boolean.FALSE);
234 
235         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
236         final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
237         Mockito.verify(endpoint, Mockito.times(1)).execute("test", request, context);
238         Mockito.verify(endpoint, Mockito.never()).disconnectEndpoint();
239         Mockito.verify(endpoint, Mockito.never()).releaseEndpoint();
240 
241         Assert.assertNotNull(finalResponse);
242         Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
243         finalResponse.close();
244 
245         Mockito.verify(endpoint).disconnectEndpoint();
246         Mockito.verify(endpoint).discardEndpoint();
247     }
248 
249     @Test(expected=InterruptedIOException.class)
250     public void testExecConnectionShutDown() throws Exception {
251         final HttpRoute route = new HttpRoute(target);
252         final HttpClientContext context = new HttpClientContext();
253         final ClassicHttpRequest request = new HttpGet("http://bar/test");
254 
255         Mockito.when(endpoint.execute(
256                 Mockito.anyString(),
257                 Mockito.same(request),
258                 Mockito.<HttpClientContext>any())).thenThrow(new ConnectionShutdownException());
259 
260         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
261         try {
262             mainClientExec.execute(request, scope, null);
263         } catch (final Exception ex) {
264             Mockito.verify(endpoint).discardEndpoint();
265             throw ex;
266         }
267     }
268 
269     @Test(expected=RuntimeException.class)
270     public void testExecRuntimeException() throws Exception {
271         final HttpRoute route = new HttpRoute(target);
272         final HttpClientContext context = new HttpClientContext();
273         final ClassicHttpRequest request = new HttpGet("http://bar/test");
274 
275         Mockito.when(endpoint.execute(
276                 Mockito.anyString(),
277                 Mockito.same(request),
278                 Mockito.<HttpClientContext>any())).thenThrow(new RuntimeException("Ka-boom"));
279 
280         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
281         try {
282             mainClientExec.execute(request, scope, null);
283         } catch (final Exception ex) {
284             Mockito.verify(endpoint).discardEndpoint();
285             throw ex;
286         }
287     }
288 
289     @Test(expected=HttpException.class)
290     public void testExecHttpException() throws Exception {
291         final HttpRoute route = new HttpRoute(target);
292         final HttpClientContext context = new HttpClientContext();
293         final ClassicHttpRequest request = new HttpGet("http://bar/test");
294 
295         Mockito.when(endpoint.execute(
296                 Mockito.anyString(),
297                 Mockito.same(request),
298                 Mockito.<HttpClientContext>any())).thenThrow(new HttpException("Ka-boom"));
299 
300         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
301         try {
302             mainClientExec.execute(request, scope, null);
303         } catch (final Exception ex) {
304             Mockito.verify(endpoint).discardEndpoint();
305             throw ex;
306         }
307     }
308 
309     @Test(expected=IOException.class)
310     public void testExecIOException() throws Exception {
311         final HttpRoute route = new HttpRoute(target);
312         final HttpClientContext context = new HttpClientContext();
313         final ClassicHttpRequest request = new HttpGet("http://bar/test");
314 
315         Mockito.when(endpoint.execute(
316                 Mockito.anyString(),
317                 Mockito.same(request),
318                 Mockito.<HttpClientContext>any())).thenThrow(new IOException("Ka-boom"));
319 
320         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
321         try {
322             mainClientExec.execute(request, scope, null);
323         } catch (final Exception ex) {
324             Mockito.verify(endpoint).discardEndpoint();
325             throw ex;
326         }
327     }
328 
329     static class ConnectionState {
330 
331         private boolean connected;
332 
333         public Answer connectAnswer() {
334 
335             return new Answer() {
336 
337                 @Override
338                 public Object answer(final InvocationOnMock invocationOnMock) throws Throwable {
339                     connected = true;
340                     return null;
341                 }
342 
343             };
344         }
345 
346         public Answer<Boolean> isConnectedAnswer() {
347 
348             return new Answer<Boolean>() {
349 
350                 @Override
351                 public Boolean answer(final InvocationOnMock invocationOnMock) throws Throwable {
352                     return connected;
353                 }
354 
355             };
356 
357         }
358     }
359 
360 }