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