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