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.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.EntityUtils;
90  
91  /**
92   * Default implementation of {@link RequestDirector}.
93   * <p>
94   * The following parameters can be used to customize the behavior of this
95   * class:
96   * <ul>
97   *  <li>{@link org.apache.http.params.CoreProtocolPNames#PROTOCOL_VERSION}</li>
98   *  <li>{@link org.apache.http.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li>
99   *  <li>{@link org.apache.http.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li>
100  *  <li>{@link org.apache.http.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li>
101  *  <li>{@link org.apache.http.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li>
102  *  <li>{@link org.apache.http.params.CoreProtocolPNames#USER_AGENT}</li>
103  *  <li>{@link org.apache.http.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li>
104  *  <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li>
105  *  <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li>
106  *  <li>{@link org.apache.http.params.CoreConnectionPNames#SO_TIMEOUT}</li>
107  *  <li>{@link org.apache.http.params.CoreConnectionPNames#SO_LINGER}</li>
108  *  <li>{@link org.apache.http.params.CoreConnectionPNames#SO_REUSEADDR}</li>
109  *  <li>{@link org.apache.http.params.CoreConnectionPNames#TCP_NODELAY}</li>
110  *  <li>{@link org.apache.http.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li>
111  *  <li>{@link org.apache.http.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li>
112  *  <li>{@link org.apache.http.conn.params.ConnRoutePNames#FORCED_ROUTE}</li>
113  *  <li>{@link org.apache.http.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li>
114  *  <li>{@link org.apache.http.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li>
115  *  <li>{@link org.apache.http.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li>
116  *  <li>{@link org.apache.http.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li>
117  *  <li>{@link org.apache.http.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li>
118  *  <li>{@link org.apache.http.client.params.ClientPNames#COOKIE_POLICY}</li>
119  *  <li>{@link org.apache.http.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li>
120  *  <li>{@link org.apache.http.client.params.ClientPNames#HANDLE_REDIRECTS}</li>
121  *  <li>{@link org.apache.http.client.params.ClientPNames#MAX_REDIRECTS}</li>
122  *  <li>{@link org.apache.http.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li>
123  *  <li>{@link org.apache.http.client.params.ClientPNames#VIRTUAL_HOST}</li>
124  *  <li>{@link org.apache.http.client.params.ClientPNames#DEFAULT_HOST}</li>
125  *  <li>{@link org.apache.http.client.params.ClientPNames#DEFAULT_HEADERS}</li>
126  *  <li>{@link org.apache.http.client.params.ClientPNames#CONN_MANAGER_TIMEOUT}</li>
127  * </ul>
128  *
129  * @since 4.0
130  *
131  * @deprecated (4.3)
132  */
133 @Deprecated
134 public class DefaultRequestDirector implements RequestDirector {
135 
136     private final Log log;
137 
138     /** The connection manager. */
139     protected final ClientConnectionManager connManager;
140 
141     /** The route planner. */
142     protected final HttpRoutePlanner routePlanner;
143 
144     /** The connection re-use strategy. */
145     protected final ConnectionReuseStrategy reuseStrategy;
146 
147     /** The keep-alive duration strategy. */
148     protected final ConnectionKeepAliveStrategy keepAliveStrategy;
149 
150     /** The request executor. */
151     protected final HttpRequestExecutor requestExec;
152 
153     /** The HTTP protocol processor. */
154     protected final HttpProcessor httpProcessor;
155 
156     /** The request retry handler. */
157     protected final HttpRequestRetryHandler retryHandler;
158 
159     /** The redirect handler. */
160     @Deprecated
161     protected final RedirectHandler redirectHandler;
162 
163     /** The redirect strategy. */
164     protected final RedirectStrategy redirectStrategy;
165 
166     /** The target authentication handler. */
167     @Deprecated
168     protected final AuthenticationHandler targetAuthHandler;
169 
170     /** The target authentication handler. */
171     protected final AuthenticationStrategy targetAuthStrategy;
172 
173     /** The proxy authentication handler. */
174     @Deprecated
175     protected final AuthenticationHandler proxyAuthHandler;
176 
177     /** The proxy authentication handler. */
178     protected final AuthenticationStrategy proxyAuthStrategy;
179 
180     /** The user token handler. */
181     protected final UserTokenHandler userTokenHandler;
182 
183     /** The HTTP parameters. */
184     protected final HttpParams params;
185 
186     /** The currently allocated connection. */
187     protected ManagedClientConnection managedConn;
188 
189     protected final AuthState targetAuthState;
190 
191     protected final AuthState proxyAuthState;
192 
193     private final HttpAuthenticator authenticator;
194 
195     private int execCount;
196 
197     private int redirectCount;
198 
199     private final int maxRedirects;
200 
201     private HttpHost virtualHost;
202 
203     @Deprecated
204     public DefaultRequestDirector(
205             final HttpRequestExecutor requestExec,
206             final ClientConnectionManager conman,
207             final ConnectionReuseStrategy reustrat,
208             final ConnectionKeepAliveStrategy kastrat,
209             final HttpRoutePlanner rouplan,
210             final HttpProcessor httpProcessor,
211             final HttpRequestRetryHandler retryHandler,
212             final RedirectHandler redirectHandler,
213             final AuthenticationHandler targetAuthHandler,
214             final AuthenticationHandler proxyAuthHandler,
215             final UserTokenHandler userTokenHandler,
216             final HttpParams params) {
217         this(LogFactory.getLog(DefaultRequestDirector.class),
218                 requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler,
219                 new DefaultRedirectStrategyAdaptor(redirectHandler),
220                 new AuthenticationStrategyAdaptor(targetAuthHandler),
221                 new AuthenticationStrategyAdaptor(proxyAuthHandler),
222                 userTokenHandler,
223                 params);
224     }
225 
226 
227     @Deprecated
228     public DefaultRequestDirector(
229             final Log log,
230             final HttpRequestExecutor requestExec,
231             final ClientConnectionManager conman,
232             final ConnectionReuseStrategy reustrat,
233             final ConnectionKeepAliveStrategy kastrat,
234             final HttpRoutePlanner rouplan,
235             final HttpProcessor httpProcessor,
236             final HttpRequestRetryHandler retryHandler,
237             final RedirectStrategy redirectStrategy,
238             final AuthenticationHandler targetAuthHandler,
239             final AuthenticationHandler proxyAuthHandler,
240             final UserTokenHandler userTokenHandler,
241             final HttpParams params) {
242         this(LogFactory.getLog(DefaultRequestDirector.class),
243                 requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler,
244                 redirectStrategy,
245                 new AuthenticationStrategyAdaptor(targetAuthHandler),
246                 new AuthenticationStrategyAdaptor(proxyAuthHandler),
247                 userTokenHandler,
248                 params);
249     }
250 
251     /**
252      * @since 4.2
253      */
254     public DefaultRequestDirector(
255             final Log log,
256             final HttpRequestExecutor requestExec,
257             final ClientConnectionManager conman,
258             final ConnectionReuseStrategy reustrat,
259             final ConnectionKeepAliveStrategy kastrat,
260             final HttpRoutePlanner rouplan,
261             final HttpProcessor httpProcessor,
262             final HttpRequestRetryHandler retryHandler,
263             final RedirectStrategy redirectStrategy,
264             final AuthenticationStrategy targetAuthStrategy,
265             final AuthenticationStrategy proxyAuthStrategy,
266             final UserTokenHandler userTokenHandler,
267             final HttpParams params) {
268 
269         Args.notNull(log, "Log");
270         Args.notNull(requestExec, "Request executor");
271         Args.notNull(conman, "Client connection manager");
272         Args.notNull(reustrat, "Connection reuse strategy");
273         Args.notNull(kastrat, "Connection keep alive strategy");
274         Args.notNull(rouplan, "Route planner");
275         Args.notNull(httpProcessor, "HTTP protocol processor");
276         Args.notNull(retryHandler, "HTTP request retry handler");
277         Args.notNull(redirectStrategy, "Redirect strategy");
278         Args.notNull(targetAuthStrategy, "Target authentication strategy");
279         Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
280         Args.notNull(userTokenHandler, "User token handler");
281         Args.notNull(params, "HTTP parameters");
282         this.log               = log;
283         this.authenticator     = new HttpAuthenticator(log);
284         this.requestExec        = requestExec;
285         this.connManager        = conman;
286         this.reuseStrategy      = reustrat;
287         this.keepAliveStrategy  = kastrat;
288         this.routePlanner       = rouplan;
289         this.httpProcessor      = httpProcessor;
290         this.retryHandler       = retryHandler;
291         this.redirectStrategy   = redirectStrategy;
292         this.targetAuthStrategy = targetAuthStrategy;
293         this.proxyAuthStrategy  = proxyAuthStrategy;
294         this.userTokenHandler   = userTokenHandler;
295         this.params             = params;
296 
297         if (redirectStrategy instanceof DefaultRedirectStrategyAdaptor) {
298             this.redirectHandler = ((DefaultRedirectStrategyAdaptor) redirectStrategy).getHandler();
299         } else {
300             this.redirectHandler = null;
301         }
302         if (targetAuthStrategy instanceof AuthenticationStrategyAdaptor) {
303             this.targetAuthHandler = ((AuthenticationStrategyAdaptor) targetAuthStrategy).getHandler();
304         } else {
305             this.targetAuthHandler = null;
306         }
307         if (proxyAuthStrategy instanceof AuthenticationStrategyAdaptor) {
308             this.proxyAuthHandler = ((AuthenticationStrategyAdaptor) proxyAuthStrategy).getHandler();
309         } else {
310             this.proxyAuthHandler = null;
311         }
312 
313         this.managedConn = null;
314 
315         this.execCount = 0;
316         this.redirectCount = 0;
317         this.targetAuthState = new AuthState();
318         this.proxyAuthState = new AuthState();
319         this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100);
320     }
321 
322 
323     private RequestWrapper wrapRequest(
324             final HttpRequest request) throws ProtocolException {
325         if (request instanceof HttpEntityEnclosingRequest) {
326             return new EntityEnclosingRequestWrapper(
327                     (HttpEntityEnclosingRequest) request);
328         } else {
329             return new RequestWrapper(
330                     request);
331         }
332     }
333 
334 
335     protected void rewriteRequestURI(
336             final RequestWrapper request,
337             final HttpRoute route) throws ProtocolException {
338         try {
339 
340             URI uri = request.getURI();
341             if (route.getProxyHost() != null && !route.isTunnelled()) {
342                 // Make sure the request URI is absolute
343                 if (!uri.isAbsolute()) {
344                     final HttpHost target = route.getTargetHost();
345                     uri = URIUtils.rewriteURI(uri, target, true);
346                 } else {
347                     uri = URIUtils.rewriteURI(uri);
348                 }
349             } else {
350                 // Make sure the request URI is relative
351                 if (uri.isAbsolute()) {
352                     uri = URIUtils.rewriteURI(uri, null, true);
353                 } else {
354                     uri = URIUtils.rewriteURI(uri);
355                 }
356             }
357             request.setURI(uri);
358 
359         } catch (final URISyntaxException ex) {
360             throw new ProtocolException("Invalid URI: " +
361                     request.getRequestLine().getUri(), ex);
362         }
363     }
364 
365 
366     // non-javadoc, see interface ClientRequestDirector
367     @Override
368     public HttpResponse execute(final HttpHost targetHost, final HttpRequest request,
369                                 final HttpContext context)
370         throws HttpException, IOException {
371 
372         context.setAttribute(ClientContext.TARGET_AUTH_STATE, targetAuthState);
373         context.setAttribute(ClientContext.PROXY_AUTH_STATE, proxyAuthState);
374 
375         HttpHost target = targetHost;
376 
377         final HttpRequest orig = request;
378         final RequestWrapper origWrapper = wrapRequest(orig);
379         origWrapper.setParams(params);
380         final HttpRoute origRoute = determineRoute(target, origWrapper, context);
381 
382         virtualHost = (HttpHost) origWrapper.getParams().getParameter(ClientPNames.VIRTUAL_HOST);
383 
384         // HTTPCLIENT-1092 - add the port if necessary
385         if (virtualHost != null && virtualHost.getPort() == -1) {
386             final HttpHost host = (target != null) ? target : origRoute.getTargetHost();
387             final int port = host.getPort();
388             if (port != -1){
389                 virtualHost = new HttpHost(virtualHost.getHostName(), port, virtualHost.getSchemeName());
390             }
391         }
392 
393         RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute);
394 
395         boolean reuse = false;
396         boolean done = false;
397         try {
398             HttpResponse response = null;
399             while (!done) {
400                 // In this loop, the RoutedRequest may be replaced by a
401                 // followup request and route. The request and route passed
402                 // in the method arguments will be replaced. The original
403                 // request is still available in 'orig'.
404 
405                 final RequestWrapper wrapper = roureq.getRequest();
406                 final HttpRoute route = roureq.getRoute();
407                 response = null;
408 
409                 // See if we have a user token bound to the execution context
410                 Object userToken = context.getAttribute(ClientContext.USER_TOKEN);
411 
412                 // Allocate connection if needed
413                 if (managedConn == null) {
414                     final ClientConnectionRequest connRequest = connManager.requestConnection(
415                             route, userToken);
416                     if (orig instanceof AbortableHttpRequest) {
417                         ((AbortableHttpRequest) orig).setConnectionRequest(connRequest);
418                     }
419 
420                     final long timeout = HttpClientParams.getConnectionManagerTimeout(params);
421                     try {
422                         managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
423                     } catch(final InterruptedException interrupted) {
424                         Thread.currentThread().interrupt();
425                         throw new InterruptedIOException();
426                     }
427 
428                     if (HttpConnectionParams.isStaleCheckingEnabled(params)) {
429                         // validate connection
430                         if (managedConn.isOpen()) {
431                             this.log.debug("Stale connection check");
432                             if (managedConn.isStale()) {
433                                 this.log.debug("Stale connection detected");
434                                 managedConn.close();
435                             }
436                         }
437                     }
438                 }
439 
440                 if (orig instanceof AbortableHttpRequest) {
441                     ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn);
442                 }
443 
444                 try {
445                     tryConnect(roureq, context);
446                 } catch (final TunnelRefusedException ex) {
447                     if (this.log.isDebugEnabled()) {
448                         this.log.debug(ex.getMessage());
449                     }
450                     response = ex.getResponse();
451                     break;
452                 }
453 
454                 final String userinfo = wrapper.getURI().getUserInfo();
455                 if (userinfo != null) {
456                     targetAuthState.update(
457                             new BasicScheme(), new UsernamePasswordCredentials(userinfo));
458                 }
459 
460                 // Get target.  Even if there's virtual host, we may need the target to set the port.
461                 if (virtualHost != null) {
462                     target = virtualHost;
463                 } else {
464                     final URI requestURI = wrapper.getURI();
465                     if (requestURI.isAbsolute()) {
466                         target = URIUtils.extractHost(requestURI);
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                         final 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 "
625                                 + route +
626                                 ": "
627                                 + ex.getMessage());
628                         if (this.log.isDebugEnabled()) {
629                             this.log.debug(ex.getMessage(), ex);
630                         }
631                         this.log.info("Retrying connect to " + route);
632                     }
633                 } else {
634                     throw ex;
635                 }
636             }
637         }
638     }
639 
640     /**
641      * Execute request and retry in case of a recoverable I/O failure
642      */
643     private HttpResponse tryExecute(
644             final RoutedRequest req, final HttpContext context) throws HttpException, IOException {
645         final RequestWrapper wrapper = req.getRequest();
646         final HttpRoute route = req.getRoute();
647         HttpResponse response = null;
648 
649         Exception retryReason = null;
650         for (;;) {
651             // Increment total exec count (with redirects)
652             execCount++;
653             // Increment exec count for this particular request
654             wrapper.incrementExecCount();
655             if (!wrapper.isRepeatable()) {
656                 this.log.debug("Cannot retry non-repeatable request");
657                 if (retryReason != null) {
658                     throw new NonRepeatableRequestException("Cannot retry request " +
659                         "with a non-repeatable request entity.  The cause lists the " +
660                         "reason the original request failed.", retryReason);
661                 } else {
662                     throw new NonRepeatableRequestException("Cannot retry request " +
663                             "with a non-repeatable request entity.");
664                 }
665             }
666 
667             try {
668                 if (!managedConn.isOpen()) {
669                     // If we have a direct route to the target host
670                     // just re-open connection and re-try the request
671                     if (!route.isTunnelled()) {
672                         this.log.debug("Reopening the direct connection.");
673                         managedConn.open(route, context, params);
674                     } else {
675                         // otherwise give up
676                         this.log.debug("Proxied connection. Need to start over.");
677                         break;
678                     }
679                 }
680 
681                 if (this.log.isDebugEnabled()) {
682                     this.log.debug("Attempt " + execCount + " to execute request");
683                 }
684                 response = requestExec.execute(wrapper, managedConn, context);
685                 break;
686 
687             } catch (final IOException ex) {
688                 this.log.debug("Closing the connection.");
689                 try {
690                     managedConn.close();
691                 } catch (final IOException ignore) {
692                 }
693                 if (retryHandler.retryRequest(ex, wrapper.getExecCount(), context)) {
694                     if (this.log.isInfoEnabled()) {
695                         this.log.info("I/O exception ("+ ex.getClass().getName() +
696                                 ") caught when processing request to "
697                                 + route +
698                                 ": "
699                                 + ex.getMessage());
700                     }
701                     if (this.log.isDebugEnabled()) {
702                         this.log.debug(ex.getMessage(), ex);
703                     }
704                     if (this.log.isInfoEnabled()) {
705                         this.log.info("Retrying request to " + route);
706                     }
707                     retryReason = ex;
708                 } else {
709                     if (ex instanceof NoHttpResponseException) {
710                         final NoHttpResponseException updatedex = new NoHttpResponseException(
711                                 route.getTargetHost().toHostString() + " failed to respond");
712                         updatedex.setStackTrace(ex.getStackTrace());
713                         throw updatedex;
714                     } else {
715                         throw ex;
716                     }
717                 }
718             }
719         }
720         return response;
721     }
722 
723     /**
724      * Returns the connection back to the connection manager
725      * and prepares for retrieving a new connection during
726      * the next request.
727      */
728     protected void releaseConnection() {
729         // Release the connection through the ManagedConnection instead of the
730         // ConnectionManager directly.  This lets the connection control how
731         // it is released.
732         try {
733             managedConn.releaseConnection();
734         } catch(final IOException ignored) {
735             this.log.debug("IOException releasing connection", ignored);
736         }
737         managedConn = null;
738     }
739 
740     /**
741      * Determines the route for a request.
742      * Called by {@link #execute}
743      * to determine the route for either the original or a followup request.
744      *
745      * @param targetHost  the target host for the request.
746      *                  Implementations may accept {@code null}
747      *                  if they can still determine a route, for example
748      *                  to a default target or by inspecting the request.
749      * @param request   the request to execute
750      * @param context   the context to use for the execution,
751      *                  never {@code null}
752      *
753      * @return  the route the request should take
754      *
755      * @throws HttpException    in case of a problem
756      */
757     protected HttpRoute determineRoute(final HttpHost targetHost,
758                                            final HttpRequest request,
759                                            final HttpContext context)
760         throws HttpException {
761         return this.routePlanner.determineRoute(
762                 targetHost != null ? targetHost : (HttpHost) request.getParams()
763                         .getParameter(ClientPNames.DEFAULT_HOST),
764                 request, context);
765     }
766 
767 
768     /**
769      * Establishes the target route.
770      *
771      * @param route     the route to establish
772      * @param context   the context for the request execution
773      *
774      * @throws HttpException    in case of a problem
775      * @throws IOException      in case of an IO problem
776      */
777     protected void establishRoute(final HttpRoute route, final HttpContext context)
778         throws HttpException, IOException {
779 
780         final HttpRouteDirector rowdy = new BasicRouteDirector();
781         int step;
782         do {
783             final HttpRoute fact = managedConn.getRoute();
784             step = rowdy.nextStep(route, fact);
785 
786             switch (step) {
787 
788             case HttpRouteDirector.CONNECT_TARGET:
789             case HttpRouteDirector.CONNECT_PROXY:
790                 managedConn.open(route, context, this.params);
791                 break;
792 
793             case HttpRouteDirector.TUNNEL_TARGET: {
794                 final boolean secure = createTunnelToTarget(route, context);
795                 this.log.debug("Tunnel to target created.");
796                 managedConn.tunnelTarget(secure, this.params);
797             }   break;
798 
799             case HttpRouteDirector.TUNNEL_PROXY: {
800                 // The most simple example for this case is a proxy chain
801                 // of two proxies, where P1 must be tunnelled to P2.
802                 // route: Source -> P1 -> P2 -> Target (3 hops)
803                 // fact:  Source -> P1 -> Target       (2 hops)
804                 final int hop = fact.getHopCount()-1; // the hop to establish
805                 final boolean secure = createTunnelToProxy(route, hop, context);
806                 this.log.debug("Tunnel to proxy created.");
807                 managedConn.tunnelProxy(route.getHopTarget(hop),
808                                         secure, this.params);
809             }   break;
810 
811 
812             case HttpRouteDirector.LAYER_PROTOCOL:
813                 managedConn.layerProtocol(context, this.params);
814                 break;
815 
816             case HttpRouteDirector.UNREACHABLE:
817                 throw new HttpException("Unable to establish route: " +
818                         "planned = " + route + "; current = " + fact);
819             case HttpRouteDirector.COMPLETE:
820                 // do nothing
821                 break;
822             default:
823                 throw new IllegalStateException("Unknown step indicator "
824                         + step + " from RouteDirector.");
825             }
826 
827         } while (step > HttpRouteDirector.COMPLETE);
828 
829     } // establishConnection
830 
831 
832     /**
833      * Creates a tunnel to the target server.
834      * The connection must be established to the (last) proxy.
835      * A CONNECT request for tunnelling through the proxy will
836      * be created and sent, the response received and checked.
837      * This method does <i>not</i> update the connection with
838      * information about the tunnel, that is left to the caller.
839      *
840      * @param route     the route to establish
841      * @param context   the context for request execution
842      *
843      * @return  {@code true} if the tunnelled route is secure,
844      *          {@code false} otherwise.
845      *          The implementation here always returns {@code false},
846      *          but derived classes may override.
847      *
848      * @throws HttpException    in case of a problem
849      * @throws IOException      in case of an IO problem
850      */
851     protected boolean createTunnelToTarget(final HttpRoute route,
852                                            final HttpContext context)
853         throws HttpException, IOException {
854 
855         final HttpHost proxy = route.getProxyHost();
856         final HttpHost target = route.getTargetHost();
857         HttpResponse response = null;
858 
859         for (;;) {
860             if (!this.managedConn.isOpen()) {
861                 this.managedConn.open(route, context, this.params);
862             }
863 
864             final HttpRequest connect = createConnectRequest(route, context);
865             connect.setParams(this.params);
866 
867             // Populate the execution context
868             context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
869             context.setAttribute(ClientContext.ROUTE, route);
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)}
945      *                  will return the proxy to tunnel to.
946      * @param context   the context for request execution
947      *
948      * @return  {@code true} if the partially tunnelled connection
949      *          is secure, {@code false} 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} 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} 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