View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.impl.client;
29  
30  import java.io.IOException;
31  import java.io.InterruptedIOException;
32  import java.net.URI;
33  import java.net.URISyntaxException;
34  import java.util.concurrent.TimeUnit;
35  
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  import org.apache.http.ConnectionReuseStrategy;
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.NoHttpResponseException;
46  import org.apache.http.ProtocolException;
47  import org.apache.http.ProtocolVersion;
48  import org.apache.http.annotation.NotThreadSafe;
49  import org.apache.http.auth.AuthProtocolState;
50  import org.apache.http.auth.AuthScheme;
51  import org.apache.http.auth.AuthState;
52  import org.apache.http.auth.UsernamePasswordCredentials;
53  import org.apache.http.client.AuthenticationHandler;
54  import org.apache.http.client.AuthenticationStrategy;
55  import org.apache.http.client.HttpRequestRetryHandler;
56  import org.apache.http.client.NonRepeatableRequestException;
57  import org.apache.http.client.RedirectException;
58  import org.apache.http.client.RedirectHandler;
59  import org.apache.http.client.RedirectStrategy;
60  import org.apache.http.client.RequestDirector;
61  import org.apache.http.client.UserTokenHandler;
62  import org.apache.http.client.methods.AbortableHttpRequest;
63  import org.apache.http.client.methods.HttpUriRequest;
64  import org.apache.http.client.params.ClientPNames;
65  import org.apache.http.client.params.HttpClientParams;
66  import org.apache.http.client.protocol.ClientContext;
67  import org.apache.http.client.utils.URIUtils;
68  import org.apache.http.conn.BasicManagedEntity;
69  import org.apache.http.conn.ClientConnectionManager;
70  import org.apache.http.conn.ClientConnectionRequest;
71  import org.apache.http.conn.ConnectionKeepAliveStrategy;
72  import org.apache.http.conn.ManagedClientConnection;
73  import org.apache.http.conn.routing.BasicRouteDirector;
74  import org.apache.http.conn.routing.HttpRoute;
75  import org.apache.http.conn.routing.HttpRouteDirector;
76  import org.apache.http.conn.routing.HttpRoutePlanner;
77  import org.apache.http.conn.scheme.Scheme;
78  import org.apache.http.entity.BufferedHttpEntity;
79  import org.apache.http.impl.auth.BasicScheme;
80  import org.apache.http.impl.conn.ConnectionShutdownException;
81  import org.apache.http.message.BasicHttpRequest;
82  import org.apache.http.params.HttpConnectionParams;
83  import org.apache.http.params.HttpParams;
84  import org.apache.http.params.HttpProtocolParams;
85  import org.apache.http.protocol.ExecutionContext;
86  import org.apache.http.protocol.HttpContext;
87  import org.apache.http.protocol.HttpProcessor;
88  import org.apache.http.protocol.HttpRequestExecutor;
89  import org.apache.http.util.Args;
90  import org.apache.http.util.EntityUtils;
91  
92  /**
93   * Default implementation of {@link RequestDirector}.
94   * <p>
95   * The following parameters can be used to customize the behavior of this
96   * class:
97   * <ul>
98   *  <li>{@link org.apache.http.params.CoreProtocolPNames#PROTOCOL_VERSION}</li>
99   *  <li>{@link org.apache.http.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li>
100  *  <li>{@link org.apache.http.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li>
101  *  <li>{@link org.apache.http.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li>
102  *  <li>{@link org.apache.http.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li>
103  *  <li>{@link org.apache.http.params.CoreProtocolPNames#USER_AGENT}</li>
104  *  <li>{@link org.apache.http.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li>
105  *  <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li>
106  *  <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li>
107  *  <li>{@link org.apache.http.params.CoreConnectionPNames#SO_TIMEOUT}</li>
108  *  <li>{@link org.apache.http.params.CoreConnectionPNames#SO_LINGER}</li>
109  *  <li>{@link org.apache.http.params.CoreConnectionPNames#SO_REUSEADDR}</li>
110  *  <li>{@link org.apache.http.params.CoreConnectionPNames#TCP_NODELAY}</li>
111  *  <li>{@link org.apache.http.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li>
112  *  <li>{@link org.apache.http.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li>
113  *  <li>{@link org.apache.http.conn.params.ConnRoutePNames#FORCED_ROUTE}</li>
114  *  <li>{@link org.apache.http.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li>
115  *  <li>{@link org.apache.http.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li>
116  *  <li>{@link org.apache.http.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li>
117  *  <li>{@link org.apache.http.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li>
118  *  <li>{@link org.apache.http.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li>
119  *  <li>{@link org.apache.http.client.params.ClientPNames#COOKIE_POLICY}</li>
120  *  <li>{@link org.apache.http.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li>
121  *  <li>{@link org.apache.http.client.params.ClientPNames#HANDLE_REDIRECTS}</li>
122  *  <li>{@link org.apache.http.client.params.ClientPNames#MAX_REDIRECTS}</li>
123  *  <li>{@link org.apache.http.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li>
124  *  <li>{@link org.apache.http.client.params.ClientPNames#VIRTUAL_HOST}</li>
125  *  <li>{@link org.apache.http.client.params.ClientPNames#DEFAULT_HOST}</li>
126  *  <li>{@link org.apache.http.client.params.ClientPNames#DEFAULT_HEADERS}</li>
127  *  <li>{@link org.apache.http.client.params.ClientPNames#CONN_MANAGER_TIMEOUT}</li>
128  * </ul>
129  *
130  * @since 4.0
131  *
132  * @deprecated (4.3)
133  */
134 @Deprecated
135 @NotThreadSafe // e.g. managedConn
136 public class DefaultRequestDirector implements RequestDirector {
137 
138     private final Log log;
139 
140     /** The connection manager. */
141     protected final ClientConnectionManager connManager;
142 
143     /** The route planner. */
144     protected final HttpRoutePlanner routePlanner;
145 
146     /** The connection re-use strategy. */
147     protected final ConnectionReuseStrategy reuseStrategy;
148 
149     /** The keep-alive duration strategy. */
150     protected final ConnectionKeepAliveStrategy keepAliveStrategy;
151 
152     /** The request executor. */
153     protected final HttpRequestExecutor requestExec;
154 
155     /** The HTTP protocol processor. */
156     protected final HttpProcessor httpProcessor;
157 
158     /** The request retry handler. */
159     protected final HttpRequestRetryHandler retryHandler;
160 
161     /** The redirect handler. */
162     @Deprecated
163     protected final RedirectHandler redirectHandler;
164 
165     /** The redirect strategy. */
166     protected final RedirectStrategy redirectStrategy;
167 
168     /** The target authentication handler. */
169     @Deprecated
170     protected final AuthenticationHandler targetAuthHandler;
171 
172     /** The target authentication handler. */
173     protected final AuthenticationStrategy targetAuthStrategy;
174 
175     /** The proxy authentication handler. */
176     @Deprecated
177     protected final AuthenticationHandler proxyAuthHandler;
178 
179     /** The proxy authentication handler. */
180     protected final AuthenticationStrategy proxyAuthStrategy;
181 
182     /** The user token handler. */
183     protected final UserTokenHandler userTokenHandler;
184 
185     /** The HTTP parameters. */
186     protected final HttpParams params;
187 
188     /** The currently allocated connection. */
189     protected ManagedClientConnection managedConn;
190 
191     protected final AuthState targetAuthState;
192 
193     protected final AuthState proxyAuthState;
194 
195     private final HttpAuthenticator authenticator;
196 
197     private int execCount;
198 
199     private int redirectCount;
200 
201     private final int maxRedirects;
202 
203     private HttpHost virtualHost;
204 
205     @Deprecated
206     public DefaultRequestDirector(
207             final HttpRequestExecutor requestExec,
208             final ClientConnectionManager conman,
209             final ConnectionReuseStrategy reustrat,
210             final ConnectionKeepAliveStrategy kastrat,
211             final HttpRoutePlanner rouplan,
212             final HttpProcessor httpProcessor,
213             final HttpRequestRetryHandler retryHandler,
214             final RedirectHandler redirectHandler,
215             final AuthenticationHandler targetAuthHandler,
216             final AuthenticationHandler proxyAuthHandler,
217             final UserTokenHandler userTokenHandler,
218             final HttpParams params) {
219         this(LogFactory.getLog(DefaultRequestDirector.class),
220                 requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler,
221                 new DefaultRedirectStrategyAdaptor(redirectHandler),
222                 new AuthenticationStrategyAdaptor(targetAuthHandler),
223                 new AuthenticationStrategyAdaptor(proxyAuthHandler),
224                 userTokenHandler,
225                 params);
226     }
227 
228 
229     @Deprecated
230     public DefaultRequestDirector(
231             final Log log,
232             final HttpRequestExecutor requestExec,
233             final ClientConnectionManager conman,
234             final ConnectionReuseStrategy reustrat,
235             final ConnectionKeepAliveStrategy kastrat,
236             final HttpRoutePlanner rouplan,
237             final HttpProcessor httpProcessor,
238             final HttpRequestRetryHandler retryHandler,
239             final RedirectStrategy redirectStrategy,
240             final AuthenticationHandler targetAuthHandler,
241             final AuthenticationHandler proxyAuthHandler,
242             final UserTokenHandler userTokenHandler,
243             final HttpParams params) {
244         this(LogFactory.getLog(DefaultRequestDirector.class),
245                 requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler,
246                 redirectStrategy,
247                 new AuthenticationStrategyAdaptor(targetAuthHandler),
248                 new AuthenticationStrategyAdaptor(proxyAuthHandler),
249                 userTokenHandler,
250                 params);
251     }
252 
253     /**
254      * @since 4.2
255      */
256     public DefaultRequestDirector(
257             final Log log,
258             final HttpRequestExecutor requestExec,
259             final ClientConnectionManager conman,
260             final ConnectionReuseStrategy reustrat,
261             final ConnectionKeepAliveStrategy kastrat,
262             final HttpRoutePlanner rouplan,
263             final HttpProcessor httpProcessor,
264             final HttpRequestRetryHandler retryHandler,
265             final RedirectStrategy redirectStrategy,
266             final AuthenticationStrategy targetAuthStrategy,
267             final AuthenticationStrategy proxyAuthStrategy,
268             final UserTokenHandler userTokenHandler,
269             final HttpParams params) {
270 
271         Args.notNull(log, "Log");
272         Args.notNull(requestExec, "Request executor");
273         Args.notNull(conman, "Client connection manager");
274         Args.notNull(reustrat, "Connection reuse strategy");
275         Args.notNull(kastrat, "Connection keep alive strategy");
276         Args.notNull(rouplan, "Route planner");
277         Args.notNull(httpProcessor, "HTTP protocol processor");
278         Args.notNull(retryHandler, "HTTP request retry handler");
279         Args.notNull(redirectStrategy, "Redirect strategy");
280         Args.notNull(targetAuthStrategy, "Target authentication strategy");
281         Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
282         Args.notNull(userTokenHandler, "User token handler");
283         Args.notNull(params, "HTTP parameters");
284         this.log               = log;
285         this.authenticator     = new HttpAuthenticator(log);
286         this.requestExec        = requestExec;
287         this.connManager        = conman;
288         this.reuseStrategy      = reustrat;
289         this.keepAliveStrategy  = kastrat;
290         this.routePlanner       = rouplan;
291         this.httpProcessor      = httpProcessor;
292         this.retryHandler       = retryHandler;
293         this.redirectStrategy   = redirectStrategy;
294         this.targetAuthStrategy = targetAuthStrategy;
295         this.proxyAuthStrategy  = proxyAuthStrategy;
296         this.userTokenHandler   = userTokenHandler;
297         this.params             = params;
298 
299         if (redirectStrategy instanceof DefaultRedirectStrategyAdaptor) {
300             this.redirectHandler = ((DefaultRedirectStrategyAdaptor) redirectStrategy).getHandler();
301         } else {
302             this.redirectHandler = null;
303         }
304         if (targetAuthStrategy instanceof AuthenticationStrategyAdaptor) {
305             this.targetAuthHandler = ((AuthenticationStrategyAdaptor) targetAuthStrategy).getHandler();
306         } else {
307             this.targetAuthHandler = null;
308         }
309         if (proxyAuthStrategy instanceof AuthenticationStrategyAdaptor) {
310             this.proxyAuthHandler = ((AuthenticationStrategyAdaptor) proxyAuthStrategy).getHandler();
311         } else {
312             this.proxyAuthHandler = null;
313         }
314 
315         this.managedConn = null;
316 
317         this.execCount = 0;
318         this.redirectCount = 0;
319         this.targetAuthState = new AuthState();
320         this.proxyAuthState = new AuthState();
321         this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100);
322     }
323 
324 
325     private RequestWrapper wrapRequest(
326             final HttpRequest request) throws ProtocolException {
327         if (request instanceof HttpEntityEnclosingRequest) {
328             return new EntityEnclosingRequestWrapper(
329                     (HttpEntityEnclosingRequest) request);
330         } else {
331             return new RequestWrapper(
332                     request);
333         }
334     }
335 
336 
337     protected void rewriteRequestURI(
338             final RequestWrapper request,
339             final HttpRoute route) throws ProtocolException {
340         try {
341 
342             URI uri = request.getURI();
343             if (route.getProxyHost() != null && !route.isTunnelled()) {
344                 // Make sure the request URI is absolute
345                 if (!uri.isAbsolute()) {
346                     final HttpHost target = route.getTargetHost();
347                     uri = URIUtils.rewriteURI(uri, target, true);
348                 } else {
349                     uri = URIUtils.rewriteURI(uri);
350                 }
351             } else {
352                 // Make sure the request URI is relative
353                 if (uri.isAbsolute()) {
354                     uri = URIUtils.rewriteURI(uri, null, true);
355                 } else {
356                     uri = URIUtils.rewriteURI(uri);
357                 }
358             }
359             request.setURI(uri);
360 
361         } catch (final URISyntaxException ex) {
362             throw new ProtocolException("Invalid URI: " +
363                     request.getRequestLine().getUri(), ex);
364         }
365     }
366 
367 
368     // non-javadoc, see interface ClientRequestDirector
369     @Override
370     public HttpResponse execute(final HttpHost targetHost, final HttpRequest request,
371                                 final HttpContext context)
372         throws HttpException, IOException {
373 
374         context.setAttribute(ClientContext.TARGET_AUTH_STATE, targetAuthState);
375         context.setAttribute(ClientContext.PROXY_AUTH_STATE, proxyAuthState);
376 
377         HttpHost target = targetHost;
378 
379         final HttpRequest orig = request;
380         final RequestWrapper origWrapper = wrapRequest(orig);
381         origWrapper.setParams(params);
382         final HttpRoute origRoute = determineRoute(target, origWrapper, context);
383 
384         virtualHost = (HttpHost) origWrapper.getParams().getParameter(ClientPNames.VIRTUAL_HOST);
385 
386         // HTTPCLIENT-1092 - add the port if necessary
387         if (virtualHost != null && virtualHost.getPort() == -1) {
388             final HttpHost host = (target != null) ? target : origRoute.getTargetHost();
389             final int port = host.getPort();
390             if (port != -1){
391                 virtualHost = new HttpHost(virtualHost.getHostName(), port, virtualHost.getSchemeName());
392             }
393         }
394 
395         RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute);
396 
397         boolean reuse = false;
398         boolean done = false;
399         try {
400             HttpResponse response = null;
401             while (!done) {
402                 // In this loop, the RoutedRequest may be replaced by a
403                 // followup request and route. The request and route passed
404                 // in the method arguments will be replaced. The original
405                 // request is still available in 'orig'.
406 
407                 final RequestWrapper wrapper = roureq.getRequest();
408                 final HttpRoute route = roureq.getRoute();
409                 response = null;
410 
411                 // See if we have a user token bound to the execution context
412                 Object userToken = context.getAttribute(ClientContext.USER_TOKEN);
413 
414                 // Allocate connection if needed
415                 if (managedConn == null) {
416                     final ClientConnectionRequest connRequest = connManager.requestConnection(
417                             route, userToken);
418                     if (orig instanceof AbortableHttpRequest) {
419                         ((AbortableHttpRequest) orig).setConnectionRequest(connRequest);
420                     }
421 
422                     final long timeout = HttpClientParams.getConnectionManagerTimeout(params);
423                     try {
424                         managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
425                     } catch(final InterruptedException interrupted) {
426                         Thread.currentThread().interrupt();
427                         throw new InterruptedIOException();
428                     }
429 
430                     if (HttpConnectionParams.isStaleCheckingEnabled(params)) {
431                         // validate connection
432                         if (managedConn.isOpen()) {
433                             this.log.debug("Stale connection check");
434                             if (managedConn.isStale()) {
435                                 this.log.debug("Stale connection detected");
436                                 managedConn.close();
437                             }
438                         }
439                     }
440                 }
441 
442                 if (orig instanceof AbortableHttpRequest) {
443                     ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn);
444                 }
445 
446                 try {
447                     tryConnect(roureq, context);
448                 } catch (final TunnelRefusedException ex) {
449                     if (this.log.isDebugEnabled()) {
450                         this.log.debug(ex.getMessage());
451                     }
452                     response = ex.getResponse();
453                     break;
454                 }
455 
456                 final String userinfo = wrapper.getURI().getUserInfo();
457                 if (userinfo != null) {
458                     targetAuthState.update(
459                             new BasicScheme(), new UsernamePasswordCredentials(userinfo));
460                 }
461 
462                 // Get target.  Even if there's virtual host, we may need the target to set the port.
463                 if (virtualHost != null) {
464                     target = virtualHost;
465                 } else {
466                     final URI requestURI = wrapper.getURI();
467                     if (requestURI.isAbsolute()) {
468                         target = URIUtils.extractHost(requestURI);
469                     }
470                 }
471                 if (target == null) {
472                     target = route.getTargetHost();
473                 }
474 
475                 // Reset headers on the request wrapper
476                 wrapper.resetHeaders();
477                 // Re-write request URI if needed
478                 rewriteRequestURI(wrapper, route);
479 
480                 // Populate the execution context
481                 context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
482                 context.setAttribute(ClientContext.ROUTE, route);
483                 context.setAttribute(ExecutionContext.HTTP_CONNECTION, managedConn);
484 
485                 // Run request protocol interceptors
486                 requestExec.preProcess(wrapper, httpProcessor, context);
487 
488                 response = tryExecute(roureq, context);
489                 if (response == null) {
490                     // Need to start over
491                     continue;
492                 }
493 
494                 // Run response protocol interceptors
495                 response.setParams(params);
496                 requestExec.postProcess(response, httpProcessor, context);
497 
498 
499                 // The connection is in or can be brought to a re-usable state.
500                 reuse = reuseStrategy.keepAlive(response, context);
501                 if (reuse) {
502                     // Set the idle duration of this connection
503                     final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
504                     if (this.log.isDebugEnabled()) {
505                         final String s;
506                         if (duration > 0) {
507                             s = "for " + duration + " " + TimeUnit.MILLISECONDS;
508                         } else {
509                             s = "indefinitely";
510                         }
511                         this.log.debug("Connection can be kept alive " + s);
512                     }
513                     managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS);
514                 }
515 
516                 final RoutedRequest followup = handleResponse(roureq, response, context);
517                 if (followup == null) {
518                     done = true;
519                 } else {
520                     if (reuse) {
521                         // Make sure the response body is fully consumed, if present
522                         final HttpEntity entity = response.getEntity();
523                         EntityUtils.consume(entity);
524                         // entity consumed above is not an auto-release entity,
525                         // need to mark the connection re-usable explicitly
526                         managedConn.markReusable();
527                     } else {
528                         managedConn.close();
529                         if (proxyAuthState.getState().compareTo(AuthProtocolState.CHALLENGED) > 0
530                                 && proxyAuthState.getAuthScheme() != null
531                                 && proxyAuthState.getAuthScheme().isConnectionBased()) {
532                             this.log.debug("Resetting proxy auth state");
533                             proxyAuthState.reset();
534                         }
535                         if (targetAuthState.getState().compareTo(AuthProtocolState.CHALLENGED) > 0
536                                 && targetAuthState.getAuthScheme() != null
537                                 && targetAuthState.getAuthScheme().isConnectionBased()) {
538                             this.log.debug("Resetting target auth state");
539                             targetAuthState.reset();
540                         }
541                     }
542                     // check if we can use the same connection for the followup
543                     if (!followup.getRoute().equals(roureq.getRoute())) {
544                         releaseConnection();
545                     }
546                     roureq = followup;
547                 }
548 
549                 if (managedConn != null) {
550                     if (userToken == null) {
551                         userToken = userTokenHandler.getUserToken(context);
552                         context.setAttribute(ClientContext.USER_TOKEN, userToken);
553                     }
554                     if (userToken != null) {
555                         managedConn.setState(userToken);
556                     }
557                 }
558 
559             } // while not done
560 
561 
562             // check for entity, release connection if possible
563             if ((response == null) || (response.getEntity() == null) ||
564                 !response.getEntity().isStreaming()) {
565                 // connection not needed and (assumed to be) in re-usable state
566                 if (reuse) {
567                     managedConn.markReusable();
568                 }
569                 releaseConnection();
570             } else {
571                 // install an auto-release entity
572                 HttpEntity entity = response.getEntity();
573                 entity = new BasicManagedEntity(entity, managedConn, reuse);
574                 response.setEntity(entity);
575             }
576 
577             return response;
578 
579         } catch (final ConnectionShutdownException ex) {
580             final InterruptedIOException ioex = new InterruptedIOException(
581                     "Connection has been shut down");
582             ioex.initCause(ex);
583             throw ioex;
584         } catch (final HttpException ex) {
585             abortConnection();
586             throw ex;
587         } catch (final IOException ex) {
588             abortConnection();
589             throw ex;
590         } catch (final RuntimeException ex) {
591             abortConnection();
592             throw ex;
593         }
594     } // execute
595 
596     /**
597      * Establish connection either directly or through a tunnel and retry in case of
598      * a recoverable I/O failure
599      */
600     private void tryConnect(
601             final RoutedRequest req, final HttpContext context) throws HttpException, IOException {
602         final HttpRoute route = req.getRoute();
603         final HttpRequest wrapper = req.getRequest();
604 
605         int connectCount = 0;
606         for (;;) {
607             context.setAttribute(ExecutionContext.HTTP_REQUEST, wrapper);
608             // Increment connect count
609             connectCount++;
610             try {
611                 if (!managedConn.isOpen()) {
612                     managedConn.open(route, context, params);
613                 } else {
614                     managedConn.setSocketTimeout(HttpConnectionParams.getSoTimeout(params));
615                 }
616                 establishRoute(route, context);
617                 break;
618             } catch (final IOException ex) {
619                 try {
620                     managedConn.close();
621                 } catch (final IOException ignore) {
622                 }
623                 if (retryHandler.retryRequest(ex, connectCount, context)) {
624                     if (this.log.isInfoEnabled()) {
625                         this.log.info("I/O exception ("+ ex.getClass().getName() +
626                                 ") caught when connecting to "
627                                 + route +
628                                 ": "
629                                 + ex.getMessage());
630                         if (this.log.isDebugEnabled()) {
631                             this.log.debug(ex.getMessage(), ex);
632                         }
633                         this.log.info("Retrying connect to " + route);
634                     }
635                 } else {
636                     throw ex;
637                 }
638             }
639         }
640     }
641 
642     /**
643      * Execute request and retry in case of a recoverable I/O failure
644      */
645     private HttpResponse tryExecute(
646             final RoutedRequest req, final HttpContext context) throws HttpException, IOException {
647         final RequestWrapper wrapper = req.getRequest();
648         final HttpRoute route = req.getRoute();
649         HttpResponse response = null;
650 
651         Exception retryReason = null;
652         for (;;) {
653             // Increment total exec count (with redirects)
654             execCount++;
655             // Increment exec count for this particular request
656             wrapper.incrementExecCount();
657             if (!wrapper.isRepeatable()) {
658                 this.log.debug("Cannot retry non-repeatable request");
659                 if (retryReason != null) {
660                     throw new NonRepeatableRequestException("Cannot retry request " +
661                         "with a non-repeatable request entity.  The cause lists the " +
662                         "reason the original request failed.", retryReason);
663                 } else {
664                     throw new NonRepeatableRequestException("Cannot retry request " +
665                             "with a non-repeatable request entity.");
666                 }
667             }
668 
669             try {
670                 if (!managedConn.isOpen()) {
671                     // If we have a direct route to the target host
672                     // just re-open connection and re-try the request
673                     if (!route.isTunnelled()) {
674                         this.log.debug("Reopening the direct connection.");
675                         managedConn.open(route, context, params);
676                     } else {
677                         // otherwise give up
678                         this.log.debug("Proxied connection. Need to start over.");
679                         break;
680                     }
681                 }
682 
683                 if (this.log.isDebugEnabled()) {
684                     this.log.debug("Attempt " + execCount + " to execute request");
685                 }
686                 response = requestExec.execute(wrapper, managedConn, context);
687                 break;
688 
689             } catch (final IOException ex) {
690                 this.log.debug("Closing the connection.");
691                 try {
692                     managedConn.close();
693                 } catch (final IOException ignore) {
694                 }
695                 if (retryHandler.retryRequest(ex, wrapper.getExecCount(), context)) {
696                     if (this.log.isInfoEnabled()) {
697                         this.log.info("I/O exception ("+ ex.getClass().getName() +
698                                 ") caught when processing request to "
699                                 + route +
700                                 ": "
701                                 + ex.getMessage());
702                     }
703                     if (this.log.isDebugEnabled()) {
704                         this.log.debug(ex.getMessage(), ex);
705                     }
706                     if (this.log.isInfoEnabled()) {
707                         this.log.info("Retrying request to " + route);
708                     }
709                     retryReason = ex;
710                 } else {
711                     if (ex instanceof NoHttpResponseException) {
712                         final NoHttpResponseException updatedex = new NoHttpResponseException(
713                                 route.getTargetHost().toHostString() + " failed to respond");
714                         updatedex.setStackTrace(ex.getStackTrace());
715                         throw updatedex;
716                     } else {
717                         throw ex;
718                     }
719                 }
720             }
721         }
722         return response;
723     }
724 
725     /**
726      * Returns the connection back to the connection manager
727      * and prepares for retrieving a new connection during
728      * the next request.
729      */
730     protected void releaseConnection() {
731         // Release the connection through the ManagedConnection instead of the
732         // ConnectionManager directly.  This lets the connection control how
733         // it is released.
734         try {
735             managedConn.releaseConnection();
736         } catch(final IOException ignored) {
737             this.log.debug("IOException releasing connection", ignored);
738         }
739         managedConn = null;
740     }
741 
742     /**
743      * Determines the route for a request.
744      * Called by {@link #execute}
745      * to determine the route for either the original or a followup request.
746      *
747      * @param targetHost  the target host for the request.
748      *                  Implementations may accept {@code null}
749      *                  if they can still determine a route, for example
750      *                  to a default target or by inspecting the request.
751      * @param request   the request to execute
752      * @param context   the context to use for the execution,
753      *                  never {@code null}
754      *
755      * @return  the route the request should take
756      *
757      * @throws HttpException    in case of a problem
758      */
759     protected HttpRoute determineRoute(final HttpHost targetHost,
760                                            final HttpRequest request,
761                                            final HttpContext context)
762         throws HttpException {
763         return this.routePlanner.determineRoute(
764                 targetHost != null ? targetHost : (HttpHost) request.getParams()
765                         .getParameter(ClientPNames.DEFAULT_HOST),
766                 request, context);
767     }
768 
769 
770     /**
771      * Establishes the target route.
772      *
773      * @param route     the route to establish
774      * @param context   the context for the request execution
775      *
776      * @throws HttpException    in case of a problem
777      * @throws IOException      in case of an IO problem
778      */
779     protected void establishRoute(final HttpRoute route, final HttpContext context)
780         throws HttpException, IOException {
781 
782         final HttpRouteDirector rowdy = new BasicRouteDirector();
783         int step;
784         do {
785             final HttpRoute fact = managedConn.getRoute();
786             step = rowdy.nextStep(route, fact);
787 
788             switch (step) {
789 
790             case HttpRouteDirector.CONNECT_TARGET:
791             case HttpRouteDirector.CONNECT_PROXY:
792                 managedConn.open(route, context, this.params);
793                 break;
794 
795             case HttpRouteDirector.TUNNEL_TARGET: {
796                 final boolean secure = createTunnelToTarget(route, context);
797                 this.log.debug("Tunnel to target created.");
798                 managedConn.tunnelTarget(secure, this.params);
799             }   break;
800 
801             case HttpRouteDirector.TUNNEL_PROXY: {
802                 // The most simple example for this case is a proxy chain
803                 // of two proxies, where P1 must be tunnelled to P2.
804                 // route: Source -> P1 -> P2 -> Target (3 hops)
805                 // fact:  Source -> P1 -> Target       (2 hops)
806                 final int hop = fact.getHopCount()-1; // the hop to establish
807                 final boolean secure = createTunnelToProxy(route, hop, context);
808                 this.log.debug("Tunnel to proxy created.");
809                 managedConn.tunnelProxy(route.getHopTarget(hop),
810                                         secure, this.params);
811             }   break;
812 
813 
814             case HttpRouteDirector.LAYER_PROTOCOL:
815                 managedConn.layerProtocol(context, this.params);
816                 break;
817 
818             case HttpRouteDirector.UNREACHABLE:
819                 throw new HttpException("Unable to establish route: " +
820                         "planned = " + route + "; current = " + fact);
821             case HttpRouteDirector.COMPLETE:
822                 // do nothing
823                 break;
824             default:
825                 throw new IllegalStateException("Unknown step indicator "
826                         + step + " from RouteDirector.");
827             }
828 
829         } while (step > HttpRouteDirector.COMPLETE);
830 
831     } // establishConnection
832 
833 
834     /**
835      * Creates a tunnel to the target server.
836      * The connection must be established to the (last) proxy.
837      * A CONNECT request for tunnelling through the proxy will
838      * be created and sent, the response received and checked.
839      * This method does <i>not</i> update the connection with
840      * information about the tunnel, that is left to the caller.
841      *
842      * @param route     the route to establish
843      * @param context   the context for request execution
844      *
845      * @return  {@code true} if the tunnelled route is secure,
846      *          {@code false} otherwise.
847      *          The implementation here always returns {@code false},
848      *          but derived classes may override.
849      *
850      * @throws HttpException    in case of a problem
851      * @throws IOException      in case of an IO problem
852      */
853     protected boolean createTunnelToTarget(final HttpRoute route,
854                                            final HttpContext context)
855         throws HttpException, IOException {
856 
857         final HttpHost proxy = route.getProxyHost();
858         final HttpHost target = route.getTargetHost();
859         HttpResponse response = null;
860 
861         for (;;) {
862             if (!this.managedConn.isOpen()) {
863                 this.managedConn.open(route, context, this.params);
864             }
865 
866             final HttpRequest connect = createConnectRequest(route, context);
867             connect.setParams(this.params);
868 
869             // Populate the execution context
870             context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
871             context.setAttribute(ClientContext.ROUTE, route);
872             context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, proxy);
873             context.setAttribute(ExecutionContext.HTTP_CONNECTION, managedConn);
874             context.setAttribute(ExecutionContext.HTTP_REQUEST, connect);
875 
876             this.requestExec.preProcess(connect, this.httpProcessor, context);
877 
878             response = this.requestExec.execute(connect, this.managedConn, context);
879 
880             response.setParams(this.params);
881             this.requestExec.postProcess(response, this.httpProcessor, context);
882 
883             final int status = response.getStatusLine().getStatusCode();
884             if (status < 200) {
885                 throw new HttpException("Unexpected response to CONNECT request: " +
886                         response.getStatusLine());
887             }
888 
889             if (HttpClientParams.isAuthenticating(this.params)) {
890                 if (this.authenticator.isAuthenticationRequested(proxy, response,
891                         this.proxyAuthStrategy, this.proxyAuthState, context)) {
892                     if (this.authenticator.authenticate(proxy, response,
893                             this.proxyAuthStrategy, this.proxyAuthState, context)) {
894                         // Retry request
895                         if (this.reuseStrategy.keepAlive(response, context)) {
896                             this.log.debug("Connection kept alive");
897                             // Consume response content
898                             final HttpEntity entity = response.getEntity();
899                             EntityUtils.consume(entity);
900                         } else {
901                             this.managedConn.close();
902                         }
903                     } else {
904                         break;
905                     }
906                 } else {
907                     break;
908                 }
909             }
910         }
911 
912         final int status = response.getStatusLine().getStatusCode();
913 
914         if (status > 299) {
915 
916             // Buffer response content
917             final HttpEntity entity = response.getEntity();
918             if (entity != null) {
919                 response.setEntity(new BufferedHttpEntity(entity));
920             }
921 
922             this.managedConn.close();
923             throw new TunnelRefusedException("CONNECT refused by proxy: " +
924                     response.getStatusLine(), response);
925         }
926 
927         this.managedConn.markReusable();
928 
929         // How to decide on security of the tunnelled connection?
930         // The socket factory knows only about the segment to the proxy.
931         // Even if that is secure, the hop to the target may be insecure.
932         // Leave it to derived classes, consider insecure by default here.
933         return false;
934 
935     } // createTunnelToTarget
936 
937 
938 
939     /**
940      * Creates a tunnel to an intermediate proxy.
941      * This method is <i>not</i> implemented in this class.
942      * It just throws an exception here.
943      *
944      * @param route     the route to establish
945      * @param hop       the hop in the route to establish now.
946      *                  {@code route.getHopTarget(hop)}
947      *                  will return the proxy to tunnel to.
948      * @param context   the context for request execution
949      *
950      * @return  {@code true} if the partially tunnelled connection
951      *          is secure, {@code false} otherwise.
952      *
953      * @throws HttpException    in case of a problem
954      * @throws IOException      in case of an IO problem
955      */
956     protected boolean createTunnelToProxy(final HttpRoute route, final int hop,
957                                           final HttpContext context)
958         throws HttpException, IOException {
959 
960         // Have a look at createTunnelToTarget and replicate the parts
961         // you need in a custom derived class. If your proxies don't require
962         // authentication, it is not too hard. But for the stock version of
963         // HttpClient, we cannot make such simplifying assumptions and would
964         // have to include proxy authentication code. The HttpComponents team
965         // is currently not in a position to support rarely used code of this
966         // complexity. Feel free to submit patches that refactor the code in
967         // createTunnelToTarget to facilitate re-use for proxy tunnelling.
968 
969         throw new HttpException("Proxy chains are not supported.");
970     }
971 
972 
973 
974     /**
975      * Creates the CONNECT request for tunnelling.
976      * Called by {@link #createTunnelToTarget createTunnelToTarget}.
977      *
978      * @param route     the route to establish
979      * @param context   the context for request execution
980      *
981      * @return  the CONNECT request for tunnelling
982      */
983     protected HttpRequest createConnectRequest(final HttpRoute route,
984                                                final HttpContext context) {
985         // see RFC 2817, section 5.2 and
986         // INTERNET-DRAFT: Tunneling TCP based protocols through
987         // Web proxy servers
988 
989         final HttpHost target = route.getTargetHost();
990 
991         final String host = target.getHostName();
992         int port = target.getPort();
993         if (port < 0) {
994             final Scheme scheme = connManager.getSchemeRegistry().
995                 getScheme(target.getSchemeName());
996             port = scheme.getDefaultPort();
997         }
998 
999         final StringBuilder buffer = new StringBuilder(host.length() + 6);
1000         buffer.append(host);
1001         buffer.append(':');
1002         buffer.append(Integer.toString(port));
1003 
1004         final String authority = buffer.toString();
1005         final ProtocolVersion ver = HttpProtocolParams.getVersion(params);
1006         final HttpRequest req = new BasicHttpRequest
1007             ("CONNECT", authority, ver);
1008 
1009         return req;
1010     }
1011 
1012 
1013     /**
1014      * Analyzes a response to check need for a followup.
1015      *
1016      * @param roureq    the request and route.
1017      * @param response  the response to analayze
1018      * @param context   the context used for the current request execution
1019      *
1020      * @return  the followup request and route if there is a followup, or
1021      *          {@code null} if the response should be returned as is
1022      *
1023      * @throws HttpException    in case of a problem
1024      * @throws IOException      in case of an IO problem
1025      */
1026     protected RoutedRequest handleResponse(final RoutedRequest roureq,
1027                                            final HttpResponse response,
1028                                            final HttpContext context)
1029         throws HttpException, IOException {
1030 
1031         final HttpRoute route = roureq.getRoute();
1032         final RequestWrapper request = roureq.getRequest();
1033 
1034         final HttpParams params = request.getParams();
1035 
1036         if (HttpClientParams.isAuthenticating(params)) {
1037             HttpHost target = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
1038             if (target == null) {
1039                 target = route.getTargetHost();
1040             }
1041             if (target.getPort() < 0) {
1042                 final Scheme scheme = connManager.getSchemeRegistry().getScheme(target);
1043                 target = new HttpHost(target.getHostName(), scheme.getDefaultPort(), target.getSchemeName());
1044             }
1045 
1046             final boolean targetAuthRequested = this.authenticator.isAuthenticationRequested(
1047                     target, response, this.targetAuthStrategy, targetAuthState, context);
1048 
1049             HttpHost proxy = route.getProxyHost();
1050             // if proxy is not set use target host instead
1051             if (proxy == null) {
1052                 proxy = route.getTargetHost();
1053             }
1054             final boolean proxyAuthRequested = this.authenticator.isAuthenticationRequested(
1055                     proxy, response, this.proxyAuthStrategy, proxyAuthState, context);
1056 
1057             if (targetAuthRequested) {
1058                 if (this.authenticator.authenticate(target, response,
1059                         this.targetAuthStrategy, this.targetAuthState, context)) {
1060                     // Re-try the same request via the same route
1061                     return roureq;
1062                 }
1063             }
1064             if (proxyAuthRequested) {
1065                 if (this.authenticator.authenticate(proxy, response,
1066                         this.proxyAuthStrategy, this.proxyAuthState, context)) {
1067                     // Re-try the same request via the same route
1068                     return roureq;
1069                 }
1070             }
1071         }
1072 
1073         if (HttpClientParams.isRedirecting(params) &&
1074                 this.redirectStrategy.isRedirected(request, response, context)) {
1075 
1076             if (redirectCount >= maxRedirects) {
1077                 throw new RedirectException("Maximum redirects ("
1078                         + maxRedirects + ") exceeded");
1079             }
1080             redirectCount++;
1081 
1082             // Virtual host cannot be used any longer
1083             virtualHost = null;
1084 
1085             final HttpUriRequest redirect = redirectStrategy.getRedirect(request, response, context);
1086             final HttpRequest orig = request.getOriginal();
1087             redirect.setHeaders(orig.getAllHeaders());
1088 
1089             final URI uri = redirect.getURI();
1090             final HttpHost newTarget = URIUtils.extractHost(uri);
1091             if (newTarget == null) {
1092                 throw new ProtocolException("Redirect URI does not specify a valid host name: " + uri);
1093             }
1094 
1095             // Reset auth states if redirecting to another host
1096             if (!route.getTargetHost().equals(newTarget)) {
1097                 this.log.debug("Resetting target auth state");
1098                 targetAuthState.reset();
1099                 final AuthScheme authScheme = proxyAuthState.getAuthScheme();
1100                 if (authScheme != null && authScheme.isConnectionBased()) {
1101                     this.log.debug("Resetting proxy auth state");
1102                     proxyAuthState.reset();
1103                 }
1104             }
1105 
1106             final RequestWrapper wrapper = wrapRequest(redirect);
1107             wrapper.setParams(params);
1108 
1109             final HttpRoute newRoute = determineRoute(newTarget, wrapper, context);
1110             final RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute);
1111 
1112             if (this.log.isDebugEnabled()) {
1113                 this.log.debug("Redirecting to '" + uri + "' via " + newRoute);
1114             }
1115 
1116             return newRequest;
1117         }
1118 
1119         return null;
1120     } // handleResponse
1121 
1122 
1123     /**
1124      * Shuts down the connection.
1125      * This method is called from a {@code catch} block in
1126      * {@link #execute execute} during exception handling.
1127      */
1128     private void abortConnection() {
1129         final ManagedClientConnection mcc = managedConn;
1130         if (mcc != null) {
1131             // we got here as the result of an exception
1132             // no response will be returned, release the connection
1133             managedConn = null;
1134             try {
1135                 mcc.abortConnection();
1136             } catch (final IOException ex) {
1137                 if (this.log.isDebugEnabled()) {
1138                     this.log.debug(ex.getMessage(), ex);
1139                 }
1140             }
1141             // ensure the connection manager properly releases this connection
1142             try {
1143                 mcc.releaseConnection();
1144             } catch(final IOException ignored) {
1145                 this.log.debug("Error releasing connection", ignored);
1146             }
1147         }
1148     } // abortConnection
1149 
1150 
1151 } // class DefaultClientRequestDirector