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