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