com.grendelscan.commons.http.apache_overrides.client.CustomClientRequestDirector.java Source code

Java tutorial

Introduction

Here is the source code for com.grendelscan.commons.http.apache_overrides.client.CustomClientRequestDirector.java

Source

/*
 * ====================================================================
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation. For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package com.grendelscan.commons.http.apache_overrides.client;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.NotImplementedException;
import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.client.AuthenticationHandler;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.RequestDirector;
import org.apache.http.client.UserTokenHandler;
import org.apache.http.client.methods.AbortableHttpRequest;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.BasicManagedEntity;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.ClientConnectionRequest;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.ManagedClientConnection;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.routing.BasicRouteDirector;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.routing.HttpRouteDirector;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.TunnelRefusedException;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpRequestExecutor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Default implementation of {@link RequestDirector}.
 * <p>
 * The following parameters can be used to customize the behavior of this class:
 * <ul>
 * <li>{@link org.apache.http.params.CoreProtocolPNames#PROTOCOL_VERSION}</li>
 * <li>
 * {@link org.apache.http.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li>
 * <li>{@link org.apache.http.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li>
 * <li>{@link org.apache.http.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li>
 * <li>{@link org.apache.http.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li>
 * <li>{@link org.apache.http.params.CoreProtocolPNames#USER_AGENT}</li>
 * <li>{@link org.apache.http.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li>
 * <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li>
 * <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li>
 * <li>{@link org.apache.http.params.CoreConnectionPNames#SO_TIMEOUT}</li>
 * <li>{@link org.apache.http.params.CoreConnectionPNames#SO_LINGER}</li>
 * <li>{@link org.apache.http.params.CoreConnectionPNames#TCP_NODELAY}</li>
 * <li>{@link org.apache.http.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li>
 * <li>
 * {@link org.apache.http.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li>
 * <li>{@link org.apache.http.conn.params.ConnRoutePNames#FORCED_ROUTE}</li>
 * <li>{@link org.apache.http.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li>
 * <li>{@link org.apache.http.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li>
 * <li>{@link org.apache.http.conn.params.ConnManagerPNames#TIMEOUT}</li>
 * <li>
 * {@link org.apache.http.conn.params.ConnManagerPNames#MAX_CONNECTIONS_PER_ROUTE}
 * </li>
 * <li>
 * {@link org.apache.http.conn.params.ConnManagerPNames#MAX_TOTAL_CONNECTIONS}</li>
 * <li>{@link org.apache.http.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li>
 * <li>
 * {@link org.apache.http.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li>
 * <li>{@link org.apache.http.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li>
 * <li>{@link org.apache.http.client.params.ClientPNames#COOKIE_POLICY}</li>
 * <li>{@link org.apache.http.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li>
 * <li>{@link org.apache.http.client.params.ClientPNames#HANDLE_REDIRECTS}</li>
 * <li>{@link org.apache.http.client.params.ClientPNames#MAX_REDIRECTS}</li>
 * <li>
 * {@link org.apache.http.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li>
 * <li>{@link org.apache.http.client.params.ClientPNames#VIRTUAL_HOST}</li>
 * <li>{@link org.apache.http.client.params.ClientPNames#DEFAULT_HOST}</li>
 * <li>{@link org.apache.http.client.params.ClientPNames#DEFAULT_HEADERS}</li>
 * </ul>
 * 
 * @since 4.0
 */
@NotThreadSafe
// e.g. managedConn
public class CustomClientRequestDirector implements RequestDirector {
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomClientRequestDirector.class);

    private HttpHost virtualHost;

    /** The connection manager. */
    private final ClientConnectionManager connManager;

    /** The HTTP protocol processor. */
    private final HttpProcessor httpProcessor;

    /** The keep-alive duration strategy. */
    private final ConnectionKeepAliveStrategy keepAliveStrategy;

    /** The currently allocated connection. */
    private ManagedClientConnection managedConn;

    /** The HTTP parameters. */
    private final HttpParams params;

    /** The proxy authentication handler. */
    private final AuthenticationHandler proxyAuthHandler;

    private final AuthState proxyAuthState;

    /** The request executor. */
    private final HttpRequestExecutor requestExec;

    /** The connection re-use strategy. */
    private final ConnectionReuseStrategy reuseStrategy;

    /** The route planner. */
    private final HttpRoutePlanner routePlanner;

    private final AuthState targetAuthState;

    /** The user token handler. */
    private final UserTokenHandler userTokenHandler;

    public CustomClientRequestDirector(final HttpRequestExecutor requestExec, final ClientConnectionManager conman,
            final ConnectionReuseStrategy reustrat, final ConnectionKeepAliveStrategy kastrat,
            final HttpRoutePlanner rouplan, final HttpProcessor httpProcessor,
            final AuthenticationHandler proxyAuthHandler, final UserTokenHandler userTokenHandler,
            final HttpParams params) {

        if (requestExec == null) {
            throw new IllegalArgumentException("Request executor may not be null.");
        }
        if (conman == null) {
            throw new IllegalArgumentException("Client connection manager may not be null.");
        }
        if (reustrat == null) {
            throw new IllegalArgumentException("Connection reuse strategy may not be null.");
        }
        if (kastrat == null) {
            throw new IllegalArgumentException("Connection keep alive strategy may not be null.");
        }
        if (rouplan == null) {
            throw new IllegalArgumentException("Route planner may not be null.");
        }
        if (httpProcessor == null) {
            throw new IllegalArgumentException("HTTP protocol processor may not be null.");
        }
        if (proxyAuthHandler == null) {
            throw new IllegalArgumentException("Proxy authentication handler may not be null.");
        }
        if (userTokenHandler == null) {
            throw new IllegalArgumentException("User token handler may not be null.");
        }
        if (params == null) {
            throw new IllegalArgumentException("HTTP parameters may not be null");
        }
        this.requestExec = requestExec;
        connManager = conman;
        reuseStrategy = reustrat;
        keepAliveStrategy = kastrat;
        routePlanner = rouplan;
        this.httpProcessor = httpProcessor;
        this.proxyAuthHandler = proxyAuthHandler;
        this.userTokenHandler = userTokenHandler;
        this.params = params;

        managedConn = null;
        targetAuthState = new AuthState();
        proxyAuthState = new AuthState();
    } // constructor

    // non-javadoc, see interface ClientRequestDirector
    @Override
    public HttpResponse execute(HttpHost originalTarget, final HttpRequest request, HttpContext context)
            throws HttpException, IOException {
        HttpHost target = originalTarget;
        final HttpRoute route = determineRoute(target, request, context);

        virtualHost = (HttpHost) request.getParams().getParameter(ClientPNames.VIRTUAL_HOST);

        long timeout = ConnManagerParams.getTimeout(params);

        try {
            HttpResponse response = null;

            // See if we have a user token bound to the execution context
            Object userToken = context.getAttribute(ClientContext.USER_TOKEN);

            // Allocate connection if needed
            if (managedConn == null) {
                ClientConnectionRequest connRequest = connManager.requestConnection(route, userToken);
                if (request instanceof AbortableHttpRequest) {
                    ((AbortableHttpRequest) request).setConnectionRequest(connRequest);
                }

                try {
                    managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
                } catch (InterruptedException interrupted) {
                    InterruptedIOException iox = new InterruptedIOException();
                    iox.initCause(interrupted);
                    throw iox;
                }

                if (HttpConnectionParams.isStaleCheckingEnabled(params)) {
                    // validate connection
                    if (managedConn.isOpen()) {
                        LOGGER.debug("Stale connection check");
                        if (managedConn.isStale()) {
                            LOGGER.debug("Stale connection detected");
                            managedConn.close();
                        }
                    }
                }
            }

            if (request instanceof AbortableHttpRequest) {
                ((AbortableHttpRequest) request).setReleaseTrigger(managedConn);
            }

            // Reopen connection if needed
            if (!managedConn.isOpen()) {
                managedConn.open(route, context, params);
            } else {
                managedConn.setSocketTimeout(HttpConnectionParams.getSoTimeout(params));
            }

            try {
                establishRoute(route, context);
            } catch (TunnelRefusedException ex) {
                LOGGER.debug(ex.getMessage());
                response = ex.getResponse();
            }

            // Use virtual host if set
            target = virtualHost;

            if (target == null) {
                target = route.getTargetHost();
            }

            HttpHost proxy = route.getProxyHost();

            // Populate the execution context
            context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
            context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, proxy);
            context.setAttribute(ExecutionContext.HTTP_CONNECTION, managedConn);
            context.setAttribute(ClientContext.TARGET_AUTH_STATE, targetAuthState);
            context.setAttribute(ClientContext.PROXY_AUTH_STATE, proxyAuthState);

            // Run request protocol interceptors
            requestExec.preProcess(request, httpProcessor, context);

            try {
                response = requestExec.execute(request, managedConn, context);
            } catch (IOException ex) {
                LOGGER.debug("Closing connection after request failure.");
                managedConn.close();
                throw ex;
            }

            if (response == null) {
                return null;
            }

            // Run response protocol interceptors
            response.setParams(params);
            requestExec.postProcess(response, httpProcessor, context);

            // The connection is in or can be brought to a re-usable state.
            boolean reuse = reuseStrategy.keepAlive(response, context);
            if (reuse) {
                // Set the idle duration of this connection
                long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
                managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS);

                if (duration >= 0) {
                    LOGGER.trace("Connection can be kept alive for " + duration + " ms");
                } else {
                    LOGGER.trace("Connection can be kept alive indefinitely");
                }
            }

            if ((managedConn != null) && (userToken == null)) {
                userToken = userTokenHandler.getUserToken(context);
                context.setAttribute(ClientContext.USER_TOKEN, userToken);
                if (userToken != null) {
                    managedConn.setState(userToken);
                }
            }

            // check for entity, release connection if possible
            if ((response.getEntity() == null) || !response.getEntity().isStreaming()) {
                // connection not needed and (assumed to be) in re-usable state
                if (reuse) {
                    managedConn.markReusable();
                }
                releaseConnection();
            } else {
                // install an auto-release entity
                HttpEntity entity = response.getEntity();
                entity = new BasicManagedEntity(entity, managedConn, reuse);
                response.setEntity(entity);
            }

            return response;

        } catch (HttpException ex) {
            abortConnection();
            throw ex;
        } catch (IOException ex) {
            abortConnection();
            throw ex;
        } catch (RuntimeException ex) {
            abortConnection();
            throw ex;
        }
    } // execute

    /**
     * Shuts down the connection.
     * This method is called from a <code>catch</code> block in {@link #execute
     * execute} during exception handling.
     */
    private void abortConnection() {
        ManagedClientConnection mcc = managedConn;
        if (mcc != null) {
            // we got here as the result of an exception
            // no response will be returned, release the connection
            managedConn = null;
            try {
                mcc.abortConnection();
            } catch (IOException ex) {
                LOGGER.trace(ex.getMessage(), ex);
            }
            // ensure the connection manager properly releases this connection
            try {
                mcc.releaseConnection();
            } catch (IOException ignored) {
                LOGGER.debug("Error releasing connection", ignored);
            }
        }
    } // abortConnection

    private void processChallenges(final Map<String, Header> challenges, final AuthState authState,
            final AuthenticationHandler authHandler, final HttpResponse response, final HttpContext context)
            throws MalformedChallengeException, AuthenticationException {

        AuthScheme authScheme = authState.getAuthScheme();
        if (authScheme == null) {
            // Authentication not attempted before
            authScheme = authHandler.selectScheme(challenges, response, context);
            authState.setAuthScheme(authScheme);
        }
        String id = authScheme.getSchemeName();

        Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH));
        if (challenge == null) {
            throw new AuthenticationException(id + " authorization challenge expected, but not found");
        }
        authScheme.processChallenge(challenge);
        LOGGER.debug("Authorization challenge processed");
    }

    private void updateAuthState(final AuthState authState, final HttpHost host,
            final CredentialsProvider credsProvider) {
        if (!authState.isValid()) {
            return;
        }

        String hostname = host.getHostName();
        int port = host.getPort();
        if (port < 0) {
            Scheme scheme = connManager.getSchemeRegistry().getScheme(host);
            port = scheme.getDefaultPort();
        }

        AuthScheme authScheme = authState.getAuthScheme();
        AuthScope authScope = new AuthScope(hostname, port, authScheme.getRealm(), authScheme.getSchemeName());

        LOGGER.trace("Authentication scope: " + authScope);
        Credentials creds = authState.getCredentials();
        if (creds == null) {
            creds = credsProvider.getCredentials(authScope);
            if (creds != null) {
                LOGGER.trace("Found credentials");
            } else {
                LOGGER.trace("Credentials not found");
            }
        } else {
            if (authScheme.isComplete()) {
                LOGGER.debug("Authentication failed");
                creds = null;
            }
        }
        authState.setAuthScope(authScope);
        authState.setCredentials(creds);
    }

    //   private RequestWrapper wrapRequest(
    //         final HttpRequest request) throws ProtocolException
    //   {
    //      if (request instanceof HttpEntityEnclosingRequest)
    //      {
    //         return new EntityEnclosingRequestWrapper(
    //               (HttpEntityEnclosingRequest) request);
    //      }
    //      else
    //      {
    //         return new RequestWrapper(
    //               request);
    //      }
    //   }

    /**
     * Creates the CONNECT request for tunnelling.
     * Called by {@link #createTunnelToTarget createTunnelToTarget}.
     * 
     * @param route
     *            the route to establish
     * @param context
     *            the context for request execution
     * 
     * @return the CONNECT request for tunnelling
     */
    private HttpRequest createConnectRequest(HttpRoute route) {
        // see RFC 2817, section 5.2 and
        // INTERNET-DRAFT: Tunneling TCP based protocols through
        // Web proxy servers

        HttpHost target = route.getTargetHost();

        String host = target.getHostName();
        int port = target.getPort();
        if (port < 0) {
            Scheme scheme = connManager.getSchemeRegistry().getScheme(target.getSchemeName());
            port = scheme.getDefaultPort();
        }

        StringBuilder buffer = new StringBuilder(host.length() + 6);
        buffer.append(host);
        buffer.append(':');
        buffer.append(Integer.toString(port));

        String authority = buffer.toString();
        ProtocolVersion ver = HttpProtocolParams.getVersion(params);
        HttpRequest req = new BasicHttpRequest("CONNECT", authority, ver);

        return req;
    }

    /**
     * Creates a tunnel to the target server.
     * The connection must be established to the (last) proxy.
     * A CONNECT request for tunnelling through the proxy will
     * be created and sent, the response received and checked.
     * This method does <i>not</i> update the connection with
     * information about the tunnel, that is left to the caller.
     * 
     * @param route
     *            the route to establish
     * @param context
     *            the context for request execution
     * 
     * @return <code>true</code> if the tunnelled route is secure,
     *         <code>false</code> otherwise.
     *         The implementation here always returns <code>false</code>,
     *         but derived classes may override.
     * 
     * @throws HttpException
     *             in case of a problem
     * @throws IOException
     *             in case of an IO problem
     */
    private boolean createTunnelToTarget(HttpRoute route, HttpContext context) throws HttpException, IOException {

        HttpHost proxy = route.getProxyHost();
        HttpHost target = route.getTargetHost();
        HttpResponse response = null;

        boolean done = false;
        while (!done) {

            done = true;

            if (!managedConn.isOpen()) {
                managedConn.open(route, context, params);
            }

            HttpRequest connect = createConnectRequest(route);
            connect.setParams(params);

            // Populate the execution context
            context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
            context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, proxy);
            context.setAttribute(ExecutionContext.HTTP_CONNECTION, managedConn);
            context.setAttribute(ClientContext.TARGET_AUTH_STATE, targetAuthState);
            context.setAttribute(ClientContext.PROXY_AUTH_STATE, proxyAuthState);
            context.setAttribute(ExecutionContext.HTTP_REQUEST, connect);

            requestExec.preProcess(connect, httpProcessor, context);

            response = requestExec.execute(connect, managedConn, context);

            response.setParams(params);
            requestExec.postProcess(response, httpProcessor, context);

            int status = response.getStatusLine().getStatusCode();
            if (status < 200) {
                throw new HttpException("Unexpected response to CONNECT request: " + response.getStatusLine());
            }

            CredentialsProvider credsProvider = (CredentialsProvider) context
                    .getAttribute(ClientContext.CREDS_PROVIDER);

            if ((credsProvider != null) && HttpClientParams.isAuthenticating(params)) {
                if (proxyAuthHandler.isAuthenticationRequested(response, context)) {

                    LOGGER.debug("Proxy requested authentication");
                    Map<String, Header> challenges = proxyAuthHandler.getChallenges(response, context);
                    try {
                        processChallenges(challenges, proxyAuthState, proxyAuthHandler, response, context);
                    } catch (AuthenticationException ex) {
                        LOGGER.warn("Authentication error: " + ex.getMessage());
                    }
                    updateAuthState(proxyAuthState, proxy, credsProvider);

                    if (proxyAuthState.getCredentials() != null) {
                        done = false;

                        // Retry request
                        if (reuseStrategy.keepAlive(response, context)) {
                            LOGGER.debug("Connection kept alive");
                            // Consume response content
                            HttpEntity entity = response.getEntity();
                            if (entity != null) {
                                entity.consumeContent();
                            }
                        } else {
                            managedConn.close();
                        }

                    }

                } else {
                    // Reset proxy auth scope
                    proxyAuthState.setAuthScope(null);
                }
            }
        }

        int status = response.getStatusLine().getStatusCode(); // can't be null

        if (status > 299) {

            // Buffer response content
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                response.setEntity(new BufferedHttpEntity(entity));
            }

            managedConn.close();
            throw new TunnelRefusedException("CONNECT refused by proxy: " + response.getStatusLine(), response);
        }

        managedConn.markReusable();

        // How to decide on security of the tunnelled connection?
        // The socket factory knows only about the segment to the proxy.
        // Even if that is secure, the hop to the target may be insecure.
        // Leave it to derived classes, consider insecure by default here.
        return false;

    } // createTunnelToTarget

    /**
     * Determines the route for a request.
     * Called by {@link #execute} to determine the route for either the original
     * or a followup request.
     * 
     * @param target
     *            the target host for the request.
     *            Implementations may accept <code>null</code> if they can still
     *            determine a route, for example
     *            to a default target or by inspecting the request.
     * @param request
     *            the request to execute
     * @param context
     *            the context to use for the execution,
     *            never <code>null</code>
     * 
     * @return the route the request should take
     * 
     * @throws HttpException
     *             in case of a problem
     */
    private HttpRoute determineRoute(HttpHost originalTarget, HttpRequest request, HttpContext context)
            throws HttpException {
        HttpHost target = originalTarget;
        if (target == null) {
            target = (HttpHost) request.getParams().getParameter(ClientPNames.DEFAULT_HOST);
        }
        if (target == null) {
            throw new IllegalStateException("Target host must not be null, or set in parameters.");
        }

        return routePlanner.determineRoute(target, request, context);
    }

    /**
     * Establishes the target route.
     * 
     * @param route
     *            the route to establish
     * @param context
     *            the context for the request execution
     * 
     * @throws HttpException
     *             in case of a problem
     * @throws IOException
     *             in case of an IO problem
     */
    private void establishRoute(HttpRoute route, HttpContext context) throws HttpException, IOException {

        HttpRouteDirector rowdy = new BasicRouteDirector();
        int step;
        do {
            HttpRoute fact = managedConn.getRoute();
            step = rowdy.nextStep(route, fact);

            switch (step) {

            case HttpRouteDirector.CONNECT_TARGET:
            case HttpRouteDirector.CONNECT_PROXY:
                managedConn.open(route, context, params);
                break;

            case HttpRouteDirector.TUNNEL_TARGET: {
                boolean secure = createTunnelToTarget(route, context);
                LOGGER.debug("Tunnel to target created.");
                managedConn.tunnelTarget(secure, params);
            }
                break;

            case HttpRouteDirector.TUNNEL_PROXY: {
                throw new NotImplementedException("Proxy chaining not supported");
            }

            case HttpRouteDirector.LAYER_PROTOCOL:
                managedConn.layerProtocol(context, params);
                break;

            case HttpRouteDirector.UNREACHABLE:
                throw new IllegalStateException(
                        "Unable to establish route." + "\nplanned = " + route + "\ncurrent = " + fact);

            case HttpRouteDirector.COMPLETE:
                // do nothing
                break;

            default:
                throw new IllegalStateException("Unknown step indicator " + step + " from RouteDirector.");
            } // switch

        } while (step > HttpRouteDirector.COMPLETE);

    } // establishConnection

    /**
     * Returns the connection back to the connection manager
     * and prepares for retrieving a new connection during
     * the next request.
     */
    private void releaseConnection() {
        // Release the connection through the ManagedConnection instead of the
        // ConnectionManager directly. This lets the connection control how
        // it is released.
        try {
            managedConn.releaseConnection();
        } catch (IOException ignored) {
            LOGGER.debug("IOException releasing connection", ignored);
        }
        managedConn = null;
    }

} // class DefaultClientRequestDirector