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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRequestDirector.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1256 @@
     1.4 +/*
     1.5 + * ====================================================================
     1.6 + * Licensed to the Apache Software Foundation (ASF) under one
     1.7 + * or more contributor license agreements.  See the NOTICE file
     1.8 + * distributed with this work for additional information
     1.9 + * regarding copyright ownership.  The ASF licenses this file
    1.10 + * to you under the Apache License, Version 2.0 (the
    1.11 + * "License"); you may not use this file except in compliance
    1.12 + * with the License.  You may obtain a copy of the License at
    1.13 + *
    1.14 + *   http://www.apache.org/licenses/LICENSE-2.0
    1.15 + *
    1.16 + * Unless required by applicable law or agreed to in writing,
    1.17 + * software distributed under the License is distributed on an
    1.18 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    1.19 + * KIND, either express or implied.  See the License for the
    1.20 + * specific language governing permissions and limitations
    1.21 + * under the License.
    1.22 + * ====================================================================
    1.23 + *
    1.24 + * This software consists of voluntary contributions made by many
    1.25 + * individuals on behalf of the Apache Software Foundation.  For more
    1.26 + * information on the Apache Software Foundation, please see
    1.27 + * <http://www.apache.org/>.
    1.28 + *
    1.29 + */
    1.30 +
    1.31 +package ch.boye.httpclientandroidlib.impl.client;
    1.32 +
    1.33 +import java.io.IOException;
    1.34 +import java.io.InterruptedIOException;
    1.35 +import java.net.URI;
    1.36 +import java.net.URISyntaxException;
    1.37 +import java.util.Locale;
    1.38 +import java.util.Map;
    1.39 +import java.util.concurrent.TimeUnit;
    1.40 +
    1.41 +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe;
    1.42 +
    1.43 +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
    1.44 +/* LogFactory removed by HttpClient for Android script. */
    1.45 +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy;
    1.46 +import ch.boye.httpclientandroidlib.Header;
    1.47 +import ch.boye.httpclientandroidlib.HttpEntity;
    1.48 +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest;
    1.49 +import ch.boye.httpclientandroidlib.HttpException;
    1.50 +import ch.boye.httpclientandroidlib.HttpHost;
    1.51 +import ch.boye.httpclientandroidlib.HttpRequest;
    1.52 +import ch.boye.httpclientandroidlib.HttpResponse;
    1.53 +import ch.boye.httpclientandroidlib.ProtocolException;
    1.54 +import ch.boye.httpclientandroidlib.ProtocolVersion;
    1.55 +import ch.boye.httpclientandroidlib.auth.AuthScheme;
    1.56 +import ch.boye.httpclientandroidlib.auth.AuthScope;
    1.57 +import ch.boye.httpclientandroidlib.auth.AuthState;
    1.58 +import ch.boye.httpclientandroidlib.auth.AuthenticationException;
    1.59 +import ch.boye.httpclientandroidlib.auth.Credentials;
    1.60 +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException;
    1.61 +import ch.boye.httpclientandroidlib.client.AuthenticationHandler;
    1.62 +import ch.boye.httpclientandroidlib.client.RedirectStrategy;
    1.63 +import ch.boye.httpclientandroidlib.client.RequestDirector;
    1.64 +import ch.boye.httpclientandroidlib.client.CredentialsProvider;
    1.65 +import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler;
    1.66 +import ch.boye.httpclientandroidlib.client.NonRepeatableRequestException;
    1.67 +import ch.boye.httpclientandroidlib.client.RedirectException;
    1.68 +import ch.boye.httpclientandroidlib.client.UserTokenHandler;
    1.69 +import ch.boye.httpclientandroidlib.client.methods.AbortableHttpRequest;
    1.70 +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
    1.71 +import ch.boye.httpclientandroidlib.client.params.ClientPNames;
    1.72 +import ch.boye.httpclientandroidlib.client.params.HttpClientParams;
    1.73 +import ch.boye.httpclientandroidlib.client.protocol.ClientContext;
    1.74 +import ch.boye.httpclientandroidlib.client.utils.URIUtils;
    1.75 +import ch.boye.httpclientandroidlib.conn.BasicManagedEntity;
    1.76 +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager;
    1.77 +import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest;
    1.78 +import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy;
    1.79 +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection;
    1.80 +import ch.boye.httpclientandroidlib.conn.params.ConnManagerParams;
    1.81 +import ch.boye.httpclientandroidlib.conn.routing.BasicRouteDirector;
    1.82 +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute;
    1.83 +import ch.boye.httpclientandroidlib.conn.routing.HttpRouteDirector;
    1.84 +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner;
    1.85 +import ch.boye.httpclientandroidlib.conn.scheme.Scheme;
    1.86 +import ch.boye.httpclientandroidlib.entity.BufferedHttpEntity;
    1.87 +import ch.boye.httpclientandroidlib.impl.conn.ConnectionShutdownException;
    1.88 +import ch.boye.httpclientandroidlib.message.BasicHttpRequest;
    1.89 +import ch.boye.httpclientandroidlib.params.HttpConnectionParams;
    1.90 +import ch.boye.httpclientandroidlib.params.HttpParams;
    1.91 +import ch.boye.httpclientandroidlib.params.HttpProtocolParams;
    1.92 +import ch.boye.httpclientandroidlib.protocol.ExecutionContext;
    1.93 +import ch.boye.httpclientandroidlib.protocol.HttpContext;
    1.94 +import ch.boye.httpclientandroidlib.protocol.HttpProcessor;
    1.95 +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor;
    1.96 +import ch.boye.httpclientandroidlib.util.EntityUtils;
    1.97 +
    1.98 +/**
    1.99 + * Default implementation of {@link RequestDirector}.
   1.100 + * <p>
   1.101 + * The following parameters can be used to customize the behavior of this
   1.102 + * class:
   1.103 + * <ul>
   1.104 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}</li>
   1.105 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li>
   1.106 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li>
   1.107 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li>
   1.108 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li>
   1.109 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}</li>
   1.110 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li>
   1.111 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li>
   1.112 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li>
   1.113 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li>
   1.114 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}</li>
   1.115 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li>
   1.116 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}</li>
   1.117 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li>
   1.118 + *  <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li>
   1.119 + *  <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li>
   1.120 + *  <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li>
   1.121 + *  <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li>
   1.122 + *  <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li>
   1.123 + *  <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li>
   1.124 + *  <li>{@link ch.boye.httpclientandroidlib.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li>
   1.125 + *  <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#COOKIE_POLICY}</li>
   1.126 + *  <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li>
   1.127 + *  <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_REDIRECTS}</li>
   1.128 + *  <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#MAX_REDIRECTS}</li>
   1.129 + *  <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li>
   1.130 + *  <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#VIRTUAL_HOST}</li>
   1.131 + *  <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HOST}</li>
   1.132 + *  <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HEADERS}</li>
   1.133 + * </ul>
   1.134 + *
   1.135 + * @since 4.0
   1.136 + */
   1.137 +@SuppressWarnings("deprecation")
   1.138 +@NotThreadSafe // e.g. managedConn
   1.139 +public class DefaultRequestDirector implements RequestDirector {
   1.140 +
   1.141 +    public HttpClientAndroidLog log;
   1.142 +
   1.143 +    /** The connection manager. */
   1.144 +    protected final ClientConnectionManager connManager;
   1.145 +
   1.146 +    /** The route planner. */
   1.147 +    protected final HttpRoutePlanner routePlanner;
   1.148 +
   1.149 +    /** The connection re-use strategy. */
   1.150 +    protected final ConnectionReuseStrategy reuseStrategy;
   1.151 +
   1.152 +    /** The keep-alive duration strategy. */
   1.153 +    protected final ConnectionKeepAliveStrategy keepAliveStrategy;
   1.154 +
   1.155 +    /** The request executor. */
   1.156 +    protected final HttpRequestExecutor requestExec;
   1.157 +
   1.158 +    /** The HTTP protocol processor. */
   1.159 +    protected final HttpProcessor httpProcessor;
   1.160 +
   1.161 +    /** The request retry handler. */
   1.162 +    protected final HttpRequestRetryHandler retryHandler;
   1.163 +
   1.164 +    /** The redirect handler. */
   1.165 +    @Deprecated
   1.166 +    protected final ch.boye.httpclientandroidlib.client.RedirectHandler redirectHandler = null;
   1.167 +
   1.168 +    /** The redirect strategy. */
   1.169 +    protected final RedirectStrategy redirectStrategy;
   1.170 +
   1.171 +    /** The target authentication handler. */
   1.172 +    protected final AuthenticationHandler targetAuthHandler;
   1.173 +
   1.174 +    /** The proxy authentication handler. */
   1.175 +    protected final AuthenticationHandler proxyAuthHandler;
   1.176 +
   1.177 +    /** The user token handler. */
   1.178 +    protected final UserTokenHandler userTokenHandler;
   1.179 +
   1.180 +    /** The HTTP parameters. */
   1.181 +    protected final HttpParams params;
   1.182 +
   1.183 +    /** The currently allocated connection. */
   1.184 +    protected ManagedClientConnection managedConn;
   1.185 +
   1.186 +    protected final AuthState targetAuthState;
   1.187 +
   1.188 +    protected final AuthState proxyAuthState;
   1.189 +
   1.190 +    private int execCount;
   1.191 +
   1.192 +    private int redirectCount;
   1.193 +
   1.194 +    private int maxRedirects;
   1.195 +
   1.196 +    private HttpHost virtualHost;
   1.197 +
   1.198 +    @Deprecated
   1.199 +    public DefaultRequestDirector(
   1.200 +            final HttpRequestExecutor requestExec,
   1.201 +            final ClientConnectionManager conman,
   1.202 +            final ConnectionReuseStrategy reustrat,
   1.203 +            final ConnectionKeepAliveStrategy kastrat,
   1.204 +            final HttpRoutePlanner rouplan,
   1.205 +            final HttpProcessor httpProcessor,
   1.206 +            final HttpRequestRetryHandler retryHandler,
   1.207 +            final ch.boye.httpclientandroidlib.client.RedirectHandler redirectHandler,
   1.208 +            final AuthenticationHandler targetAuthHandler,
   1.209 +            final AuthenticationHandler proxyAuthHandler,
   1.210 +            final UserTokenHandler userTokenHandler,
   1.211 +            final HttpParams params) {
   1.212 +        this(new HttpClientAndroidLog(DefaultRequestDirector.class),
   1.213 +                requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler,
   1.214 +                new DefaultRedirectStrategyAdaptor(redirectHandler),
   1.215 +                targetAuthHandler, proxyAuthHandler, userTokenHandler, params);
   1.216 +    }
   1.217 +
   1.218 +
   1.219 +    /**
   1.220 +     * @since 4.1
   1.221 +     */
   1.222 +    public DefaultRequestDirector(
   1.223 +            final HttpClientAndroidLog log,
   1.224 +            final HttpRequestExecutor requestExec,
   1.225 +            final ClientConnectionManager conman,
   1.226 +            final ConnectionReuseStrategy reustrat,
   1.227 +            final ConnectionKeepAliveStrategy kastrat,
   1.228 +            final HttpRoutePlanner rouplan,
   1.229 +            final HttpProcessor httpProcessor,
   1.230 +            final HttpRequestRetryHandler retryHandler,
   1.231 +            final RedirectStrategy redirectStrategy,
   1.232 +            final AuthenticationHandler targetAuthHandler,
   1.233 +            final AuthenticationHandler proxyAuthHandler,
   1.234 +            final UserTokenHandler userTokenHandler,
   1.235 +            final HttpParams params) {
   1.236 +
   1.237 +        if (log == null) {
   1.238 +            throw new IllegalArgumentException
   1.239 +                ("Log may not be null.");
   1.240 +        }
   1.241 +        if (requestExec == null) {
   1.242 +            throw new IllegalArgumentException
   1.243 +                ("Request executor may not be null.");
   1.244 +        }
   1.245 +        if (conman == null) {
   1.246 +            throw new IllegalArgumentException
   1.247 +                ("Client connection manager may not be null.");
   1.248 +        }
   1.249 +        if (reustrat == null) {
   1.250 +            throw new IllegalArgumentException
   1.251 +                ("Connection reuse strategy may not be null.");
   1.252 +        }
   1.253 +        if (kastrat == null) {
   1.254 +            throw new IllegalArgumentException
   1.255 +                ("Connection keep alive strategy may not be null.");
   1.256 +        }
   1.257 +        if (rouplan == null) {
   1.258 +            throw new IllegalArgumentException
   1.259 +                ("Route planner may not be null.");
   1.260 +        }
   1.261 +        if (httpProcessor == null) {
   1.262 +            throw new IllegalArgumentException
   1.263 +                ("HTTP protocol processor may not be null.");
   1.264 +        }
   1.265 +        if (retryHandler == null) {
   1.266 +            throw new IllegalArgumentException
   1.267 +                ("HTTP request retry handler may not be null.");
   1.268 +        }
   1.269 +        if (redirectStrategy == null) {
   1.270 +            throw new IllegalArgumentException
   1.271 +                ("Redirect strategy may not be null.");
   1.272 +        }
   1.273 +        if (targetAuthHandler == null) {
   1.274 +            throw new IllegalArgumentException
   1.275 +                ("Target authentication handler may not be null.");
   1.276 +        }
   1.277 +        if (proxyAuthHandler == null) {
   1.278 +            throw new IllegalArgumentException
   1.279 +                ("Proxy authentication handler may not be null.");
   1.280 +        }
   1.281 +        if (userTokenHandler == null) {
   1.282 +            throw new IllegalArgumentException
   1.283 +                ("User token handler may not be null.");
   1.284 +        }
   1.285 +        if (params == null) {
   1.286 +            throw new IllegalArgumentException
   1.287 +                ("HTTP parameters may not be null");
   1.288 +        }
   1.289 +        this.log               = log;
   1.290 +        this.requestExec       = requestExec;
   1.291 +        this.connManager       = conman;
   1.292 +        this.reuseStrategy     = reustrat;
   1.293 +        this.keepAliveStrategy = kastrat;
   1.294 +        this.routePlanner      = rouplan;
   1.295 +        this.httpProcessor     = httpProcessor;
   1.296 +        this.retryHandler      = retryHandler;
   1.297 +        this.redirectStrategy  = redirectStrategy;
   1.298 +        this.targetAuthHandler = targetAuthHandler;
   1.299 +        this.proxyAuthHandler  = proxyAuthHandler;
   1.300 +        this.userTokenHandler  = userTokenHandler;
   1.301 +        this.params            = params;
   1.302 +
   1.303 +        this.managedConn       = null;
   1.304 +
   1.305 +        this.execCount = 0;
   1.306 +        this.redirectCount = 0;
   1.307 +        this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100);
   1.308 +        this.targetAuthState = new AuthState();
   1.309 +        this.proxyAuthState = new AuthState();
   1.310 +    } // constructor
   1.311 +
   1.312 +
   1.313 +    private RequestWrapper wrapRequest(
   1.314 +            final HttpRequest request) throws ProtocolException {
   1.315 +        if (request instanceof HttpEntityEnclosingRequest) {
   1.316 +            return new EntityEnclosingRequestWrapper(
   1.317 +                    (HttpEntityEnclosingRequest) request);
   1.318 +        } else {
   1.319 +            return new RequestWrapper(
   1.320 +                    request);
   1.321 +        }
   1.322 +    }
   1.323 +
   1.324 +
   1.325 +    protected void rewriteRequestURI(
   1.326 +            final RequestWrapper request,
   1.327 +            final HttpRoute route) throws ProtocolException {
   1.328 +        try {
   1.329 +
   1.330 +            URI uri = request.getURI();
   1.331 +            if (route.getProxyHost() != null && !route.isTunnelled()) {
   1.332 +                // Make sure the request URI is absolute
   1.333 +                if (!uri.isAbsolute()) {
   1.334 +                    HttpHost target = route.getTargetHost();
   1.335 +                    uri = URIUtils.rewriteURI(uri, target);
   1.336 +                    request.setURI(uri);
   1.337 +                }
   1.338 +            } else {
   1.339 +                // Make sure the request URI is relative
   1.340 +                if (uri.isAbsolute()) {
   1.341 +                    uri = URIUtils.rewriteURI(uri, null);
   1.342 +                    request.setURI(uri);
   1.343 +                }
   1.344 +            }
   1.345 +
   1.346 +        } catch (URISyntaxException ex) {
   1.347 +            throw new ProtocolException("Invalid URI: " +
   1.348 +                    request.getRequestLine().getUri(), ex);
   1.349 +        }
   1.350 +    }
   1.351 +
   1.352 +
   1.353 +    // non-javadoc, see interface ClientRequestDirector
   1.354 +    public HttpResponse execute(HttpHost target, HttpRequest request,
   1.355 +                                HttpContext context)
   1.356 +        throws HttpException, IOException {
   1.357 +
   1.358 +        HttpRequest orig = request;
   1.359 +        RequestWrapper origWrapper = wrapRequest(orig);
   1.360 +        origWrapper.setParams(params);
   1.361 +        HttpRoute origRoute = determineRoute(target, origWrapper, context);
   1.362 +
   1.363 +        virtualHost = (HttpHost) orig.getParams().getParameter(
   1.364 +                ClientPNames.VIRTUAL_HOST);
   1.365 +        
   1.366 +        // HTTPCLIENT-1092 - add the port if necessary
   1.367 +        if (virtualHost != null && virtualHost.getPort() == -1) 
   1.368 +        {
   1.369 +            int port = target.getPort();
   1.370 +            if (port != -1){
   1.371 +                virtualHost = new HttpHost(virtualHost.getHostName(), port, virtualHost.getSchemeName());
   1.372 +            }
   1.373 +        }
   1.374 +
   1.375 +        RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute);
   1.376 +
   1.377 +        boolean reuse = false;
   1.378 +        boolean done = false;
   1.379 +        try {
   1.380 +            HttpResponse response = null;
   1.381 +            while (!done) {
   1.382 +                // In this loop, the RoutedRequest may be replaced by a
   1.383 +                // followup request and route. The request and route passed
   1.384 +                // in the method arguments will be replaced. The original
   1.385 +                // request is still available in 'orig'.
   1.386 +
   1.387 +                RequestWrapper wrapper = roureq.getRequest();
   1.388 +                HttpRoute route = roureq.getRoute();
   1.389 +                response = null;
   1.390 +
   1.391 +                // See if we have a user token bound to the execution context
   1.392 +                Object userToken = context.getAttribute(ClientContext.USER_TOKEN);
   1.393 +
   1.394 +                // Allocate connection if needed
   1.395 +                if (managedConn == null) {
   1.396 +                    ClientConnectionRequest connRequest = connManager.requestConnection(
   1.397 +                            route, userToken);
   1.398 +                    if (orig instanceof AbortableHttpRequest) {
   1.399 +                        ((AbortableHttpRequest) orig).setConnectionRequest(connRequest);
   1.400 +                    }
   1.401 +
   1.402 +                    long timeout = ConnManagerParams.getTimeout(params);
   1.403 +                    try {
   1.404 +                        managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
   1.405 +                    } catch(InterruptedException interrupted) {
   1.406 +                        InterruptedIOException iox = new InterruptedIOException();
   1.407 +                        iox.initCause(interrupted);
   1.408 +                        throw iox;
   1.409 +                    }
   1.410 +
   1.411 +                    if (HttpConnectionParams.isStaleCheckingEnabled(params)) {
   1.412 +                        // validate connection
   1.413 +                        if (managedConn.isOpen()) {
   1.414 +                            this.log.debug("Stale connection check");
   1.415 +                            if (managedConn.isStale()) {
   1.416 +                                this.log.debug("Stale connection detected");
   1.417 +                                managedConn.close();
   1.418 +                            }
   1.419 +                        }
   1.420 +                    }
   1.421 +                }
   1.422 +
   1.423 +                if (orig instanceof AbortableHttpRequest) {
   1.424 +                    ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn);
   1.425 +                }
   1.426 +
   1.427 +                try {
   1.428 +                    tryConnect(roureq, context);
   1.429 +                } catch (TunnelRefusedException ex) {
   1.430 +                    if (this.log.isDebugEnabled()) {
   1.431 +                        this.log.debug(ex.getMessage());
   1.432 +                    }
   1.433 +                    response = ex.getResponse();
   1.434 +                    break;
   1.435 +                }
   1.436 +
   1.437 +                // Reset headers on the request wrapper
   1.438 +                wrapper.resetHeaders();
   1.439 +
   1.440 +                // Re-write request URI if needed
   1.441 +                rewriteRequestURI(wrapper, route);
   1.442 +
   1.443 +                // Use virtual host if set
   1.444 +                target = virtualHost;
   1.445 +
   1.446 +                if (target == null) {
   1.447 +                    target = route.getTargetHost();
   1.448 +                }
   1.449 +
   1.450 +                HttpHost proxy = route.getProxyHost();
   1.451 +
   1.452 +                // Populate the execution context
   1.453 +                context.setAttribute(ExecutionContext.HTTP_TARGET_HOST,
   1.454 +                        target);
   1.455 +                context.setAttribute(ExecutionContext.HTTP_PROXY_HOST,
   1.456 +                        proxy);
   1.457 +                context.setAttribute(ExecutionContext.HTTP_CONNECTION,
   1.458 +                        managedConn);
   1.459 +                context.setAttribute(ClientContext.TARGET_AUTH_STATE,
   1.460 +                        targetAuthState);
   1.461 +                context.setAttribute(ClientContext.PROXY_AUTH_STATE,
   1.462 +                        proxyAuthState);
   1.463 +
   1.464 +                // Run request protocol interceptors
   1.465 +                requestExec.preProcess(wrapper, httpProcessor, context);
   1.466 +
   1.467 +                response = tryExecute(roureq, context);
   1.468 +                if (response == null) {
   1.469 +                    // Need to start over
   1.470 +                    continue;
   1.471 +                }
   1.472 +
   1.473 +                // Run response protocol interceptors
   1.474 +                response.setParams(params);
   1.475 +                requestExec.postProcess(response, httpProcessor, context);
   1.476 +
   1.477 +
   1.478 +                // The connection is in or can be brought to a re-usable state.
   1.479 +                reuse = reuseStrategy.keepAlive(response, context);
   1.480 +                if (reuse) {
   1.481 +                    // Set the idle duration of this connection
   1.482 +                    long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
   1.483 +                    if (this.log.isDebugEnabled()) {
   1.484 +                        String s;
   1.485 +                        if (duration > 0) {
   1.486 +                            s = "for " + duration + " " + TimeUnit.MILLISECONDS;
   1.487 +                        } else {
   1.488 +                            s = "indefinitely";
   1.489 +                        }
   1.490 +                        this.log.debug("Connection can be kept alive " + s);
   1.491 +                    }
   1.492 +                    managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS);
   1.493 +                }
   1.494 +
   1.495 +                RoutedRequest followup = handleResponse(roureq, response, context);
   1.496 +                if (followup == null) {
   1.497 +                    done = true;
   1.498 +                } else {
   1.499 +                    if (reuse) {
   1.500 +                        // Make sure the response body is fully consumed, if present
   1.501 +                        HttpEntity entity = response.getEntity();
   1.502 +                        EntityUtils.consume(entity);
   1.503 +                        // entity consumed above is not an auto-release entity,
   1.504 +                        // need to mark the connection re-usable explicitly
   1.505 +                        managedConn.markReusable();
   1.506 +                    } else {
   1.507 +                        managedConn.close();
   1.508 +                        invalidateAuthIfSuccessful(this.proxyAuthState);                        
   1.509 +                        invalidateAuthIfSuccessful(this.targetAuthState);                        
   1.510 +                    }
   1.511 +                    // check if we can use the same connection for the followup
   1.512 +                    if (!followup.getRoute().equals(roureq.getRoute())) {
   1.513 +                        releaseConnection();
   1.514 +                    }
   1.515 +                    roureq = followup;
   1.516 +                }
   1.517 +
   1.518 +                if (managedConn != null && userToken == null) {
   1.519 +                    userToken = userTokenHandler.getUserToken(context);
   1.520 +                    context.setAttribute(ClientContext.USER_TOKEN, userToken);
   1.521 +                    if (userToken != null) {
   1.522 +                        managedConn.setState(userToken);
   1.523 +                    }
   1.524 +                }
   1.525 +
   1.526 +            } // while not done
   1.527 +
   1.528 +
   1.529 +            // check for entity, release connection if possible
   1.530 +            if ((response == null) || (response.getEntity() == null) ||
   1.531 +                !response.getEntity().isStreaming()) {
   1.532 +                // connection not needed and (assumed to be) in re-usable state
   1.533 +                if (reuse)
   1.534 +                    managedConn.markReusable();
   1.535 +                releaseConnection();
   1.536 +            } else {
   1.537 +                // install an auto-release entity
   1.538 +                HttpEntity entity = response.getEntity();
   1.539 +                entity = new BasicManagedEntity(entity, managedConn, reuse);
   1.540 +                response.setEntity(entity);
   1.541 +            }
   1.542 +
   1.543 +            return response;
   1.544 +
   1.545 +        } catch (ConnectionShutdownException ex) {
   1.546 +            InterruptedIOException ioex = new InterruptedIOException(
   1.547 +                    "Connection has been shut down");
   1.548 +            ioex.initCause(ex);
   1.549 +            throw ioex;
   1.550 +        } catch (HttpException ex) {
   1.551 +            abortConnection();
   1.552 +            throw ex;
   1.553 +        } catch (IOException ex) {
   1.554 +            abortConnection();
   1.555 +            throw ex;
   1.556 +        } catch (RuntimeException ex) {
   1.557 +            abortConnection();
   1.558 +            throw ex;
   1.559 +        }
   1.560 +    } // execute
   1.561 +
   1.562 +    /**
   1.563 +     * Establish connection either directly or through a tunnel and retry in case of
   1.564 +     * a recoverable I/O failure
   1.565 +     */
   1.566 +    private void tryConnect(
   1.567 +            final RoutedRequest req, final HttpContext context) throws HttpException, IOException {
   1.568 +        HttpRoute route = req.getRoute();
   1.569 +
   1.570 +        int connectCount = 0;
   1.571 +        for (;;) {
   1.572 +            // Increment connect count
   1.573 +            connectCount++;
   1.574 +            try {
   1.575 +                if (!managedConn.isOpen()) {
   1.576 +                    managedConn.open(route, context, params);
   1.577 +                } else {
   1.578 +                    managedConn.setSocketTimeout(HttpConnectionParams.getSoTimeout(params));
   1.579 +                }
   1.580 +                establishRoute(route, context);
   1.581 +                break;
   1.582 +            } catch (IOException ex) {
   1.583 +                try {
   1.584 +                    managedConn.close();
   1.585 +                } catch (IOException ignore) {
   1.586 +                }
   1.587 +                if (retryHandler.retryRequest(ex, connectCount, context)) {
   1.588 +                    if (this.log.isInfoEnabled()) {
   1.589 +                        this.log.info("I/O exception ("+ ex.getClass().getName() +
   1.590 +                                ") caught when connecting to the target host: "
   1.591 +                                + ex.getMessage());
   1.592 +                    }
   1.593 +                    if (this.log.isDebugEnabled()) {
   1.594 +                        this.log.debug(ex.getMessage(), ex);
   1.595 +                    }
   1.596 +                    this.log.info("Retrying connect");
   1.597 +                } else {
   1.598 +                    throw ex;
   1.599 +                }
   1.600 +            }
   1.601 +        }
   1.602 +    }
   1.603 +
   1.604 +    /**
   1.605 +     * Execute request and retry in case of a recoverable I/O failure
   1.606 +     */
   1.607 +    private HttpResponse tryExecute(
   1.608 +            final RoutedRequest req, final HttpContext context) throws HttpException, IOException {
   1.609 +        RequestWrapper wrapper = req.getRequest();
   1.610 +        HttpRoute route = req.getRoute();
   1.611 +        HttpResponse response = null;
   1.612 +
   1.613 +        Exception retryReason = null;
   1.614 +        for (;;) {
   1.615 +            // Increment total exec count (with redirects)
   1.616 +            execCount++;
   1.617 +            // Increment exec count for this particular request
   1.618 +            wrapper.incrementExecCount();
   1.619 +            if (!wrapper.isRepeatable()) {
   1.620 +                this.log.debug("Cannot retry non-repeatable request");
   1.621 +                if (retryReason != null) {
   1.622 +                    throw new NonRepeatableRequestException("Cannot retry request " +
   1.623 +                        "with a non-repeatable request entity.  The cause lists the " +
   1.624 +                        "reason the original request failed.", retryReason);
   1.625 +                } else {
   1.626 +                    throw new NonRepeatableRequestException("Cannot retry request " +
   1.627 +                            "with a non-repeatable request entity.");
   1.628 +                }
   1.629 +            }
   1.630 +
   1.631 +            try {
   1.632 +                if (!managedConn.isOpen()) {
   1.633 +                    // If we have a direct route to the target host
   1.634 +                    // just re-open connection and re-try the request
   1.635 +                    if (!route.isTunnelled()) {
   1.636 +                        this.log.debug("Reopening the direct connection.");
   1.637 +                        managedConn.open(route, context, params);
   1.638 +                    } else {
   1.639 +                        // otherwise give up
   1.640 +                        this.log.debug("Proxied connection. Need to start over.");
   1.641 +                        break;
   1.642 +                    }
   1.643 +                }
   1.644 +
   1.645 +                if (this.log.isDebugEnabled()) {
   1.646 +                    this.log.debug("Attempt " + execCount + " to execute request");
   1.647 +                }
   1.648 +                response = requestExec.execute(wrapper, managedConn, context);
   1.649 +                break;
   1.650 +
   1.651 +            } catch (IOException ex) {
   1.652 +                this.log.debug("Closing the connection.");
   1.653 +                try {
   1.654 +                    managedConn.close();
   1.655 +                } catch (IOException ignore) {
   1.656 +                }
   1.657 +                if (retryHandler.retryRequest(ex, wrapper.getExecCount(), context)) {
   1.658 +                    if (this.log.isInfoEnabled()) {
   1.659 +                        this.log.info("I/O exception ("+ ex.getClass().getName() +
   1.660 +                                ") caught when processing request: "
   1.661 +                                + ex.getMessage());
   1.662 +                    }
   1.663 +                    if (this.log.isDebugEnabled()) {
   1.664 +                        this.log.debug(ex.getMessage(), ex);
   1.665 +                    }
   1.666 +                    this.log.info("Retrying request");
   1.667 +                    retryReason = ex;
   1.668 +                } else {
   1.669 +                    throw ex;
   1.670 +                }
   1.671 +            }
   1.672 +        }
   1.673 +        return response;
   1.674 +    }
   1.675 +
   1.676 +    /**
   1.677 +     * Returns the connection back to the connection manager
   1.678 +     * and prepares for retrieving a new connection during
   1.679 +     * the next request.
   1.680 +     */
   1.681 +    protected void releaseConnection() {
   1.682 +        // Release the connection through the ManagedConnection instead of the
   1.683 +        // ConnectionManager directly.  This lets the connection control how
   1.684 +        // it is released.
   1.685 +        try {
   1.686 +            managedConn.releaseConnection();
   1.687 +        } catch(IOException ignored) {
   1.688 +            this.log.debug("IOException releasing connection", ignored);
   1.689 +        }
   1.690 +        managedConn = null;
   1.691 +    }
   1.692 +
   1.693 +    /**
   1.694 +     * Determines the route for a request.
   1.695 +     * Called by {@link #execute}
   1.696 +     * to determine the route for either the original or a followup request.
   1.697 +     *
   1.698 +     * @param target    the target host for the request.
   1.699 +     *                  Implementations may accept <code>null</code>
   1.700 +     *                  if they can still determine a route, for example
   1.701 +     *                  to a default target or by inspecting the request.
   1.702 +     * @param request   the request to execute
   1.703 +     * @param context   the context to use for the execution,
   1.704 +     *                  never <code>null</code>
   1.705 +     *
   1.706 +     * @return  the route the request should take
   1.707 +     *
   1.708 +     * @throws HttpException    in case of a problem
   1.709 +     */
   1.710 +    protected HttpRoute determineRoute(HttpHost    target,
   1.711 +                                           HttpRequest request,
   1.712 +                                           HttpContext context)
   1.713 +        throws HttpException {
   1.714 +
   1.715 +        if (target == null) {
   1.716 +            target = (HttpHost) request.getParams().getParameter(
   1.717 +                ClientPNames.DEFAULT_HOST);
   1.718 +        }
   1.719 +        if (target == null) {
   1.720 +            throw new IllegalStateException
   1.721 +                ("Target host must not be null, or set in parameters.");
   1.722 +        }
   1.723 +
   1.724 +        return this.routePlanner.determineRoute(target, request, context);
   1.725 +    }
   1.726 +
   1.727 +
   1.728 +    /**
   1.729 +     * Establishes the target route.
   1.730 +     *
   1.731 +     * @param route     the route to establish
   1.732 +     * @param context   the context for the request execution
   1.733 +     *
   1.734 +     * @throws HttpException    in case of a problem
   1.735 +     * @throws IOException      in case of an IO problem
   1.736 +     */
   1.737 +    protected void establishRoute(HttpRoute route, HttpContext context)
   1.738 +        throws HttpException, IOException {
   1.739 +
   1.740 +        HttpRouteDirector rowdy = new BasicRouteDirector();
   1.741 +        int step;
   1.742 +        do {
   1.743 +            HttpRoute fact = managedConn.getRoute();
   1.744 +            step = rowdy.nextStep(route, fact);
   1.745 +
   1.746 +            switch (step) {
   1.747 +
   1.748 +            case HttpRouteDirector.CONNECT_TARGET:
   1.749 +            case HttpRouteDirector.CONNECT_PROXY:
   1.750 +                managedConn.open(route, context, this.params);
   1.751 +                break;
   1.752 +
   1.753 +            case HttpRouteDirector.TUNNEL_TARGET: {
   1.754 +                boolean secure = createTunnelToTarget(route, context);
   1.755 +                this.log.debug("Tunnel to target created.");
   1.756 +                managedConn.tunnelTarget(secure, this.params);
   1.757 +            }   break;
   1.758 +
   1.759 +            case HttpRouteDirector.TUNNEL_PROXY: {
   1.760 +                // The most simple example for this case is a proxy chain
   1.761 +                // of two proxies, where P1 must be tunnelled to P2.
   1.762 +                // route: Source -> P1 -> P2 -> Target (3 hops)
   1.763 +                // fact:  Source -> P1 -> Target       (2 hops)
   1.764 +                final int hop = fact.getHopCount()-1; // the hop to establish
   1.765 +                boolean secure = createTunnelToProxy(route, hop, context);
   1.766 +                this.log.debug("Tunnel to proxy created.");
   1.767 +                managedConn.tunnelProxy(route.getHopTarget(hop),
   1.768 +                                        secure, this.params);
   1.769 +            }   break;
   1.770 +
   1.771 +
   1.772 +            case HttpRouteDirector.LAYER_PROTOCOL:
   1.773 +                managedConn.layerProtocol(context, this.params);
   1.774 +                break;
   1.775 +
   1.776 +            case HttpRouteDirector.UNREACHABLE:
   1.777 +                throw new HttpException("Unable to establish route: " +
   1.778 +                        "planned = " + route + "; current = " + fact);
   1.779 +            case HttpRouteDirector.COMPLETE:
   1.780 +                // do nothing
   1.781 +                break;
   1.782 +            default:
   1.783 +                throw new IllegalStateException("Unknown step indicator "
   1.784 +                        + step + " from RouteDirector.");
   1.785 +            }
   1.786 +
   1.787 +        } while (step > HttpRouteDirector.COMPLETE);
   1.788 +
   1.789 +    } // establishConnection
   1.790 +
   1.791 +
   1.792 +    /**
   1.793 +     * Creates a tunnel to the target server.
   1.794 +     * The connection must be established to the (last) proxy.
   1.795 +     * A CONNECT request for tunnelling through the proxy will
   1.796 +     * be created and sent, the response received and checked.
   1.797 +     * This method does <i>not</i> update the connection with
   1.798 +     * information about the tunnel, that is left to the caller.
   1.799 +     *
   1.800 +     * @param route     the route to establish
   1.801 +     * @param context   the context for request execution
   1.802 +     *
   1.803 +     * @return  <code>true</code> if the tunnelled route is secure,
   1.804 +     *          <code>false</code> otherwise.
   1.805 +     *          The implementation here always returns <code>false</code>,
   1.806 +     *          but derived classes may override.
   1.807 +     *
   1.808 +     * @throws HttpException    in case of a problem
   1.809 +     * @throws IOException      in case of an IO problem
   1.810 +     */
   1.811 +    protected boolean createTunnelToTarget(HttpRoute route,
   1.812 +                                           HttpContext context)
   1.813 +        throws HttpException, IOException {
   1.814 +
   1.815 +        HttpHost proxy = route.getProxyHost();
   1.816 +        HttpHost target = route.getTargetHost();
   1.817 +        HttpResponse response = null;
   1.818 +
   1.819 +        boolean done = false;
   1.820 +        while (!done) {
   1.821 +
   1.822 +            done = true;
   1.823 +
   1.824 +            if (!this.managedConn.isOpen()) {
   1.825 +                this.managedConn.open(route, context, this.params);
   1.826 +            }
   1.827 +
   1.828 +            HttpRequest connect = createConnectRequest(route, context);
   1.829 +            connect.setParams(this.params);
   1.830 +
   1.831 +            // Populate the execution context
   1.832 +            context.setAttribute(ExecutionContext.HTTP_TARGET_HOST,
   1.833 +                    target);
   1.834 +            context.setAttribute(ExecutionContext.HTTP_PROXY_HOST,
   1.835 +                    proxy);
   1.836 +            context.setAttribute(ExecutionContext.HTTP_CONNECTION,
   1.837 +                    managedConn);
   1.838 +            context.setAttribute(ClientContext.TARGET_AUTH_STATE,
   1.839 +                    targetAuthState);
   1.840 +            context.setAttribute(ClientContext.PROXY_AUTH_STATE,
   1.841 +                    proxyAuthState);
   1.842 +            context.setAttribute(ExecutionContext.HTTP_REQUEST,
   1.843 +                    connect);
   1.844 +
   1.845 +            this.requestExec.preProcess(connect, this.httpProcessor, context);
   1.846 +
   1.847 +            response = this.requestExec.execute(connect, this.managedConn, context);
   1.848 +
   1.849 +            response.setParams(this.params);
   1.850 +            this.requestExec.postProcess(response, this.httpProcessor, context);
   1.851 +
   1.852 +            int status = response.getStatusLine().getStatusCode();
   1.853 +            if (status < 200) {
   1.854 +                throw new HttpException("Unexpected response to CONNECT request: " +
   1.855 +                        response.getStatusLine());
   1.856 +            }
   1.857 +
   1.858 +            CredentialsProvider credsProvider = (CredentialsProvider)
   1.859 +                context.getAttribute(ClientContext.CREDS_PROVIDER);
   1.860 +
   1.861 +            if (credsProvider != null && HttpClientParams.isAuthenticating(params)) {
   1.862 +                if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) {
   1.863 +
   1.864 +                    this.log.debug("Proxy requested authentication");
   1.865 +                    Map<String, Header> challenges = this.proxyAuthHandler.getChallenges(
   1.866 +                            response, context);
   1.867 +                    try {
   1.868 +                        processChallenges(
   1.869 +                                challenges, this.proxyAuthState, this.proxyAuthHandler,
   1.870 +                                response, context);
   1.871 +                    } catch (AuthenticationException ex) {
   1.872 +                        if (this.log.isWarnEnabled()) {
   1.873 +                            this.log.warn("Authentication error: " +  ex.getMessage());
   1.874 +                            break;
   1.875 +                        }
   1.876 +                    }
   1.877 +                    updateAuthState(this.proxyAuthState, proxy, credsProvider);
   1.878 +
   1.879 +                    if (this.proxyAuthState.getCredentials() != null) {
   1.880 +                        done = false;
   1.881 +
   1.882 +                        // Retry request
   1.883 +                        if (this.reuseStrategy.keepAlive(response, context)) {
   1.884 +                            this.log.debug("Connection kept alive");
   1.885 +                            // Consume response content
   1.886 +                            HttpEntity entity = response.getEntity();
   1.887 +                            EntityUtils.consume(entity);
   1.888 +                        } else {
   1.889 +                            this.managedConn.close();
   1.890 +                        }
   1.891 +
   1.892 +                    }
   1.893 +
   1.894 +                } else {
   1.895 +                    // Reset proxy auth scope
   1.896 +                    this.proxyAuthState.setAuthScope(null);
   1.897 +                }
   1.898 +            }
   1.899 +        }
   1.900 +
   1.901 +        int status = response.getStatusLine().getStatusCode(); // can't be null
   1.902 +
   1.903 +        if (status > 299) {
   1.904 +
   1.905 +            // Buffer response content
   1.906 +            HttpEntity entity = response.getEntity();
   1.907 +            if (entity != null) {
   1.908 +                response.setEntity(new BufferedHttpEntity(entity));
   1.909 +            }
   1.910 +
   1.911 +            this.managedConn.close();
   1.912 +            throw new TunnelRefusedException("CONNECT refused by proxy: " +
   1.913 +                    response.getStatusLine(), response);
   1.914 +        }
   1.915 +
   1.916 +        this.managedConn.markReusable();
   1.917 +
   1.918 +        // How to decide on security of the tunnelled connection?
   1.919 +        // The socket factory knows only about the segment to the proxy.
   1.920 +        // Even if that is secure, the hop to the target may be insecure.
   1.921 +        // Leave it to derived classes, consider insecure by default here.
   1.922 +        return false;
   1.923 +
   1.924 +    } // createTunnelToTarget
   1.925 +
   1.926 +
   1.927 +
   1.928 +    /**
   1.929 +     * Creates a tunnel to an intermediate proxy.
   1.930 +     * This method is <i>not</i> implemented in this class.
   1.931 +     * It just throws an exception here.
   1.932 +     *
   1.933 +     * @param route     the route to establish
   1.934 +     * @param hop       the hop in the route to establish now.
   1.935 +     *                  <code>route.getHopTarget(hop)</code>
   1.936 +     *                  will return the proxy to tunnel to.
   1.937 +     * @param context   the context for request execution
   1.938 +     *
   1.939 +     * @return  <code>true</code> if the partially tunnelled connection
   1.940 +     *          is secure, <code>false</code> otherwise.
   1.941 +     *
   1.942 +     * @throws HttpException    in case of a problem
   1.943 +     * @throws IOException      in case of an IO problem
   1.944 +     */
   1.945 +    protected boolean createTunnelToProxy(HttpRoute route, int hop,
   1.946 +                                          HttpContext context)
   1.947 +        throws HttpException, IOException {
   1.948 +
   1.949 +        // Have a look at createTunnelToTarget and replicate the parts
   1.950 +        // you need in a custom derived class. If your proxies don't require
   1.951 +        // authentication, it is not too hard. But for the stock version of
   1.952 +        // HttpClient, we cannot make such simplifying assumptions and would
   1.953 +        // have to include proxy authentication code. The HttpComponents team
   1.954 +        // is currently not in a position to support rarely used code of this
   1.955 +        // complexity. Feel free to submit patches that refactor the code in
   1.956 +        // createTunnelToTarget to facilitate re-use for proxy tunnelling.
   1.957 +
   1.958 +        throw new HttpException("Proxy chains are not supported.");
   1.959 +    }
   1.960 +
   1.961 +
   1.962 +
   1.963 +    /**
   1.964 +     * Creates the CONNECT request for tunnelling.
   1.965 +     * Called by {@link #createTunnelToTarget createTunnelToTarget}.
   1.966 +     *
   1.967 +     * @param route     the route to establish
   1.968 +     * @param context   the context for request execution
   1.969 +     *
   1.970 +     * @return  the CONNECT request for tunnelling
   1.971 +     */
   1.972 +    protected HttpRequest createConnectRequest(HttpRoute route,
   1.973 +                                               HttpContext context) {
   1.974 +        // see RFC 2817, section 5.2 and
   1.975 +        // INTERNET-DRAFT: Tunneling TCP based protocols through
   1.976 +        // Web proxy servers
   1.977 +
   1.978 +        HttpHost target = route.getTargetHost();
   1.979 +
   1.980 +        String host = target.getHostName();
   1.981 +        int port = target.getPort();
   1.982 +        if (port < 0) {
   1.983 +            Scheme scheme = connManager.getSchemeRegistry().
   1.984 +                getScheme(target.getSchemeName());
   1.985 +            port = scheme.getDefaultPort();
   1.986 +        }
   1.987 +
   1.988 +        StringBuilder buffer = new StringBuilder(host.length() + 6);
   1.989 +        buffer.append(host);
   1.990 +        buffer.append(':');
   1.991 +        buffer.append(Integer.toString(port));
   1.992 +
   1.993 +        String authority = buffer.toString();
   1.994 +        ProtocolVersion ver = HttpProtocolParams.getVersion(params);
   1.995 +        HttpRequest req = new BasicHttpRequest
   1.996 +            ("CONNECT", authority, ver);
   1.997 +
   1.998 +        return req;
   1.999 +    }
  1.1000 +
  1.1001 +
  1.1002 +    /**
  1.1003 +     * Analyzes a response to check need for a followup.
  1.1004 +     *
  1.1005 +     * @param roureq    the request and route.
  1.1006 +     * @param response  the response to analayze
  1.1007 +     * @param context   the context used for the current request execution
  1.1008 +     *
  1.1009 +     * @return  the followup request and route if there is a followup, or
  1.1010 +     *          <code>null</code> if the response should be returned as is
  1.1011 +     *
  1.1012 +     * @throws HttpException    in case of a problem
  1.1013 +     * @throws IOException      in case of an IO problem
  1.1014 +     */
  1.1015 +    protected RoutedRequest handleResponse(RoutedRequest roureq,
  1.1016 +                                           HttpResponse response,
  1.1017 +                                           HttpContext context)
  1.1018 +        throws HttpException, IOException {
  1.1019 +
  1.1020 +        HttpRoute route = roureq.getRoute();
  1.1021 +        RequestWrapper request = roureq.getRequest();
  1.1022 +
  1.1023 +        HttpParams params = request.getParams();
  1.1024 +        if (HttpClientParams.isRedirecting(params) &&
  1.1025 +                this.redirectStrategy.isRedirected(request, response, context)) {
  1.1026 +
  1.1027 +            if (redirectCount >= maxRedirects) {
  1.1028 +                throw new RedirectException("Maximum redirects ("
  1.1029 +                        + maxRedirects + ") exceeded");
  1.1030 +            }
  1.1031 +            redirectCount++;
  1.1032 +
  1.1033 +            // Virtual host cannot be used any longer
  1.1034 +            virtualHost = null;
  1.1035 +
  1.1036 +            HttpUriRequest redirect = redirectStrategy.getRedirect(request, response, context);
  1.1037 +            HttpRequest orig = request.getOriginal();
  1.1038 +            redirect.setHeaders(orig.getAllHeaders());
  1.1039 +
  1.1040 +            URI uri = redirect.getURI();
  1.1041 +            if (uri.getHost() == null) {
  1.1042 +                throw new ProtocolException("Redirect URI does not specify a valid host name: " + uri);
  1.1043 +            }
  1.1044 +
  1.1045 +            HttpHost newTarget = new HttpHost(
  1.1046 +                    uri.getHost(),
  1.1047 +                    uri.getPort(),
  1.1048 +                    uri.getScheme());
  1.1049 +
  1.1050 +            // Unset auth scope
  1.1051 +            targetAuthState.setAuthScope(null);
  1.1052 +            proxyAuthState.setAuthScope(null);
  1.1053 +
  1.1054 +            // Invalidate auth states if redirecting to another host
  1.1055 +            if (!route.getTargetHost().equals(newTarget)) {
  1.1056 +                targetAuthState.invalidate();
  1.1057 +                AuthScheme authScheme = proxyAuthState.getAuthScheme();
  1.1058 +                if (authScheme != null && authScheme.isConnectionBased()) {
  1.1059 +                    proxyAuthState.invalidate();
  1.1060 +                }
  1.1061 +            }
  1.1062 +
  1.1063 +            RequestWrapper wrapper = wrapRequest(redirect);
  1.1064 +            wrapper.setParams(params);
  1.1065 +
  1.1066 +            HttpRoute newRoute = determineRoute(newTarget, wrapper, context);
  1.1067 +            RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute);
  1.1068 +
  1.1069 +            if (this.log.isDebugEnabled()) {
  1.1070 +                this.log.debug("Redirecting to '" + uri + "' via " + newRoute);
  1.1071 +            }
  1.1072 +
  1.1073 +            return newRequest;
  1.1074 +        }
  1.1075 +
  1.1076 +        CredentialsProvider credsProvider = (CredentialsProvider)
  1.1077 +            context.getAttribute(ClientContext.CREDS_PROVIDER);
  1.1078 +
  1.1079 +        if (credsProvider != null && HttpClientParams.isAuthenticating(params)) {
  1.1080 +
  1.1081 +            if (this.targetAuthHandler.isAuthenticationRequested(response, context)) {
  1.1082 +
  1.1083 +                HttpHost target = (HttpHost)
  1.1084 +                    context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
  1.1085 +                if (target == null) {
  1.1086 +                    target = route.getTargetHost();
  1.1087 +                }
  1.1088 +
  1.1089 +                this.log.debug("Target requested authentication");
  1.1090 +                Map<String, Header> challenges = this.targetAuthHandler.getChallenges(
  1.1091 +                        response, context);
  1.1092 +                try {
  1.1093 +                    processChallenges(challenges,
  1.1094 +                            this.targetAuthState, this.targetAuthHandler,
  1.1095 +                            response, context);
  1.1096 +                } catch (AuthenticationException ex) {
  1.1097 +                    if (this.log.isWarnEnabled()) {
  1.1098 +                        this.log.warn("Authentication error: " +  ex.getMessage());
  1.1099 +                        return null;
  1.1100 +                    }
  1.1101 +                }
  1.1102 +                updateAuthState(this.targetAuthState, target, credsProvider);
  1.1103 +
  1.1104 +                if (this.targetAuthState.getCredentials() != null) {
  1.1105 +                    // Re-try the same request via the same route
  1.1106 +                    return roureq;
  1.1107 +                } else {
  1.1108 +                    return null;
  1.1109 +                }
  1.1110 +            } else {
  1.1111 +                // Reset target auth scope
  1.1112 +                this.targetAuthState.setAuthScope(null);
  1.1113 +            }
  1.1114 +
  1.1115 +            if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) {
  1.1116 +
  1.1117 +                HttpHost proxy = route.getProxyHost();
  1.1118 +
  1.1119 +                this.log.debug("Proxy requested authentication");
  1.1120 +                Map<String, Header> challenges = this.proxyAuthHandler.getChallenges(
  1.1121 +                        response, context);
  1.1122 +                try {
  1.1123 +                    processChallenges(challenges,
  1.1124 +                            this.proxyAuthState, this.proxyAuthHandler,
  1.1125 +                            response, context);
  1.1126 +                } catch (AuthenticationException ex) {
  1.1127 +                    if (this.log.isWarnEnabled()) {
  1.1128 +                        this.log.warn("Authentication error: " +  ex.getMessage());
  1.1129 +                        return null;
  1.1130 +                    }
  1.1131 +                }
  1.1132 +                updateAuthState(this.proxyAuthState, proxy, credsProvider);
  1.1133 +
  1.1134 +                if (this.proxyAuthState.getCredentials() != null) {
  1.1135 +                    // Re-try the same request via the same route
  1.1136 +                    return roureq;
  1.1137 +                } else {
  1.1138 +                    return null;
  1.1139 +                }
  1.1140 +            } else {
  1.1141 +                // Reset proxy auth scope
  1.1142 +                this.proxyAuthState.setAuthScope(null);
  1.1143 +            }
  1.1144 +        }
  1.1145 +        return null;
  1.1146 +    } // handleResponse
  1.1147 +
  1.1148 +
  1.1149 +    /**
  1.1150 +     * Shuts down the connection.
  1.1151 +     * This method is called from a <code>catch</code> block in
  1.1152 +     * {@link #execute execute} during exception handling.
  1.1153 +     */
  1.1154 +    private void abortConnection() {
  1.1155 +        ManagedClientConnection mcc = managedConn;
  1.1156 +        if (mcc != null) {
  1.1157 +            // we got here as the result of an exception
  1.1158 +            // no response will be returned, release the connection
  1.1159 +            managedConn = null;
  1.1160 +            try {
  1.1161 +                mcc.abortConnection();
  1.1162 +            } catch (IOException ex) {
  1.1163 +                if (this.log.isDebugEnabled()) {
  1.1164 +                    this.log.debug(ex.getMessage(), ex);
  1.1165 +                }
  1.1166 +            }
  1.1167 +            // ensure the connection manager properly releases this connection
  1.1168 +            try {
  1.1169 +                mcc.releaseConnection();
  1.1170 +            } catch(IOException ignored) {
  1.1171 +                this.log.debug("Error releasing connection", ignored);
  1.1172 +            }
  1.1173 +        }
  1.1174 +    } // abortConnection
  1.1175 +
  1.1176 +
  1.1177 +    private void processChallenges(
  1.1178 +            final Map<String, Header> challenges,
  1.1179 +            final AuthState authState,
  1.1180 +            final AuthenticationHandler authHandler,
  1.1181 +            final HttpResponse response,
  1.1182 +            final HttpContext context)
  1.1183 +                throws MalformedChallengeException, AuthenticationException {
  1.1184 +
  1.1185 +        AuthScheme authScheme = authState.getAuthScheme();
  1.1186 +        if (authScheme == null) {
  1.1187 +            // Authentication not attempted before
  1.1188 +            authScheme = authHandler.selectScheme(challenges, response, context);
  1.1189 +            authState.setAuthScheme(authScheme);
  1.1190 +        }
  1.1191 +        String id = authScheme.getSchemeName();
  1.1192 +
  1.1193 +        Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH));
  1.1194 +        if (challenge == null) {
  1.1195 +            throw new AuthenticationException(id +
  1.1196 +                " authorization challenge expected, but not found");
  1.1197 +        }
  1.1198 +        authScheme.processChallenge(challenge);
  1.1199 +        this.log.debug("Authorization challenge processed");
  1.1200 +    }
  1.1201 +
  1.1202 +
  1.1203 +    private void updateAuthState(
  1.1204 +            final AuthState authState,
  1.1205 +            final HttpHost host,
  1.1206 +            final CredentialsProvider credsProvider) {
  1.1207 +
  1.1208 +        if (!authState.isValid()) {
  1.1209 +            return;
  1.1210 +        }
  1.1211 +
  1.1212 +        String hostname = host.getHostName();
  1.1213 +        int port = host.getPort();
  1.1214 +        if (port < 0) {
  1.1215 +            Scheme scheme = connManager.getSchemeRegistry().getScheme(host);
  1.1216 +            port = scheme.getDefaultPort();
  1.1217 +        }
  1.1218 +
  1.1219 +        AuthScheme authScheme = authState.getAuthScheme();
  1.1220 +        AuthScope authScope = new AuthScope(
  1.1221 +                hostname,
  1.1222 +                port,
  1.1223 +                authScheme.getRealm(),
  1.1224 +                authScheme.getSchemeName());
  1.1225 +
  1.1226 +        if (this.log.isDebugEnabled()) {
  1.1227 +            this.log.debug("Authentication scope: " + authScope);
  1.1228 +        }
  1.1229 +        Credentials creds = authState.getCredentials();
  1.1230 +        if (creds == null) {
  1.1231 +            creds = credsProvider.getCredentials(authScope);
  1.1232 +            if (this.log.isDebugEnabled()) {
  1.1233 +                if (creds != null) {
  1.1234 +                    this.log.debug("Found credentials");
  1.1235 +                } else {
  1.1236 +                    this.log.debug("Credentials not found");
  1.1237 +                }
  1.1238 +            }
  1.1239 +        } else {
  1.1240 +            if (authScheme.isComplete()) {
  1.1241 +                this.log.debug("Authentication failed");
  1.1242 +                creds = null;
  1.1243 +            }
  1.1244 +        }
  1.1245 +        authState.setAuthScope(authScope);
  1.1246 +        authState.setCredentials(creds);
  1.1247 +    }
  1.1248 +
  1.1249 +    private void invalidateAuthIfSuccessful(final AuthState authState) {
  1.1250 +        AuthScheme authscheme = authState.getAuthScheme();
  1.1251 +        if (authscheme != null
  1.1252 +                && authscheme.isConnectionBased()
  1.1253 +                && authscheme.isComplete()
  1.1254 +                && authState.getCredentials() != null) {
  1.1255 +            authState.invalidate();
  1.1256 +        }
  1.1257 +    }
  1.1258 +
  1.1259 +} // class DefaultClientRequestDirector

mercurial