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.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.StandardAuthScheme;
40 import org.apache.hc.client5.http.auth.AuthScope;
41 import org.apache.hc.client5.http.auth.ChallengeType;
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"})
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).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.same(request),
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).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 }