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