mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRequestDirector.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial