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
28 package org.apache.hc.client5.http.impl.classic;
29
30 import java.io.IOException;
31
32 import org.apache.hc.client5.http.AuthenticationStrategy;
33 import org.apache.hc.client5.http.EndpointInfo;
34 import org.apache.hc.client5.http.HttpRoute;
35 import org.apache.hc.client5.http.RouteTracker;
36 import org.apache.hc.client5.http.SchemePortResolver;
37 import org.apache.hc.client5.http.auth.AuthExchange;
38 import org.apache.hc.client5.http.auth.ChallengeType;
39 import org.apache.hc.client5.http.classic.ExecChain;
40 import org.apache.hc.client5.http.classic.ExecChainHandler;
41 import org.apache.hc.client5.http.classic.ExecRuntime;
42 import org.apache.hc.client5.http.config.RequestConfig;
43 import org.apache.hc.client5.http.impl.auth.AuthCacheKeeper;
44 import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
45 import org.apache.hc.client5.http.impl.routing.BasicRouteDirector;
46 import org.apache.hc.client5.http.protocol.HttpClientContext;
47 import org.apache.hc.client5.http.routing.HttpRouteDirector;
48 import org.apache.hc.core5.annotation.Contract;
49 import org.apache.hc.core5.annotation.Internal;
50 import org.apache.hc.core5.annotation.ThreadingBehavior;
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.ContentType;
55 import org.apache.hc.core5.http.HttpEntity;
56 import org.apache.hc.core5.http.HttpException;
57 import org.apache.hc.core5.http.HttpHeaders;
58 import org.apache.hc.core5.http.HttpHost;
59 import org.apache.hc.core5.http.HttpRequest;
60 import org.apache.hc.core5.http.HttpStatus;
61 import org.apache.hc.core5.http.HttpVersion;
62 import org.apache.hc.core5.http.Method;
63 import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
64 import org.apache.hc.core5.http.io.entity.EntityUtils;
65 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
66 import org.apache.hc.core5.http.message.StatusLine;
67 import org.apache.hc.core5.http.protocol.HttpProcessor;
68 import org.apache.hc.core5.util.Args;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
71
72
73
74
75
76
77
78
79 @Contract(threading = ThreadingBehavior.STATELESS)
80 @Internal
81 public final class ConnectExec implements ExecChainHandler {
82
83 private static final Logger LOG = LoggerFactory.getLogger(ConnectExec.class);
84
85 private final ConnectionReuseStrategy reuseStrategy;
86 private final HttpProcessor proxyHttpProcessor;
87 private final AuthenticationStrategy proxyAuthStrategy;
88 private final HttpAuthenticator authenticator;
89 private final AuthCacheKeeper authCacheKeeper;
90 private final HttpRouteDirector routeDirector;
91
92 public ConnectExec(
93 final ConnectionReuseStrategy reuseStrategy,
94 final HttpProcessor proxyHttpProcessor,
95 final AuthenticationStrategy proxyAuthStrategy,
96 final SchemePortResolver schemePortResolver,
97 final boolean authCachingDisabled) {
98 Args.notNull(reuseStrategy, "Connection reuse strategy");
99 Args.notNull(proxyHttpProcessor, "Proxy HTTP processor");
100 Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
101 this.reuseStrategy = reuseStrategy;
102 this.proxyHttpProcessor = proxyHttpProcessor;
103 this.proxyAuthStrategy = proxyAuthStrategy;
104 this.authenticator = new HttpAuthenticator();
105 this.authCacheKeeper = authCachingDisabled ? null : new AuthCacheKeeper(schemePortResolver);
106 this.routeDirector = BasicRouteDirector.INSTANCE;
107 }
108
109 @Override
110 public ClassicHttpResponse execute(
111 final ClassicHttpRequest request,
112 final ExecChain.Scope scope,
113 final ExecChain chain) throws IOException, HttpException {
114 Args.notNull(request, "HTTP request");
115 Args.notNull(scope, "Scope");
116
117 final String exchangeId = scope.exchangeId;
118 final HttpRoute route = scope.route;
119 final HttpClientContext context = scope.clientContext;
120 final ExecRuntime execRuntime = scope.execRuntime;
121
122 if (!execRuntime.isEndpointAcquired()) {
123 final Object userToken = context.getUserToken();
124 if (LOG.isDebugEnabled()) {
125 LOG.debug("{} acquiring connection with route {}", exchangeId, route);
126 }
127 execRuntime.acquireEndpoint(exchangeId, route, userToken, context);
128 }
129 try {
130 if (!execRuntime.isEndpointConnected()) {
131 if (LOG.isDebugEnabled()) {
132 LOG.debug("{} opening connection {}", exchangeId, route);
133 }
134
135 final RouteTracker tracker = new RouteTracker(route);
136 int step;
137 do {
138 final HttpRoute fact = tracker.toRoute();
139 step = this.routeDirector.nextStep(route, fact);
140
141 switch (step) {
142
143 case HttpRouteDirector.CONNECT_TARGET:
144 execRuntime.connectEndpoint(context);
145 tracker.connectTarget(route.isSecure());
146 break;
147 case HttpRouteDirector.CONNECT_PROXY:
148 execRuntime.connectEndpoint(context);
149 final HttpHost proxy = route.getProxyHost();
150 tracker.connectProxy(proxy, route.isSecure() && !route.isTunnelled());
151 break;
152 case HttpRouteDirector.TUNNEL_TARGET: {
153 final ClassicHttpResponse finalResponse = createTunnelToTarget(
154 exchangeId, route, request, execRuntime, context);
155 if (finalResponse != null) {
156 return finalResponse;
157 }
158 if (LOG.isDebugEnabled()) {
159 LOG.debug("{} tunnel to target created.", exchangeId);
160 }
161 tracker.tunnelTarget(false);
162 } break;
163
164 case HttpRouteDirector.TUNNEL_PROXY: {
165
166
167
168
169 final int hop = fact.getHopCount()-1;
170 final boolean secure = createTunnelToProxy(route, hop, context);
171 if (LOG.isDebugEnabled()) {
172 LOG.debug("{} tunnel to proxy created.", exchangeId);
173 }
174 tracker.tunnelProxy(route.getHopTarget(hop), secure);
175 } break;
176
177 case HttpRouteDirector.LAYER_PROTOCOL:
178 execRuntime.upgradeTls(context);
179 tracker.layerProtocol(route.isSecure());
180 break;
181
182 case HttpRouteDirector.UNREACHABLE:
183 throw new HttpException("Unable to establish route: " +
184 "planned = " + route + "; current = " + fact);
185 case HttpRouteDirector.COMPLETE:
186 break;
187 default:
188 throw new IllegalStateException("Unknown step indicator "
189 + step + " from RouteDirector.");
190 }
191
192 } while (step > HttpRouteDirector.COMPLETE);
193 }
194 final EndpointInfo endpointInfo = execRuntime.getEndpointInfo();
195 if (endpointInfo != null) {
196 context.setProtocolVersion(endpointInfo.getProtocol());
197 context.setSSLSession(endpointInfo.getSslSession());
198 }
199 return chain.proceed(request, scope);
200
201 } catch (final IOException | HttpException | RuntimeException ex) {
202 execRuntime.discardEndpoint();
203 throw ex;
204 }
205 }
206
207
208
209
210
211
212
213
214
215 private ClassicHttpResponse createTunnelToTarget(
216 final String exchangeId,
217 final HttpRoute route,
218 final HttpRequest request,
219 final ExecRuntime execRuntime,
220 final HttpClientContext context) throws HttpException, IOException {
221
222 final RequestConfig config = context.getRequestConfigOrDefault();
223
224 final HttpHost target = route.getTargetHost();
225 final HttpHost proxy = route.getProxyHost();
226 final AuthExchange proxyAuthExchange = context.getAuthExchange(proxy);
227
228 if (authCacheKeeper != null) {
229 authCacheKeeper.loadPreemptively(proxy, null, proxyAuthExchange, context);
230 }
231
232 ClassicHttpResponse response = null;
233
234 final String authority = target.toHostString();
235 final ClassicHttpRequest connect = new BasicClassicHttpRequest(Method.CONNECT, target, authority);
236 connect.setVersion(HttpVersion.HTTP_1_1);
237
238 this.proxyHttpProcessor.process(connect, null, context);
239
240 while (response == null) {
241 connect.removeHeaders(HttpHeaders.PROXY_AUTHORIZATION);
242 this.authenticator.addAuthResponse(proxy, ChallengeType.PROXY, connect, proxyAuthExchange, context);
243
244 response = execRuntime.execute(exchangeId, connect, context);
245 this.proxyHttpProcessor.process(response, response.getEntity(), context);
246
247 final int status = response.getCode();
248 if (status < HttpStatus.SC_SUCCESS) {
249 throw new HttpException("Unexpected response to CONNECT request: " + new StatusLine(response));
250 }
251
252 if (config.isAuthenticationEnabled()) {
253 final boolean proxyAuthRequested = authenticator.isChallenged(proxy, ChallengeType.PROXY, response, proxyAuthExchange, context);
254
255 if (authCacheKeeper != null) {
256 if (proxyAuthRequested) {
257 authCacheKeeper.updateOnChallenge(proxy, null, proxyAuthExchange, context);
258 } else {
259 authCacheKeeper.updateOnNoChallenge(proxy, null, proxyAuthExchange, context);
260 }
261 }
262
263 if (proxyAuthRequested) {
264 final boolean updated = authenticator.updateAuthState(proxy, ChallengeType.PROXY, response,
265 proxyAuthStrategy, proxyAuthExchange, context);
266
267 if (authCacheKeeper != null) {
268 authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
269 }
270 if (updated) {
271
272 if (this.reuseStrategy.keepAlive(connect, response, context)) {
273 if (LOG.isDebugEnabled()) {
274 LOG.debug("{} connection kept alive", exchangeId);
275 }
276
277 final HttpEntity entity = response.getEntity();
278 EntityUtils.consume(entity);
279 } else {
280 execRuntime.disconnectEndpoint();
281 }
282 response = null;
283 }
284 }
285 }
286 }
287
288 final int status = response.getCode();
289 if (status != HttpStatus.SC_OK) {
290 final HttpEntity entity = response.getEntity();
291 if (entity != null) {
292 response.setEntity(new ByteArrayEntity(
293 EntityUtils.toByteArray(entity, 4096),
294 ContentType.parseLenient(entity.getContentType())));
295 execRuntime.disconnectEndpoint();
296 }
297 return response;
298 }
299 return null;
300 }
301
302
303
304
305
306
307 private boolean createTunnelToProxy(
308 final HttpRoute route,
309 final int hop,
310 final HttpClientContext context) throws HttpException {
311
312
313
314
315
316
317
318
319
320
321 throw new HttpException("Proxy chains are not supported.");
322 }
323
324 }