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     public HttpResponse execute(final HttpHost targetHost, final HttpRequest request,
370                                 final HttpContext context)
371         throws HttpException, IOException {
372 
373         context.setAttribute(ClientContext.TARGET_AUTH_STATE, targetAuthState);
374         context.setAttribute(ClientContext.PROXY_AUTH_STATE, proxyAuthState);
375 
376         HttpHost target = targetHost;
377 
378         final HttpRequest orig = request;
379         final RequestWrapper origWrapper = wrapRequest(orig);
380         origWrapper.setParams(params);
381         final HttpRoute origRoute = determineRoute(target, origWrapper, context);
382 
383         virtualHost = (HttpHost) origWrapper.getParams().getParameter(ClientPNames.VIRTUAL_HOST);
384 
385         // HTTPCLIENT-1092 - add the port if necessary
386         if (virtualHost != null && virtualHost.getPort() == -1) {
387             final HttpHost host = (target != null) ? target : origRoute.getTargetHost();
388             final int port = host.getPort();
389             if (port != -1){
390                 virtualHost = new HttpHost(virtualHost.getHostName(), port, virtualHost.getSchemeName());
391             }
392         }
393 
394         RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute);
395 
396         boolean reuse = false;
397         boolean done = false;
398         try {
399             HttpResponse response = null;
400             while (!done) {
401                 // In this loop, the RoutedRequest may be replaced by a
402                 // followup request and route. The request and route passed
403                 // in the method arguments will be replaced. The original
404                 // request is still available in 'orig'.
405 
406                 final RequestWrapper wrapper = roureq.getRequest();
407                 final HttpRoute route = roureq.getRoute();
408                 response = null;
409 
410                 // See if we have a user token bound to the execution context
411                 Object userToken = context.getAttribute(ClientContext.USER_TOKEN);
412 
413                 // Allocate connection if needed
414                 if (managedConn == null) {
415                     final ClientConnectionRequest connRequest = connManager.requestConnection(
416                             route, userToken);
417                     if (orig instanceof AbortableHttpRequest) {
418                         ((AbortableHttpRequest) orig).setConnectionRequest(connRequest);
419                     }
420 
421                     final long timeout = HttpClientParams.getConnectionManagerTimeout(params);
422                     try {
423                         managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
424                     } catch(final InterruptedException interrupted) {
425                         Thread.currentThread().interrupt();
426                         throw new InterruptedIOException();
427                     }
428 
429                     if (HttpConnectionParams.isStaleCheckingEnabled(params)) {
430                         // validate connection
431                         if (managedConn.isOpen()) {
432                             this.log.debug("Stale connection check");
433                             if (managedConn.isStale()) {
434                                 this.log.debug("Stale connection detected");
435                                 managedConn.close();
436                             }
437                         }
438                     }
439                 }
440 
441                 if (orig instanceof AbortableHttpRequest) {
442                     ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn);
443                 }
444 
445                 try {
446                     tryConnect(roureq, context);
447                 } catch (final TunnelRefusedException ex) {
448                     if (this.log.isDebugEnabled()) {
449                         this.log.debug(ex.getMessage());
450                     }
451                     response = ex.getResponse();
452                     break;
453                 }
454 
455                 final String userinfo = wrapper.getURI().getUserInfo();
456                 if (userinfo != null) {
457                     targetAuthState.update(
458                             new BasicScheme(), new UsernamePasswordCredentials(userinfo));
459                 }
460 
461                 // Get target.  Even if there's virtual host, we may need the target to set the port.
462                 if (virtualHost != null) {
463                     target = virtualHost;
464                 } else {
465                     final URI requestURI = wrapper.getURI();
466                     if (requestURI.isAbsolute()) {
467                         target = URIUtils.extractHost(requestURI);
468                     }
469                 }
470                 if (target == null) {
471                     target = route.getTargetHost();
472                 }
473 
474                 // Reset headers on the request wrapper
475                 wrapper.resetHeaders();
476                 // Re-write request URI if needed
477                 rewriteRequestURI(wrapper, route);
478 
479                 // Populate the execution context
480                 context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
481                 context.setAttribute(ClientContext.ROUTE, route);
482                 context.setAttribute(ExecutionContext.HTTP_CONNECTION, managedConn);
483 
484                 // Run request protocol interceptors
485                 requestExec.preProcess(wrapper, httpProcessor, context);
486 
487                 response = tryExecute(roureq, context);
488                 if (response == null) {
489                     // Need to start over
490                     continue;
491                 }
492 
493                 // Run response protocol interceptors
494                 response.setParams(params);
495                 requestExec.postProcess(response, httpProcessor, context);
496 
497 
498                 // The connection is in or can be brought to a re-usable state.
499                 reuse = reuseStrategy.keepAlive(response, context);
500                 if (reuse) {
501                     // Set the idle duration of this connection
502                     final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
503                     if (this.log.isDebugEnabled()) {
504                         final String s;
505                         if (duration > 0) {
506                             s = "for " + duration + " " + TimeUnit.MILLISECONDS;
507                         } else {
508                             s = "indefinitely";
509                         }
510                         this.log.debug("Connection can be kept alive " + s);
511                     }
512                     managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS);
513                 }
514 
515                 final RoutedRequest followup = handleResponse(roureq, response, context);
516                 if (followup == null) {
517                     done = true;
518                 } else {
519                     if (reuse) {
520                         // Make sure the response body is fully consumed, if present
521                         final HttpEntity entity = response.getEntity();
522                         EntityUtils.consume(entity);
523                         // entity consumed above is not an auto-release entity,
524                         // need to mark the connection re-usable explicitly
525                         managedConn.markReusable();
526                     } else {
527                         managedConn.close();
528                         if (proxyAuthState.getState().compareTo(AuthProtocolState.CHALLENGED) > 0
529                                 && proxyAuthState.getAuthScheme() != null
530                                 && proxyAuthState.getAuthScheme().isConnectionBased()) {
531                             this.log.debug("Resetting proxy auth state");
532                             proxyAuthState.reset();
533                         }
534                         if (targetAuthState.getState().compareTo(AuthProtocolState.CHALLENGED) > 0
535                                 && targetAuthState.getAuthScheme() != null
536                                 && targetAuthState.getAuthScheme().isConnectionBased()) {
537                             this.log.debug("Resetting target auth state");
538                             targetAuthState.reset();
539                         }
540                     }
541                     // check if we can use the same connection for the followup
542                     if (!followup.getRoute().equals(roureq.getRoute())) {
543                         releaseConnection();
544                     }
545                     roureq = followup;
546                 }
547 
548                 if (managedConn != null) {
549                     if (userToken == null) {
550                         userToken = userTokenHandler.getUserToken(context);
551                         context.setAttribute(ClientContext.USER_TOKEN, userToken);
552                     }
553                     if (userToken != null) {
554                         managedConn.setState(userToken);
555                     }
556                 }
557 
558             } // while not done
559 
560 
561             // check for entity, release connection if possible
562             if ((response == null) || (response.getEntity() == null) ||
563                 !response.getEntity().isStreaming()) {
564                 // connection not needed and (assumed to be) in re-usable state
565                 if (reuse) {
566                     managedConn.markReusable();
567                 }
568                 releaseConnection();
569             } else {
570                 // install an auto-release entity
571                 HttpEntity entity = response.getEntity();
572                 entity = new BasicManagedEntity(entity, managedConn, reuse);
573                 response.setEntity(entity);
574             }
575 
576             return response;
577 
578         } catch (final ConnectionShutdownException ex) {
579             final InterruptedIOException ioex = new InterruptedIOException(
580                     "Connection has been shut down");
581             ioex.initCause(ex);
582             throw ioex;
583         } catch (final HttpException ex) {
584             abortConnection();
585             throw ex;
586         } catch (final IOException ex) {
587             abortConnection();
588             throw ex;
589         } catch (final RuntimeException ex) {
590             abortConnection();
591             throw ex;
592         }
593     } // execute
594 
595     /**
596      * Establish connection either directly or through a tunnel and retry in case of
597      * a recoverable I/O failure
598      */
599     private void tryConnect(
600             final RoutedRequest req, final HttpContext context) throws HttpException, IOException {
601         final HttpRoute route = req.getRoute();
602         final HttpRequest wrapper = req.getRequest();
603 
604         int connectCount = 0;
605         for (;;) {
606             context.setAttribute(ExecutionContext.HTTP_REQUEST, wrapper);
607             // Increment connect count
608             connectCount++;
609             try {
610                 if (!managedConn.isOpen()) {
611                     managedConn.open(route, context, params);
612                 } else {
613                     managedConn.setSocketTimeout(HttpConnectionParams.getSoTimeout(params));
614                 }
615                 establishRoute(route, context);
616                 break;
617             } catch (final IOException ex) {
618                 try {
619                     managedConn.close();
620                 } catch (final IOException ignore) {
621                 }
622                 if (retryHandler.retryRequest(ex, connectCount, context)) {
623                     if (this.log.isInfoEnabled()) {
624                         this.log.info("I/O exception ("+ ex.getClass().getName() +
625                                 ") caught when connecting to "
626                                 + route +
627                                 ": "
628                                 + ex.getMessage());
629                         if (this.log.isDebugEnabled()) {
630                             this.log.debug(ex.getMessage(), ex);
631                         }
632                         this.log.info("Retrying connect to " + route);
633                     }
634                 } else {
635                     throw ex;
636                 }
637             }
638         }
639     }
640 
641     /**
642      * Execute request and retry in case of a recoverable I/O failure
643      */
644     private HttpResponse tryExecute(
645             final RoutedRequest req, final HttpContext context) throws HttpException, IOException {
646         final RequestWrapper wrapper = req.getRequest();
647         final HttpRoute route = req.getRoute();
648         HttpResponse response = null;
649 
650         Exception retryReason = null;
651         for (;;) {
652             // Increment total exec count (with redirects)
653             execCount++;
654             // Increment exec count for this particular request
655             wrapper.incrementExecCount();
656             if (!wrapper.isRepeatable()) {
657                 this.log.debug("Cannot retry non-repeatable request");
658                 if (retryReason != null) {
659                     throw new NonRepeatableRequestException("Cannot retry request " +
660                         "with a non-repeatable request entity.  The cause lists the " +
661                         "reason the original request failed.", retryReason);
662                 } else {
663                     throw new NonRepeatableRequestException("Cannot retry request " +
664                             "with a non-repeatable request entity.");
665                 }
666             }
667 
668             try {
669                 if (!managedConn.isOpen()) {
670                     // If we have a direct route to the target host
671                     // just re-open connection and re-try the request
672                     if (!route.isTunnelled()) {
673                         this.log.debug("Reopening the direct connection.");
674                         managedConn.open(route, context, params);
675                     } else {
676                         // otherwise give up
677                         this.log.debug("Proxied connection. Need to start over.");
678                         break;
679                     }
680                 }
681 
682                 if (this.log.isDebugEnabled()) {
683                     this.log.debug("Attempt " + execCount + " to execute request");
684                 }
685                 response = requestExec.execute(wrapper, managedConn, context);
686                 break;
687 
688             } catch (final IOException ex) {
689                 this.log.debug("Closing the connection.");
690                 try {
691                     managedConn.close();
692                 } catch (final IOException ignore) {
693                 }
694                 if (retryHandler.retryRequest(ex, wrapper.getExecCount(), context)) {
695                     if (this.log.isInfoEnabled()) {
696                         this.log.info("I/O exception ("+ ex.getClass().getName() +
697                                 ") caught when processing request to "
698                                 + route +
699                                 ": "
700                                 + ex.getMessage());
701                     }
702                     if (this.log.isDebugEnabled()) {
703                         this.log.debug(ex.getMessage(), ex);
704                     }
705                     if (this.log.isInfoEnabled()) {
706                         this.log.info("Retrying request to " + route);
707                     }
708                     retryReason = ex;
709                 } else {
710                     if (ex instanceof NoHttpResponseException) {
711                         final NoHttpResponseException updatedex = new NoHttpResponseException(
712                                 route.getTargetHost().toHostString() + " failed to respond");
713                         updatedex.setStackTrace(ex.getStackTrace());
714                         throw updatedex;
715                     } else {
716                         throw ex;
717                     }
718                 }
719             }
720         }
721         return response;
722     }
723 
724     /**
725      * Returns the connection back to the connection manager
726      * and prepares for retrieving a new connection during
727      * the next request.
728      */
729     protected void releaseConnection() {
730         // Release the connection through the ManagedConnection instead of the
731         // ConnectionManager directly.  This lets the connection control how
732         // it is released.
733         try {
734             managedConn.releaseConnection();
735         } catch(final IOException ignored) {
736             this.log.debug("IOException releasing connection", ignored);
737         }
738         managedConn = null;
739     }
740 
741     /**
742      * Determines the route for a request.
743      * Called by {@link #execute}
744      * to determine the route for either the original or a followup request.
745      *
746      * @param targetHost  the target host for the request.
747      *                  Implementations may accept <code>null</code>
748      *                  if they can still determine a route, for example
749      *                  to a default target or by inspecting the request.
750      * @param request   the request to execute
751      * @param context   the context to use for the execution,
752      *                  never <code>null</code>
753      *
754      * @return  the route the request should take
755      *
756      * @throws HttpException    in case of a problem
757      */
758     protected HttpRoute determineRoute(final HttpHost targetHost,
759                                            final HttpRequest request,
760                                            final HttpContext context)
761         throws HttpException {
762         return this.routePlanner.determineRoute(
763                 targetHost != null ? targetHost : (HttpHost) request.getParams()
764                         .getParameter(ClientPNames.DEFAULT_HOST),
765                 request, context);
766     }
767 
768 
769     /**
770      * Establishes the target route.
771      *
772      * @param route     the route to establish
773      * @param context   the context for the request execution
774      *
775      * @throws HttpException    in case of a problem
776      * @throws IOException      in case of an IO problem
777      */
778     protected void establishRoute(final HttpRoute route, final HttpContext context)
779         throws HttpException, IOException {
780 
781         final HttpRouteDirector rowdy = new BasicRouteDirector();
782         int step;
783         do {
784             final HttpRoute fact = managedConn.getRoute();
785             step = rowdy.nextStep(route, fact);
786 
787             switch (step) {
788 
789             case HttpRouteDirector.CONNECT_TARGET:
790             case HttpRouteDirector.CONNECT_PROXY:
791                 managedConn.open(route, context, this.params);
792                 break;
793 
794             case HttpRouteDirector.TUNNEL_TARGET: {
795                 final boolean secure = createTunnelToTarget(route, context);
796                 this.log.debug("Tunnel to target created.");
797                 managedConn.tunnelTarget(secure, this.params);
798             }   break;
799 
800             case HttpRouteDirector.TUNNEL_PROXY: {
801                 // The most simple example for this case is a proxy chain
802                 // of two proxies, where P1 must be tunnelled to P2.
803                 // route: Source -> P1 -> P2 -> Target (3 hops)
804                 // fact:  Source -> P1 -> Target       (2 hops)
805                 final int hop = fact.getHopCount()-1; // the hop to establish
806                 final boolean secure = createTunnelToProxy(route, hop, context);
807                 this.log.debug("Tunnel to proxy created.");
808                 managedConn.tunnelProxy(route.getHopTarget(hop),
809                                         secure, this.params);
810             }   break;
811 
812 
813             case HttpRouteDirector.LAYER_PROTOCOL:
814                 managedConn.layerProtocol(context, this.params);
815                 break;
816 
817             case HttpRouteDirector.UNREACHABLE:
818                 throw new HttpException("Unable to establish route: " +
819                         "planned = " + route + "; current = " + fact);
820             case HttpRouteDirector.COMPLETE:
821                 // do nothing
822                 break;
823             default:
824                 throw new IllegalStateException("Unknown step indicator "
825                         + step + " from RouteDirector.");
826             }
827 
828         } while (step > HttpRouteDirector.COMPLETE);
829 
830     } // establishConnection
831 
832 
833     /**
834      * Creates a tunnel to the target server.
835      * The connection must be established to the (last) proxy.
836      * A CONNECT request for tunnelling through the proxy will
837      * be created and sent, the response received and checked.
838      * This method does <i>not</i> update the connection with
839      * information about the tunnel, that is left to the caller.
840      *
841      * @param route     the route to establish
842      * @param context   the context for request execution
843      *
844      * @return  <code>true</code> if the tunnelled route is secure,
845      *          <code>false</code> otherwise.
846      *          The implementation here always returns <code>false</code>,
847      *          but derived classes may override.
848      *
849      * @throws HttpException    in case of a problem
850      * @throws IOException      in case of an IO problem
851      */
852     protected boolean createTunnelToTarget(final HttpRoute route,
853                                            final HttpContext context)
854         throws HttpException, IOException {
855 
856         final HttpHost proxy = route.getProxyHost();
857         final HttpHost target = route.getTargetHost();
858         HttpResponse response = null;
859 
860         for (;;) {
861             if (!this.managedConn.isOpen()) {
862                 this.managedConn.open(route, context, this.params);
863             }
864 
865             final HttpRequest connect = createConnectRequest(route, context);
866             connect.setParams(this.params);
867 
868             // Populate the execution context
869             context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
870             context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, proxy);
871             context.setAttribute(ExecutionContext.HTTP_CONNECTION, managedConn);
872             context.setAttribute(ExecutionContext.HTTP_REQUEST, connect);
873 
874             this.requestExec.preProcess(connect, this.httpProcessor, context);
875 
876             response = this.requestExec.execute(connect, this.managedConn, context);
877 
878             response.setParams(this.params);
879             this.requestExec.postProcess(response, this.httpProcessor, context);
880 
881             final int status = response.getStatusLine().getStatusCode();
882             if (status < 200) {
883                 throw new HttpException("Unexpected response to CONNECT request: " +
884                         response.getStatusLine());
885             }
886 
887             if (HttpClientParams.isAuthenticating(this.params)) {
888                 if (this.authenticator.isAuthenticationRequested(proxy, response,
889                         this.proxyAuthStrategy, this.proxyAuthState, context)) {
890                     if (this.authenticator.authenticate(proxy, response,
891                             this.proxyAuthStrategy, this.proxyAuthState, context)) {
892                         // Retry request
893                         if (this.reuseStrategy.keepAlive(response, context)) {
894                             this.log.debug("Connection kept alive");
895                             // Consume response content
896                             final HttpEntity entity = response.getEntity();
897                             EntityUtils.consume(entity);
898                         } else {
899                             this.managedConn.close();
900                         }
901                     } else {
902                         break;
903                     }
904                 } else {
905                     break;
906                 }
907             }
908         }
909 
910         final int status = response.getStatusLine().getStatusCode();
911 
912         if (status > 299) {
913 
914             // Buffer response content
915             final HttpEntity entity = response.getEntity();
916             if (entity != null) {
917                 response.setEntity(new BufferedHttpEntity(entity));
918             }
919 
920             this.managedConn.close();
921             throw new TunnelRefusedException("CONNECT refused by proxy: " +
922                     response.getStatusLine(), response);
923         }
924 
925         this.managedConn.markReusable();
926 
927         // How to decide on security of the tunnelled connection?
928         // The socket factory knows only about the segment to the proxy.
929         // Even if that is secure, the hop to the target may be insecure.
930         // Leave it to derived classes, consider insecure by default here.
931         return false;
932 
933     } // createTunnelToTarget
934 
935 
936 
937     /**
938      * Creates a tunnel to an intermediate proxy.
939      * This method is <i>not</i> implemented in this class.
940      * It just throws an exception here.
941      *
942      * @param route     the route to establish
943      * @param hop       the hop in the route to establish now.
944      *                  <code>route.getHopTarget(hop)</code>
945      *                  will return the proxy to tunnel to.
946      * @param context   the context for request execution
947      *
948      * @return  <code>true</code> if the partially tunnelled connection
949      *          is secure, <code>false</code> otherwise.
950      *
951      * @throws HttpException    in case of a problem
952      * @throws IOException      in case of an IO problem
953      */
954     protected boolean createTunnelToProxy(final HttpRoute route, final int hop,
955                                           final HttpContext context)
956         throws HttpException, IOException {
957 
958         // Have a look at createTunnelToTarget and replicate the parts
959         // you need in a custom derived class. If your proxies don't require
960         // authentication, it is not too hard. But for the stock version of
961         // HttpClient, we cannot make such simplifying assumptions and would
962         // have to include proxy authentication code. The HttpComponents team
963         // is currently not in a position to support rarely used code of this
964         // complexity. Feel free to submit patches that refactor the code in
965         // createTunnelToTarget to facilitate re-use for proxy tunnelling.
966 
967         throw new HttpException("Proxy chains are not supported.");
968     }
969 
970 
971 
972     /**
973      * Creates the CONNECT request for tunnelling.
974      * Called by {@link #createTunnelToTarget createTunnelToTarget}.
975      *
976      * @param route     the route to establish
977      * @param context   the context for request execution
978      *
979      * @return  the CONNECT request for tunnelling
980      */
981     protected HttpRequest createConnectRequest(final HttpRoute route,
982                                                final HttpContext context) {
983         // see RFC 2817, section 5.2 and
984         // INTERNET-DRAFT: Tunneling TCP based protocols through
985         // Web proxy servers
986 
987         final HttpHost target = route.getTargetHost();
988 
989         final String host = target.getHostName();
990         int port = target.getPort();
991         if (port < 0) {
992             final Scheme scheme = connManager.getSchemeRegistry().
993                 getScheme(target.getSchemeName());
994             port = scheme.getDefaultPort();
995         }
996 
997         final StringBuilder buffer = new StringBuilder(host.length() + 6);
998         buffer.append(host);
999         buffer.append(':');
1000         buffer.append(Integer.toString(port));
1001 
1002         final String authority = buffer.toString();
1003         final ProtocolVersion ver = HttpProtocolParams.getVersion(params);
1004         final HttpRequest req = new BasicHttpRequest
1005             ("CONNECT", authority, ver);
1006 
1007         return req;
1008     }
1009 
1010 
1011     /**
1012      * Analyzes a response to check need for a followup.
1013      *
1014      * @param roureq    the request and route.
1015      * @param response  the response to analayze
1016      * @param context   the context used for the current request execution
1017      *
1018      * @return  the followup request and route if there is a followup, or
1019      *          <code>null</code> if the response should be returned as is
1020      *
1021      * @throws HttpException    in case of a problem
1022      * @throws IOException      in case of an IO problem
1023      */
1024     protected RoutedRequest handleResponse(final RoutedRequest roureq,
1025                                            final HttpResponse response,
1026                                            final HttpContext context)
1027         throws HttpException, IOException {
1028 
1029         final HttpRoute route = roureq.getRoute();
1030         final RequestWrapper request = roureq.getRequest();
1031 
1032         final HttpParams params = request.getParams();
1033 
1034         if (HttpClientParams.isAuthenticating(params)) {
1035             HttpHost target = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
1036             if (target == null) {
1037                 target = route.getTargetHost();
1038             }
1039             if (target.getPort() < 0) {
1040                 final Scheme scheme = connManager.getSchemeRegistry().getScheme(target);
1041                 target = new HttpHost(target.getHostName(), scheme.getDefaultPort(), target.getSchemeName());
1042             }
1043 
1044             final boolean targetAuthRequested = this.authenticator.isAuthenticationRequested(
1045                     target, response, this.targetAuthStrategy, targetAuthState, context);
1046 
1047             HttpHost proxy = route.getProxyHost();
1048             // if proxy is not set use target host instead
1049             if (proxy == null) {
1050                 proxy = route.getTargetHost();
1051             }
1052             final boolean proxyAuthRequested = this.authenticator.isAuthenticationRequested(
1053                     proxy, response, this.proxyAuthStrategy, proxyAuthState, context);
1054 
1055             if (targetAuthRequested) {
1056                 if (this.authenticator.authenticate(target, response,
1057                         this.targetAuthStrategy, this.targetAuthState, context)) {
1058                     // Re-try the same request via the same route
1059                     return roureq;
1060                 }
1061             }
1062             if (proxyAuthRequested) {
1063                 if (this.authenticator.authenticate(proxy, response,
1064                         this.proxyAuthStrategy, this.proxyAuthState, context)) {
1065                     // Re-try the same request via the same route
1066                     return roureq;
1067                 }
1068             }
1069         }
1070 
1071         if (HttpClientParams.isRedirecting(params) &&
1072                 this.redirectStrategy.isRedirected(request, response, context)) {
1073 
1074             if (redirectCount >= maxRedirects) {
1075                 throw new RedirectException("Maximum redirects ("
1076                         + maxRedirects + ") exceeded");
1077             }
1078             redirectCount++;
1079 
1080             // Virtual host cannot be used any longer
1081             virtualHost = null;
1082 
1083             final HttpUriRequest redirect = redirectStrategy.getRedirect(request, response, context);
1084             final HttpRequest orig = request.getOriginal();
1085             redirect.setHeaders(orig.getAllHeaders());
1086 
1087             final URI uri = redirect.getURI();
1088             final HttpHost newTarget = URIUtils.extractHost(uri);
1089             if (newTarget == null) {
1090                 throw new ProtocolException("Redirect URI does not specify a valid host name: " + uri);
1091             }
1092 
1093             // Reset auth states if redirecting to another host
1094             if (!route.getTargetHost().equals(newTarget)) {
1095                 this.log.debug("Resetting target auth state");
1096                 targetAuthState.reset();
1097                 final AuthScheme authScheme = proxyAuthState.getAuthScheme();
1098                 if (authScheme != null && authScheme.isConnectionBased()) {
1099                     this.log.debug("Resetting proxy auth state");
1100                     proxyAuthState.reset();
1101                 }
1102             }
1103 
1104             final RequestWrapper wrapper = wrapRequest(redirect);
1105             wrapper.setParams(params);
1106 
1107             final HttpRoute newRoute = determineRoute(newTarget, wrapper, context);
1108             final RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute);
1109 
1110             if (this.log.isDebugEnabled()) {
1111                 this.log.debug("Redirecting to '" + uri + "' via " + newRoute);
1112             }
1113 
1114             return newRequest;
1115         }
1116 
1117         return null;
1118     } // handleResponse
1119 
1120 
1121     /**
1122      * Shuts down the connection.
1123      * This method is called from a <code>catch</code> block in
1124      * {@link #execute execute} during exception handling.
1125      */
1126     private void abortConnection() {
1127         final ManagedClientConnection mcc = managedConn;
1128         if (mcc != null) {
1129             // we got here as the result of an exception
1130             // no response will be returned, release the connection
1131             managedConn = null;
1132             try {
1133                 mcc.abortConnection();
1134             } catch (final IOException ex) {
1135                 if (this.log.isDebugEnabled()) {
1136                     this.log.debug(ex.getMessage(), ex);
1137                 }
1138             }
1139             // ensure the connection manager properly releases this connection
1140             try {
1141                 mcc.releaseConnection();
1142             } catch(final IOException ignored) {
1143                 this.log.debug("Error releasing connection", ignored);
1144             }
1145         }
1146     } // abortConnection
1147 
1148 
1149 } // class DefaultClientRequestDirector