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.InputStream;
31  import java.util.Collections;
32  import java.util.Map;
33  
34  import org.apache.hc.client5.http.AuthenticationStrategy;
35  import org.apache.hc.client5.http.HttpRoute;
36  import org.apache.hc.client5.http.RouteInfo;
37  import org.apache.hc.client5.http.auth.AuthChallenge;
38  import org.apache.hc.client5.http.auth.AuthScheme;
39  import org.apache.hc.client5.http.auth.AuthScope;
40  import org.apache.hc.client5.http.auth.ChallengeType;
41  import org.apache.hc.client5.http.auth.StandardAuthScheme;
42  import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
43  import org.apache.hc.client5.http.classic.ExecChain;
44  import org.apache.hc.client5.http.classic.ExecRuntime;
45  import org.apache.hc.client5.http.classic.methods.HttpGet;
46  import org.apache.hc.client5.http.entity.EntityBuilder;
47  import org.apache.hc.client5.http.impl.TunnelRefusedException;
48  import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
49  import org.apache.hc.client5.http.impl.auth.BasicScheme;
50  import org.apache.hc.client5.http.protocol.HttpClientContext;
51  import org.apache.hc.core5.http.ClassicHttpRequest;
52  import org.apache.hc.core5.http.ClassicHttpResponse;
53  import org.apache.hc.core5.http.ConnectionReuseStrategy;
54  import org.apache.hc.core5.http.HttpException;
55  import org.apache.hc.core5.http.HttpHeaders;
56  import org.apache.hc.core5.http.HttpHost;
57  import org.apache.hc.core5.http.HttpRequest;
58  import org.apache.hc.core5.http.HttpResponse;
59  import org.apache.hc.core5.http.HttpVersion;
60  import org.apache.hc.core5.http.io.entity.StringEntity;
61  import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
62  import org.apache.hc.core5.http.protocol.HttpProcessor;
63  import org.junit.Assert;
64  import org.junit.Before;
65  import org.junit.Test;
66  import org.mockito.ArgumentCaptor;
67  import org.mockito.Mock;
68  import org.mockito.Mockito;
69  import org.mockito.MockitoAnnotations;
70  import org.mockito.invocation.InvocationOnMock;
71  import org.mockito.stubbing.Answer;
72  
73  @SuppressWarnings({"boxing","static-access"}) // test code
74  public class TestConnectExec {
75  
76      @Mock
77      private ConnectionReuseStrategy reuseStrategy;
78      @Mock
79      private HttpProcessor proxyHttpProcessor;
80      @Mock
81      private AuthenticationStrategy proxyAuthStrategy;
82      @Mock
83      private ExecRuntime execRuntime;
84      @Mock
85      private ExecChain execChain;
86  
87      private ConnectExec exec;
88      private HttpHost target;
89      private HttpHost proxy;
90  
91      @Before
92      public void setup() throws Exception {
93          MockitoAnnotations.initMocks(this);
94          exec = new ConnectExec(reuseStrategy, proxyHttpProcessor, proxyAuthStrategy);
95          target = new HttpHost("foo", 80);
96          proxy = new HttpHost("bar", 8888);
97      }
98  
99      @Test
100     public void testExecAcquireConnection() throws Exception {
101         final HttpRoute route = new HttpRoute(target);
102         final HttpClientContext context = new HttpClientContext();
103         final ClassicHttpRequest request = new HttpGet("http://bar/test");
104         final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
105         response.setEntity(EntityBuilder.create()
106                 .setStream(new ByteArrayInputStream(new byte[]{}))
107                 .build());
108         context.setUserToken("Blah");
109 
110         Mockito.when(execRuntime.isEndpointAcquired()).thenReturn(false);
111         Mockito.when(execRuntime.execute(
112                 Mockito.eq("test"),
113                 Mockito.same(request),
114                 Mockito.<HttpClientContext>any())).thenReturn(response);
115         Mockito.when(reuseStrategy.keepAlive(
116                 Mockito.same(request),
117                 Mockito.same(response),
118                 Mockito.<HttpClientContext>any())).thenReturn(false);
119         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
120         exec.execute(request, scope, execChain);
121         Mockito.verify(execRuntime).acquireEndpoint("test", route, "Blah", context);
122         Mockito.verify(execRuntime).connectEndpoint(context);
123     }
124 
125     @Test
126     public void testEstablishDirectRoute() throws Exception {
127         final HttpRoute route = new HttpRoute(target);
128         final HttpClientContext context = new HttpClientContext();
129         final ClassicHttpRequest request = new HttpGet("http://bar/test");
130 
131         final ConnectionState connectionState = new ConnectionState();
132         Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connectEndpoint(Mockito.<HttpClientContext>any());
133         Mockito.when(execRuntime.isEndpointConnected()).thenAnswer(connectionState.isConnectedAnswer());
134 
135         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
136         exec.execute(request, scope, execChain);
137 
138         Mockito.verify(execRuntime).connectEndpoint(context);
139         Mockito.verify(execRuntime, Mockito.never()).execute(
140                 Mockito.anyString(),
141                 Mockito.<ClassicHttpRequest>any(),
142                 Mockito.<HttpClientContext>any());
143     }
144 
145     @Test
146     public void testEstablishRouteDirectProxy() throws Exception {
147         final HttpRoute route = new HttpRoute(target, null, proxy, false);
148         final HttpClientContext context = new HttpClientContext();
149         final ClassicHttpRequest request = new HttpGet("http://bar/test");
150 
151         final ConnectionState connectionState = new ConnectionState();
152         Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connectEndpoint(Mockito.<HttpClientContext>any());
153         Mockito.when(execRuntime.isEndpointConnected()).thenAnswer(connectionState.isConnectedAnswer());
154 
155         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
156         exec.execute(request, scope, execChain);
157 
158         Mockito.verify(execRuntime).connectEndpoint(context);
159         Mockito.verify(execRuntime, Mockito.never()).execute(
160                 Mockito.anyString(),
161                 Mockito.<ClassicHttpRequest>any(),
162                 Mockito.<HttpClientContext>any());
163     }
164 
165     @Test
166     public void testEstablishRouteViaProxyTunnel() throws Exception {
167         final HttpRoute route = new HttpRoute(target, null, proxy, true);
168         final HttpClientContext context = new HttpClientContext();
169         final ClassicHttpRequest request = new HttpGet("http://bar/test");
170         final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
171 
172         final ConnectionState connectionState = new ConnectionState();
173         Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connectEndpoint(Mockito.<HttpClientContext>any());
174         Mockito.when(execRuntime.isEndpointConnected()).thenAnswer(connectionState.isConnectedAnswer());
175         Mockito.when(execRuntime.execute(
176                 Mockito.anyString(),
177                 Mockito.<ClassicHttpRequest>any(),
178                 Mockito.<HttpClientContext>any())).thenReturn(response);
179 
180         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
181         exec.execute(request, scope, execChain);
182 
183         Mockito.verify(execRuntime).connectEndpoint(context);
184         final ArgumentCaptor<ClassicHttpRequest> reqCaptor = ArgumentCaptor.forClass(ClassicHttpRequest.class);
185         Mockito.verify(execRuntime).execute(
186                 Mockito.anyString(),
187                 reqCaptor.capture(),
188                 Mockito.same(context));
189         final HttpRequest connect = reqCaptor.getValue();
190         Assert.assertNotNull(connect);
191         Assert.assertEquals("CONNECT", connect.getMethod());
192         Assert.assertEquals(HttpVersion.HTTP_1_1, connect.getVersion());
193         Assert.assertEquals("foo:80", connect.getRequestUri());
194     }
195 
196     @Test(expected = HttpException.class)
197     public void testEstablishRouteViaProxyTunnelUnexpectedResponse() throws Exception {
198         final HttpRoute route = new HttpRoute(target, null, proxy, true);
199         final HttpClientContext context = new HttpClientContext();
200         final ClassicHttpRequest request = new HttpGet("http://bar/test");
201         final ClassicHttpResponse response = new BasicClassicHttpResponse(101, "Lost");
202 
203         final ConnectionState connectionState = new ConnectionState();
204         Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connectEndpoint(Mockito.<HttpClientContext>any());
205         Mockito.when(execRuntime.isEndpointConnected()).thenAnswer(connectionState.isConnectedAnswer());
206         Mockito.when(execRuntime.execute(
207                 Mockito.anyString(),
208                 Mockito.<ClassicHttpRequest>any(),
209                 Mockito.<HttpClientContext>any())).thenReturn(response);
210 
211         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
212         exec.execute(request, scope, execChain);
213     }
214 
215     @Test(expected = HttpException.class)
216     public void testEstablishRouteViaProxyTunnelFailure() throws Exception {
217         final HttpRoute route = new HttpRoute(target, null, proxy, true);
218         final HttpClientContext context = new HttpClientContext();
219         final ClassicHttpRequest request = new HttpGet("http://bar/test");
220         final ClassicHttpResponse response = new BasicClassicHttpResponse(500, "Boom");
221         response.setEntity(new StringEntity("Ka-boom"));
222 
223         final ConnectionState connectionState = new ConnectionState();
224         Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connectEndpoint(Mockito.<HttpClientContext>any());
225         Mockito.when(execRuntime.isEndpointConnected()).thenAnswer(connectionState.isConnectedAnswer());
226         Mockito.when(execRuntime.execute(
227                 Mockito.anyString(),
228                 Mockito.<ClassicHttpRequest>any(),
229                 Mockito.<HttpClientContext>any())).thenReturn(response);
230 
231         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
232         try {
233             exec.execute(request, scope, execChain);
234         } catch (final TunnelRefusedException ex) {
235             Assert.assertEquals("Ka-boom", ex.getResponseMessage());
236             Mockito.verify(execRuntime, Mockito.atLeastOnce()).disconnectEndpoint();
237             Mockito.verify(execRuntime).discardEndpoint();
238             throw ex;
239         }
240     }
241 
242     @Test
243     public void testEstablishRouteViaProxyTunnelRetryOnAuthChallengePersistentConnection() throws Exception {
244         final HttpRoute route = new HttpRoute(target, null, proxy, true);
245         final HttpClientContext context = new HttpClientContext();
246         final ClassicHttpRequest request = new HttpGet("http://bar/test");
247         final ClassicHttpResponse response1 = new BasicClassicHttpResponse(407, "Huh?");
248         response1.setHeader(HttpHeaders.PROXY_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=test");
249         final InputStream inStream1 = Mockito.spy(new ByteArrayInputStream(new byte[] {1, 2, 3}));
250         response1.setEntity(EntityBuilder.create()
251                 .setStream(inStream1)
252                 .build());
253         final ClassicHttpResponse response2 = new BasicClassicHttpResponse(200, "OK");
254 
255         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
256         credentialsProvider.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials("user", "pass".toCharArray()));
257         context.setCredentialsProvider(credentialsProvider);
258 
259         final ConnectionState connectionState = new ConnectionState();
260         Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connectEndpoint(Mockito.<HttpClientContext>any());
261         Mockito.when(execRuntime.isEndpointConnected()).thenAnswer(connectionState.isConnectedAnswer());
262         Mockito.when(reuseStrategy.keepAlive(
263                 Mockito.<HttpRequest>any(),
264                 Mockito.<HttpResponse>any(),
265                 Mockito.<HttpClientContext>any())).thenReturn(Boolean.TRUE);
266         Mockito.when(execRuntime.execute(
267                 Mockito.anyString(),
268                 Mockito.<ClassicHttpRequest>any(),
269                 Mockito.<HttpClientContext>any())).thenReturn(response1, response2);
270 
271         Mockito.when(proxyAuthStrategy.select(
272                 Mockito.eq(ChallengeType.PROXY),
273                 Mockito.<Map<String, AuthChallenge>>any(),
274                 Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
275 
276         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
277         exec.execute(request, scope, execChain);
278 
279         Mockito.verify(execRuntime).connectEndpoint(context);
280         Mockito.verify(inStream1).close();
281     }
282 
283     @Test
284     public void testEstablishRouteViaProxyTunnelRetryOnAuthChallengeNonPersistentConnection() throws Exception {
285         final HttpRoute route = new HttpRoute(target, null, proxy, true);
286         final HttpClientContext context = new HttpClientContext();
287         final ClassicHttpRequest request = new HttpGet("http://bar/test");
288         final ClassicHttpResponse response1 = new BasicClassicHttpResponse(407, "Huh?");
289         response1.setHeader(HttpHeaders.PROXY_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=test");
290         final InputStream inStream1 = Mockito.spy(new ByteArrayInputStream(new byte[] {1, 2, 3}));
291         response1.setEntity(EntityBuilder.create()
292                 .setStream(inStream1)
293                 .build());
294         final ClassicHttpResponse response2 = new BasicClassicHttpResponse(200, "OK");
295 
296         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
297         credentialsProvider.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials("user", "pass".toCharArray()));
298         context.setCredentialsProvider(credentialsProvider);
299 
300         final ConnectionState connectionState = new ConnectionState();
301         Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connectEndpoint(Mockito.<HttpClientContext>any());
302         Mockito.when(execRuntime.isEndpointConnected()).thenAnswer(connectionState.isConnectedAnswer());
303         Mockito.when(reuseStrategy.keepAlive(
304                 Mockito.same(request),
305                 Mockito.<HttpResponse>any(),
306                 Mockito.<HttpClientContext>any())).thenReturn(Boolean.FALSE);
307         Mockito.when(execRuntime.execute(
308                 Mockito.anyString(),
309                 Mockito.<ClassicHttpRequest>any(),
310                 Mockito.<HttpClientContext>any())).thenReturn(response1, response2);
311 
312         Mockito.when(proxyAuthStrategy.select(
313                 Mockito.eq(ChallengeType.PROXY),
314                 Mockito.<Map<String, AuthChallenge>>any(),
315                 Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
316 
317         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
318         exec.execute(request, scope, execChain);
319 
320         Mockito.verify(execRuntime).connectEndpoint(context);
321         Mockito.verify(inStream1, Mockito.never()).close();
322         Mockito.verify(execRuntime, Mockito.atLeastOnce()).disconnectEndpoint();
323     }
324 
325     @Test(expected = HttpException.class)
326     public void testEstablishRouteViaProxyTunnelMultipleHops() throws Exception {
327         final HttpHost proxy1 = new HttpHost("this", 8888);
328         final HttpHost proxy2 = new HttpHost("that", 8888);
329         final HttpRoute route = new HttpRoute(target, null, new HttpHost[] {proxy1, proxy2},
330                 true, RouteInfo.TunnelType.TUNNELLED, RouteInfo.LayerType.LAYERED);
331         final HttpClientContext context = new HttpClientContext();
332         final ClassicHttpRequest request = new HttpGet("http://bar/test");
333 
334         final ConnectionState connectionState = new ConnectionState();
335         Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connectEndpoint(Mockito.<HttpClientContext>any());
336         Mockito.when(execRuntime.isEndpointConnected()).thenAnswer(connectionState.isConnectedAnswer());
337 
338         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
339         exec.execute(request, scope, execChain);
340     }
341 
342     static class ConnectionState {
343 
344         private boolean connected;
345 
346         public Answer connectAnswer() {
347 
348             return new Answer() {
349 
350                 @Override
351                 public Object answer(final InvocationOnMock invocationOnMock) throws Throwable {
352                     connected = true;
353                     return null;
354                 }
355 
356             };
357         }
358 
359         public Answer<Boolean> isConnectedAnswer() {
360 
361             return new Answer<Boolean>() {
362 
363                 @Override
364                 public Boolean answer(final InvocationOnMock invocationOnMock) throws Throwable {
365                     return connected;
366                 }
367 
368             };
369 
370         }
371     }
372 
373 }