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