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