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.http.impl.execchain;
29
30 import java.io.IOException;
31 import java.io.InterruptedIOException;
32 import java.util.concurrent.ExecutionException;
33 import java.util.concurrent.TimeUnit;
34
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 import org.apache.http.ConnectionReuseStrategy;
38 import org.apache.http.HttpClientConnection;
39 import org.apache.http.HttpEntity;
40 import org.apache.http.HttpEntityEnclosingRequest;
41 import org.apache.http.HttpException;
42 import org.apache.http.HttpHost;
43 import org.apache.http.HttpRequest;
44 import org.apache.http.HttpRequestInterceptor;
45 import org.apache.http.HttpResponse;
46 import org.apache.http.annotation.Immutable;
47 import org.apache.http.auth.AUTH;
48 import org.apache.http.auth.AuthProtocolState;
49 import org.apache.http.auth.AuthState;
50 import org.apache.http.client.AuthenticationStrategy;
51 import org.apache.http.client.NonRepeatableRequestException;
52 import org.apache.http.client.UserTokenHandler;
53 import org.apache.http.client.config.RequestConfig;
54 import org.apache.http.client.methods.CloseableHttpResponse;
55 import org.apache.http.client.methods.HttpExecutionAware;
56 import org.apache.http.client.methods.HttpRequestWrapper;
57 import org.apache.http.client.protocol.HttpClientContext;
58 import org.apache.http.client.protocol.RequestClientConnControl;
59 import org.apache.http.conn.ConnectionKeepAliveStrategy;
60 import org.apache.http.conn.ConnectionRequest;
61 import org.apache.http.conn.HttpClientConnectionManager;
62 import org.apache.http.conn.routing.BasicRouteDirector;
63 import org.apache.http.conn.routing.HttpRoute;
64 import org.apache.http.conn.routing.HttpRouteDirector;
65 import org.apache.http.conn.routing.RouteTracker;
66 import org.apache.http.entity.BufferedHttpEntity;
67 import org.apache.http.impl.auth.HttpAuthenticator;
68 import org.apache.http.impl.conn.ConnectionShutdownException;
69 import org.apache.http.message.BasicHttpRequest;
70 import org.apache.http.protocol.HttpProcessor;
71 import org.apache.http.protocol.HttpRequestExecutor;
72 import org.apache.http.protocol.ImmutableHttpProcessor;
73 import org.apache.http.protocol.RequestUserAgent;
74 import org.apache.http.util.Args;
75 import org.apache.http.util.EntityUtils;
76
77
78
79
80 @Immutable
81 public class MainClientExec implements ClientExecChain {
82
83 private final Log log = LogFactory.getLog(getClass());
84
85 private final HttpRequestExecutor requestExecutor;
86 private final HttpClientConnectionManager connManager;
87 private final ConnectionReuseStrategy reuseStrategy;
88 private final ConnectionKeepAliveStrategy keepAliveStrategy;
89 private final HttpProcessor proxyHttpProcessor;
90 private final AuthenticationStrategy targetAuthStrategy;
91 private final AuthenticationStrategy proxyAuthStrategy;
92 private final HttpAuthenticator authenticator;
93 private final UserTokenHandler userTokenHandler;
94 private final HttpRouteDirector routeDirector;
95
96
97 public MainClientExec(
98 final HttpRequestExecutor requestExecutor,
99 final HttpClientConnectionManager connManager,
100 final ConnectionReuseStrategy reuseStrategy,
101 final ConnectionKeepAliveStrategy keepAliveStrategy,
102 final AuthenticationStrategy targetAuthStrategy,
103 final AuthenticationStrategy proxyAuthStrategy,
104 final UserTokenHandler userTokenHandler) {
105 Args.notNull(requestExecutor, "HTTP request executor");
106 Args.notNull(connManager, "Client connection manager");
107 Args.notNull(reuseStrategy, "Connection reuse strategy");
108 Args.notNull(keepAliveStrategy, "Connection keep alive strategy");
109 Args.notNull(targetAuthStrategy, "Target authentication strategy");
110 Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
111 Args.notNull(userTokenHandler, "User token handler");
112 this.authenticator = new HttpAuthenticator();
113 this.proxyHttpProcessor = new ImmutableHttpProcessor(new HttpRequestInterceptor[] {
114 new RequestClientConnControl(),
115 new RequestUserAgent()
116 } );
117 this.routeDirector = new BasicRouteDirector();
118 this.requestExecutor = requestExecutor;
119 this.connManager = connManager;
120 this.reuseStrategy = reuseStrategy;
121 this.keepAliveStrategy = keepAliveStrategy;
122 this.targetAuthStrategy = targetAuthStrategy;
123 this.proxyAuthStrategy = proxyAuthStrategy;
124 this.userTokenHandler = userTokenHandler;
125 }
126
127 public CloseableHttpResponse execute(
128 final HttpRoute route,
129 final HttpRequestWrapper request,
130 final HttpClientContext context,
131 final HttpExecutionAware execAware) throws IOException, HttpException {
132 Args.notNull(route, "HTTP route");
133 Args.notNull(request, "HTTP request");
134 Args.notNull(context, "HTTP context");
135
136 AuthState targetAuthState = context.getTargetAuthState();
137 if (targetAuthState == null) {
138 targetAuthState = new AuthState();
139 context.setAttribute(HttpClientContext.TARGET_AUTH_STATE, targetAuthState);
140 }
141 AuthState proxyAuthState = context.getProxyAuthState();
142 if (proxyAuthState == null) {
143 proxyAuthState = new AuthState();
144 context.setAttribute(HttpClientContext.PROXY_AUTH_STATE, proxyAuthState);
145 }
146
147 if (request instanceof HttpEntityEnclosingRequest) {
148 Proxies.enhanceEntity((HttpEntityEnclosingRequest) request);
149 }
150
151 Object userToken = context.getUserToken();
152
153 final ConnectionRequest connRequest = connManager.requestConnection(route, userToken);
154 if (execAware != null) {
155 if (execAware.isAborted()) {
156 connRequest.cancel();
157 throw new RequestAbortedException("Request aborted");
158 } else {
159 execAware.setCancellable(connRequest);
160 }
161 }
162
163 final RequestConfig config = context.getRequestConfig();
164
165 HttpClientConnection managedConn;
166 try {
167 final int timeout = config.getConnectionRequestTimeout();
168 managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS);
169 } catch(final InterruptedException interrupted) {
170 Thread.currentThread().interrupt();
171 throw new RequestAbortedException("Request aborted", interrupted);
172 } catch(final ExecutionException ex) {
173 Throwable cause = ex.getCause();
174 if (cause == null) {
175 cause = ex;
176 }
177 throw new RequestAbortedException("Request execution failed", cause);
178 }
179
180 context.setAttribute(HttpClientContext.HTTP_CONNECTION, managedConn);
181
182 if (config.isStaleConnectionCheckEnabled()) {
183
184 if (managedConn.isOpen()) {
185 this.log.debug("Stale connection check");
186 if (managedConn.isStale()) {
187 this.log.debug("Stale connection detected");
188 managedConn.close();
189 }
190 }
191 }
192
193 final ConnectionHolder connHolder = new ConnectionHolder(this.log, this.connManager, managedConn);
194 try {
195 if (execAware != null) {
196 if (execAware.isAborted()) {
197 connHolder.abortConnection();
198 throw new RequestAbortedException("Request aborted");
199 } else {
200 execAware.setCancellable(connHolder);
201 }
202 }
203
204 HttpResponse response = null;
205 for (int execCount = 1;; execCount++) {
206
207 if (execCount > 1 && !Proxies.isRepeatable(request)) {
208 throw new NonRepeatableRequestException("Cannot retry request " +
209 "with a non-repeatable request entity.");
210 }
211
212 if (execAware != null && execAware.isAborted()) {
213 throw new RequestAbortedException("Request aborted");
214 }
215
216 if (!managedConn.isOpen()) {
217 this.log.debug("Opening connection " + route);
218 try {
219 establishRoute(proxyAuthState, managedConn, route, request, context);
220 } catch (final TunnelRefusedException ex) {
221 if (this.log.isDebugEnabled()) {
222 this.log.debug(ex.getMessage());
223 }
224 response = ex.getResponse();
225 break;
226 }
227 }
228 final int timeout = config.getSocketTimeout();
229 if (timeout >= 0) {
230 managedConn.setSocketTimeout(timeout);
231 }
232
233 if (execAware != null && execAware.isAborted()) {
234 throw new RequestAbortedException("Request aborted");
235 }
236
237 if (this.log.isDebugEnabled()) {
238 this.log.debug("Executing request " + request.getRequestLine());
239 }
240
241 if (!request.containsHeader(AUTH.WWW_AUTH_RESP)) {
242 if (this.log.isDebugEnabled()) {
243 this.log.debug("Target auth state: " + targetAuthState.getState());
244 }
245 this.authenticator.generateAuthResponse(request, targetAuthState, context);
246 }
247 if (!request.containsHeader(AUTH.PROXY_AUTH_RESP) && !route.isTunnelled()) {
248 if (this.log.isDebugEnabled()) {
249 this.log.debug("Proxy auth state: " + proxyAuthState.getState());
250 }
251 this.authenticator.generateAuthResponse(request, proxyAuthState, context);
252 }
253
254 response = requestExecutor.execute(request, managedConn, context);
255
256
257 if (reuseStrategy.keepAlive(response, context)) {
258
259 final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
260 if (this.log.isDebugEnabled()) {
261 String s;
262 if (duration > 0) {
263 s = "for " + duration + " " + TimeUnit.MILLISECONDS;
264 } else {
265 s = "indefinitely";
266 }
267 this.log.debug("Connection can be kept alive " + s);
268 }
269 connHolder.setValidFor(duration, TimeUnit.MILLISECONDS);
270 connHolder.markReusable();
271 } else {
272 connHolder.markNonReusable();
273 }
274
275 if (needAuthentication(
276 targetAuthState, proxyAuthState, route, request, response, context)) {
277 if (connHolder.isReusable()) {
278
279 final HttpEntity entity = response.getEntity();
280 EntityUtils.consume(entity);
281
282
283 } else {
284 managedConn.close();
285 if (proxyAuthState.getState() == AuthProtocolState.SUCCESS
286 && proxyAuthState.getAuthScheme() != null
287 && proxyAuthState.getAuthScheme().isConnectionBased()) {
288 this.log.debug("Resetting proxy auth state");
289 proxyAuthState.reset();
290 }
291 if (targetAuthState.getState() == AuthProtocolState.SUCCESS
292 && targetAuthState.getAuthScheme() != null
293 && targetAuthState.getAuthScheme().isConnectionBased()) {
294 this.log.debug("Resetting target auth state");
295 targetAuthState.reset();
296 }
297 }
298
299 request.removeHeaders(AUTH.WWW_AUTH_RESP);
300 request.removeHeaders(AUTH.PROXY_AUTH_RESP);
301 } else {
302 break;
303 }
304 }
305
306 if (userToken == null) {
307 userToken = userTokenHandler.getUserToken(context);
308 context.setAttribute(HttpClientContext.USER_TOKEN, userToken);
309 }
310 if (userToken != null) {
311 connHolder.setState(userToken);
312 }
313
314
315 final HttpEntity entity = response.getEntity();
316 if (entity == null || !entity.isStreaming()) {
317
318 connHolder.releaseConnection();
319 return Proxies.enhanceResponse(response, null);
320 } else {
321 return Proxies.enhanceResponse(response, connHolder);
322 }
323 } catch (final ConnectionShutdownException ex) {
324 final InterruptedIOException ioex = new InterruptedIOException(
325 "Connection has been shut down");
326 ioex.initCause(ex);
327 throw ioex;
328 } catch (final HttpException ex) {
329 connHolder.abortConnection();
330 throw ex;
331 } catch (final IOException ex) {
332 connHolder.abortConnection();
333 throw ex;
334 } catch (final RuntimeException ex) {
335 connHolder.abortConnection();
336 throw ex;
337 }
338 }
339
340
341
342
343 private void establishRoute(
344 final AuthState proxyAuthState,
345 final HttpClientConnection managedConn,
346 final HttpRoute route,
347 final HttpRequest request,
348 final HttpClientContext context) throws HttpException, IOException {
349 final RequestConfig config = context.getRequestConfig();
350 final int timeout = config.getConnectTimeout();
351 final RouteTracker tracker = new RouteTracker(route);
352 int step;
353 do {
354 final HttpRoute fact = tracker.toRoute();
355 step = this.routeDirector.nextStep(route, fact);
356
357 switch (step) {
358
359 case HttpRouteDirector.CONNECT_TARGET:
360 this.connManager.connect(
361 managedConn,
362 route,
363 timeout > 0 ? timeout : 0,
364 context);
365 tracker.connectTarget(route.isSecure());
366 break;
367 case HttpRouteDirector.CONNECT_PROXY:
368 this.connManager.connect(
369 managedConn,
370 route,
371 timeout > 0 ? timeout : 0,
372 context);
373 final HttpHost proxy = route.getProxyHost();
374 tracker.connectProxy(proxy, false);
375 break;
376 case HttpRouteDirector.TUNNEL_TARGET: {
377 final boolean secure = createTunnelToTarget(
378 proxyAuthState, managedConn, route, request, context);
379 this.log.debug("Tunnel to target created.");
380 tracker.tunnelTarget(secure);
381 } break;
382
383 case HttpRouteDirector.TUNNEL_PROXY: {
384
385
386
387
388 final int hop = fact.getHopCount()-1;
389 final boolean secure = createTunnelToProxy(route, hop, context);
390 this.log.debug("Tunnel to proxy created.");
391 tracker.tunnelProxy(route.getHopTarget(hop), secure);
392 } break;
393
394 case HttpRouteDirector.LAYER_PROTOCOL:
395 this.connManager.upgrade(managedConn, route, context);
396 tracker.layerProtocol(route.isSecure());
397 break;
398
399 case HttpRouteDirector.UNREACHABLE:
400 throw new HttpException("Unable to establish route: " +
401 "planned = " + route + "; current = " + fact);
402 case HttpRouteDirector.COMPLETE:
403 this.connManager.routeComplete(managedConn, route, context);
404 break;
405 default:
406 throw new IllegalStateException("Unknown step indicator "
407 + step + " from RouteDirector.");
408 }
409
410 } while (step > HttpRouteDirector.COMPLETE);
411 }
412
413
414
415
416
417
418
419
420
421 private boolean createTunnelToTarget(
422 final AuthState proxyAuthState,
423 final HttpClientConnection managedConn,
424 final HttpRoute route,
425 final HttpRequest request,
426 final HttpClientContext context) throws HttpException, IOException {
427
428 final RequestConfig config = context.getRequestConfig();
429 final int timeout = config.getConnectTimeout();
430
431 final HttpHost target = route.getTargetHost();
432 final HttpHost proxy = route.getProxyHost();
433 HttpResponse response = null;
434
435 final String authority = target.toHostString();
436 final HttpRequest connect = new BasicHttpRequest("CONNECT", authority, request.getProtocolVersion());
437
438 this.requestExecutor.preProcess(connect, this.proxyHttpProcessor, context);
439
440 for (;;) {
441 if (!managedConn.isOpen()) {
442 this.connManager.connect(
443 managedConn,
444 route,
445 timeout > 0 ? timeout : 0,
446 context);
447 }
448
449 connect.removeHeaders(AUTH.PROXY_AUTH_RESP);
450 this.authenticator.generateAuthResponse(connect, proxyAuthState, context);
451
452 response = this.requestExecutor.execute(connect, managedConn, context);
453
454 final int status = response.getStatusLine().getStatusCode();
455 if (status < 200) {
456 throw new HttpException("Unexpected response to CONNECT request: " +
457 response.getStatusLine());
458 }
459
460 if (config.isAuthenticationEnabled()) {
461 if (this.authenticator.isAuthenticationRequested(proxy, response,
462 this.proxyAuthStrategy, proxyAuthState, context)) {
463 if (this.authenticator.handleAuthChallenge(proxy, response,
464 this.proxyAuthStrategy, proxyAuthState, context)) {
465
466 if (this.reuseStrategy.keepAlive(response, context)) {
467 this.log.debug("Connection kept alive");
468
469 final HttpEntity entity = response.getEntity();
470 EntityUtils.consume(entity);
471 } else {
472 managedConn.close();
473 }
474 } else {
475 break;
476 }
477 } else {
478 break;
479 }
480 }
481 }
482
483 final int status = response.getStatusLine().getStatusCode();
484
485 if (status > 299) {
486
487
488 final HttpEntity entity = response.getEntity();
489 if (entity != null) {
490 response.setEntity(new BufferedHttpEntity(entity));
491 }
492
493 managedConn.close();
494 throw new TunnelRefusedException("CONNECT refused by proxy: " +
495 response.getStatusLine(), response);
496 }
497
498
499
500
501
502 return false;
503 }
504
505
506
507
508
509
510 private boolean createTunnelToProxy(
511 final HttpRoute route,
512 final int hop,
513 final HttpClientContext context) throws HttpException, IOException {
514
515
516
517
518
519
520
521
522
523
524 throw new HttpException("Proxy chains are not supported.");
525 }
526
527 private boolean needAuthentication(
528 final AuthState targetAuthState,
529 final AuthState proxyAuthState,
530 final HttpRoute route,
531 final HttpRequestWrapper request,
532 final HttpResponse response,
533 final HttpClientContext context) throws HttpException, IOException {
534 final RequestConfig config = context.getRequestConfig();
535 if (config.isAuthenticationEnabled()) {
536 HttpHost target = context.getTargetHost();
537 if (target == null) {
538 target = route.getTargetHost();
539 }
540 if (target.getPort() < 0) {
541 target = new HttpHost(
542 target.getHostName(),
543 route.getTargetHost().getPort(),
544 target.getSchemeName());
545 }
546 if (this.authenticator.isAuthenticationRequested(target, response,
547 this.targetAuthStrategy, targetAuthState, context)) {
548 return this.authenticator.handleAuthChallenge(target, response,
549 this.targetAuthStrategy, targetAuthState, context);
550 }
551 HttpHost proxy = route.getProxyHost();
552 if (this.authenticator.isAuthenticationRequested(proxy, response,
553 this.proxyAuthStrategy, proxyAuthState, context)) {
554
555 if (proxy == null) {
556 proxy = route.getTargetHost();
557 }
558 return this.authenticator.handleAuthChallenge(proxy, response,
559 this.proxyAuthStrategy, proxyAuthState, context);
560 }
561 }
562 return false;
563 }
564
565 }