View Javadoc

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