michael@0: /*
michael@0: * ====================================================================
michael@0: * Licensed to the Apache Software Foundation (ASF) under one
michael@0: * or more contributor license agreements. See the NOTICE file
michael@0: * distributed with this work for additional information
michael@0: * regarding copyright ownership. The ASF licenses this file
michael@0: * to you under the Apache License, Version 2.0 (the
michael@0: * "License"); you may not use this file except in compliance
michael@0: * with the License. You may obtain a copy of the License at
michael@0: *
michael@0: * http://www.apache.org/licenses/LICENSE-2.0
michael@0: *
michael@0: * Unless required by applicable law or agreed to in writing,
michael@0: * software distributed under the License is distributed on an
michael@0: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
michael@0: * KIND, either express or implied. See the License for the
michael@0: * specific language governing permissions and limitations
michael@0: * under the License.
michael@0: * ====================================================================
michael@0: *
michael@0: * This software consists of voluntary contributions made by many
michael@0: * individuals on behalf of the Apache Software Foundation. For more
michael@0: * information on the Apache Software Foundation, please see
michael@0: * .
michael@0: *
michael@0: */
michael@0:
michael@0: package ch.boye.httpclientandroidlib.impl.client;
michael@0:
michael@0: import java.io.IOException;
michael@0: import java.io.InterruptedIOException;
michael@0: import java.net.URI;
michael@0: import java.net.URISyntaxException;
michael@0: import java.util.Locale;
michael@0: import java.util.Map;
michael@0: import java.util.concurrent.TimeUnit;
michael@0:
michael@0: import ch.boye.httpclientandroidlib.annotation.NotThreadSafe;
michael@0:
michael@0: import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
michael@0: /* LogFactory removed by HttpClient for Android script. */
michael@0: import ch.boye.httpclientandroidlib.ConnectionReuseStrategy;
michael@0: import ch.boye.httpclientandroidlib.Header;
michael@0: import ch.boye.httpclientandroidlib.HttpEntity;
michael@0: import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest;
michael@0: import ch.boye.httpclientandroidlib.HttpException;
michael@0: import ch.boye.httpclientandroidlib.HttpHost;
michael@0: import ch.boye.httpclientandroidlib.HttpRequest;
michael@0: import ch.boye.httpclientandroidlib.HttpResponse;
michael@0: import ch.boye.httpclientandroidlib.ProtocolException;
michael@0: import ch.boye.httpclientandroidlib.ProtocolVersion;
michael@0: import ch.boye.httpclientandroidlib.auth.AuthScheme;
michael@0: import ch.boye.httpclientandroidlib.auth.AuthScope;
michael@0: import ch.boye.httpclientandroidlib.auth.AuthState;
michael@0: import ch.boye.httpclientandroidlib.auth.AuthenticationException;
michael@0: import ch.boye.httpclientandroidlib.auth.Credentials;
michael@0: import ch.boye.httpclientandroidlib.auth.MalformedChallengeException;
michael@0: import ch.boye.httpclientandroidlib.client.AuthenticationHandler;
michael@0: import ch.boye.httpclientandroidlib.client.RedirectStrategy;
michael@0: import ch.boye.httpclientandroidlib.client.RequestDirector;
michael@0: import ch.boye.httpclientandroidlib.client.CredentialsProvider;
michael@0: import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler;
michael@0: import ch.boye.httpclientandroidlib.client.NonRepeatableRequestException;
michael@0: import ch.boye.httpclientandroidlib.client.RedirectException;
michael@0: import ch.boye.httpclientandroidlib.client.UserTokenHandler;
michael@0: import ch.boye.httpclientandroidlib.client.methods.AbortableHttpRequest;
michael@0: import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
michael@0: import ch.boye.httpclientandroidlib.client.params.ClientPNames;
michael@0: import ch.boye.httpclientandroidlib.client.params.HttpClientParams;
michael@0: import ch.boye.httpclientandroidlib.client.protocol.ClientContext;
michael@0: import ch.boye.httpclientandroidlib.client.utils.URIUtils;
michael@0: import ch.boye.httpclientandroidlib.conn.BasicManagedEntity;
michael@0: import ch.boye.httpclientandroidlib.conn.ClientConnectionManager;
michael@0: import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest;
michael@0: import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy;
michael@0: import ch.boye.httpclientandroidlib.conn.ManagedClientConnection;
michael@0: import ch.boye.httpclientandroidlib.conn.params.ConnManagerParams;
michael@0: import ch.boye.httpclientandroidlib.conn.routing.BasicRouteDirector;
michael@0: import ch.boye.httpclientandroidlib.conn.routing.HttpRoute;
michael@0: import ch.boye.httpclientandroidlib.conn.routing.HttpRouteDirector;
michael@0: import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner;
michael@0: import ch.boye.httpclientandroidlib.conn.scheme.Scheme;
michael@0: import ch.boye.httpclientandroidlib.entity.BufferedHttpEntity;
michael@0: import ch.boye.httpclientandroidlib.impl.conn.ConnectionShutdownException;
michael@0: import ch.boye.httpclientandroidlib.message.BasicHttpRequest;
michael@0: import ch.boye.httpclientandroidlib.params.HttpConnectionParams;
michael@0: import ch.boye.httpclientandroidlib.params.HttpParams;
michael@0: import ch.boye.httpclientandroidlib.params.HttpProtocolParams;
michael@0: import ch.boye.httpclientandroidlib.protocol.ExecutionContext;
michael@0: import ch.boye.httpclientandroidlib.protocol.HttpContext;
michael@0: import ch.boye.httpclientandroidlib.protocol.HttpProcessor;
michael@0: import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor;
michael@0: import ch.boye.httpclientandroidlib.util.EntityUtils;
michael@0:
michael@0: /**
michael@0: * Default implementation of {@link RequestDirector}.
michael@0: *
michael@0: * The following parameters can be used to customize the behavior of this
michael@0: * class:
michael@0: *
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}
michael@0: * - {@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}
michael@0: * - {@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}
michael@0: * - {@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY}
michael@0: * - {@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#DATE_PATTERNS}
michael@0: * - {@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}
michael@0: * - {@link ch.boye.httpclientandroidlib.auth.params.AuthPNames#CREDENTIAL_CHARSET}
michael@0: * - {@link ch.boye.httpclientandroidlib.client.params.ClientPNames#COOKIE_POLICY}
michael@0: * - {@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_AUTHENTICATION}
michael@0: * - {@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_REDIRECTS}
michael@0: * - {@link ch.boye.httpclientandroidlib.client.params.ClientPNames#MAX_REDIRECTS}
michael@0: * - {@link ch.boye.httpclientandroidlib.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}
michael@0: * - {@link ch.boye.httpclientandroidlib.client.params.ClientPNames#VIRTUAL_HOST}
michael@0: * - {@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HOST}
michael@0: * - {@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HEADERS}
michael@0: *
michael@0: *
michael@0: * @since 4.0
michael@0: */
michael@0: @SuppressWarnings("deprecation")
michael@0: @NotThreadSafe // e.g. managedConn
michael@0: public class DefaultRequestDirector implements RequestDirector {
michael@0:
michael@0: public HttpClientAndroidLog log;
michael@0:
michael@0: /** The connection manager. */
michael@0: protected final ClientConnectionManager connManager;
michael@0:
michael@0: /** The route planner. */
michael@0: protected final HttpRoutePlanner routePlanner;
michael@0:
michael@0: /** The connection re-use strategy. */
michael@0: protected final ConnectionReuseStrategy reuseStrategy;
michael@0:
michael@0: /** The keep-alive duration strategy. */
michael@0: protected final ConnectionKeepAliveStrategy keepAliveStrategy;
michael@0:
michael@0: /** The request executor. */
michael@0: protected final HttpRequestExecutor requestExec;
michael@0:
michael@0: /** The HTTP protocol processor. */
michael@0: protected final HttpProcessor httpProcessor;
michael@0:
michael@0: /** The request retry handler. */
michael@0: protected final HttpRequestRetryHandler retryHandler;
michael@0:
michael@0: /** The redirect handler. */
michael@0: @Deprecated
michael@0: protected final ch.boye.httpclientandroidlib.client.RedirectHandler redirectHandler = null;
michael@0:
michael@0: /** The redirect strategy. */
michael@0: protected final RedirectStrategy redirectStrategy;
michael@0:
michael@0: /** The target authentication handler. */
michael@0: protected final AuthenticationHandler targetAuthHandler;
michael@0:
michael@0: /** The proxy authentication handler. */
michael@0: protected final AuthenticationHandler proxyAuthHandler;
michael@0:
michael@0: /** The user token handler. */
michael@0: protected final UserTokenHandler userTokenHandler;
michael@0:
michael@0: /** The HTTP parameters. */
michael@0: protected final HttpParams params;
michael@0:
michael@0: /** The currently allocated connection. */
michael@0: protected ManagedClientConnection managedConn;
michael@0:
michael@0: protected final AuthState targetAuthState;
michael@0:
michael@0: protected final AuthState proxyAuthState;
michael@0:
michael@0: private int execCount;
michael@0:
michael@0: private int redirectCount;
michael@0:
michael@0: private int maxRedirects;
michael@0:
michael@0: private HttpHost virtualHost;
michael@0:
michael@0: @Deprecated
michael@0: public DefaultRequestDirector(
michael@0: final HttpRequestExecutor requestExec,
michael@0: final ClientConnectionManager conman,
michael@0: final ConnectionReuseStrategy reustrat,
michael@0: final ConnectionKeepAliveStrategy kastrat,
michael@0: final HttpRoutePlanner rouplan,
michael@0: final HttpProcessor httpProcessor,
michael@0: final HttpRequestRetryHandler retryHandler,
michael@0: final ch.boye.httpclientandroidlib.client.RedirectHandler redirectHandler,
michael@0: final AuthenticationHandler targetAuthHandler,
michael@0: final AuthenticationHandler proxyAuthHandler,
michael@0: final UserTokenHandler userTokenHandler,
michael@0: final HttpParams params) {
michael@0: this(new HttpClientAndroidLog(DefaultRequestDirector.class),
michael@0: requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler,
michael@0: new DefaultRedirectStrategyAdaptor(redirectHandler),
michael@0: targetAuthHandler, proxyAuthHandler, userTokenHandler, params);
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * @since 4.1
michael@0: */
michael@0: public DefaultRequestDirector(
michael@0: final HttpClientAndroidLog log,
michael@0: final HttpRequestExecutor requestExec,
michael@0: final ClientConnectionManager conman,
michael@0: final ConnectionReuseStrategy reustrat,
michael@0: final ConnectionKeepAliveStrategy kastrat,
michael@0: final HttpRoutePlanner rouplan,
michael@0: final HttpProcessor httpProcessor,
michael@0: final HttpRequestRetryHandler retryHandler,
michael@0: final RedirectStrategy redirectStrategy,
michael@0: final AuthenticationHandler targetAuthHandler,
michael@0: final AuthenticationHandler proxyAuthHandler,
michael@0: final UserTokenHandler userTokenHandler,
michael@0: final HttpParams params) {
michael@0:
michael@0: if (log == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("Log may not be null.");
michael@0: }
michael@0: if (requestExec == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("Request executor may not be null.");
michael@0: }
michael@0: if (conman == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("Client connection manager may not be null.");
michael@0: }
michael@0: if (reustrat == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("Connection reuse strategy may not be null.");
michael@0: }
michael@0: if (kastrat == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("Connection keep alive strategy may not be null.");
michael@0: }
michael@0: if (rouplan == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("Route planner may not be null.");
michael@0: }
michael@0: if (httpProcessor == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("HTTP protocol processor may not be null.");
michael@0: }
michael@0: if (retryHandler == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("HTTP request retry handler may not be null.");
michael@0: }
michael@0: if (redirectStrategy == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("Redirect strategy may not be null.");
michael@0: }
michael@0: if (targetAuthHandler == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("Target authentication handler may not be null.");
michael@0: }
michael@0: if (proxyAuthHandler == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("Proxy authentication handler may not be null.");
michael@0: }
michael@0: if (userTokenHandler == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("User token handler may not be null.");
michael@0: }
michael@0: if (params == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("HTTP parameters may not be null");
michael@0: }
michael@0: this.log = log;
michael@0: this.requestExec = requestExec;
michael@0: this.connManager = conman;
michael@0: this.reuseStrategy = reustrat;
michael@0: this.keepAliveStrategy = kastrat;
michael@0: this.routePlanner = rouplan;
michael@0: this.httpProcessor = httpProcessor;
michael@0: this.retryHandler = retryHandler;
michael@0: this.redirectStrategy = redirectStrategy;
michael@0: this.targetAuthHandler = targetAuthHandler;
michael@0: this.proxyAuthHandler = proxyAuthHandler;
michael@0: this.userTokenHandler = userTokenHandler;
michael@0: this.params = params;
michael@0:
michael@0: this.managedConn = null;
michael@0:
michael@0: this.execCount = 0;
michael@0: this.redirectCount = 0;
michael@0: this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100);
michael@0: this.targetAuthState = new AuthState();
michael@0: this.proxyAuthState = new AuthState();
michael@0: } // constructor
michael@0:
michael@0:
michael@0: private RequestWrapper wrapRequest(
michael@0: final HttpRequest request) throws ProtocolException {
michael@0: if (request instanceof HttpEntityEnclosingRequest) {
michael@0: return new EntityEnclosingRequestWrapper(
michael@0: (HttpEntityEnclosingRequest) request);
michael@0: } else {
michael@0: return new RequestWrapper(
michael@0: request);
michael@0: }
michael@0: }
michael@0:
michael@0:
michael@0: protected void rewriteRequestURI(
michael@0: final RequestWrapper request,
michael@0: final HttpRoute route) throws ProtocolException {
michael@0: try {
michael@0:
michael@0: URI uri = request.getURI();
michael@0: if (route.getProxyHost() != null && !route.isTunnelled()) {
michael@0: // Make sure the request URI is absolute
michael@0: if (!uri.isAbsolute()) {
michael@0: HttpHost target = route.getTargetHost();
michael@0: uri = URIUtils.rewriteURI(uri, target);
michael@0: request.setURI(uri);
michael@0: }
michael@0: } else {
michael@0: // Make sure the request URI is relative
michael@0: if (uri.isAbsolute()) {
michael@0: uri = URIUtils.rewriteURI(uri, null);
michael@0: request.setURI(uri);
michael@0: }
michael@0: }
michael@0:
michael@0: } catch (URISyntaxException ex) {
michael@0: throw new ProtocolException("Invalid URI: " +
michael@0: request.getRequestLine().getUri(), ex);
michael@0: }
michael@0: }
michael@0:
michael@0:
michael@0: // non-javadoc, see interface ClientRequestDirector
michael@0: public HttpResponse execute(HttpHost target, HttpRequest request,
michael@0: HttpContext context)
michael@0: throws HttpException, IOException {
michael@0:
michael@0: HttpRequest orig = request;
michael@0: RequestWrapper origWrapper = wrapRequest(orig);
michael@0: origWrapper.setParams(params);
michael@0: HttpRoute origRoute = determineRoute(target, origWrapper, context);
michael@0:
michael@0: virtualHost = (HttpHost) orig.getParams().getParameter(
michael@0: ClientPNames.VIRTUAL_HOST);
michael@0:
michael@0: // HTTPCLIENT-1092 - add the port if necessary
michael@0: if (virtualHost != null && virtualHost.getPort() == -1)
michael@0: {
michael@0: int port = target.getPort();
michael@0: if (port != -1){
michael@0: virtualHost = new HttpHost(virtualHost.getHostName(), port, virtualHost.getSchemeName());
michael@0: }
michael@0: }
michael@0:
michael@0: RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute);
michael@0:
michael@0: boolean reuse = false;
michael@0: boolean done = false;
michael@0: try {
michael@0: HttpResponse response = null;
michael@0: while (!done) {
michael@0: // In this loop, the RoutedRequest may be replaced by a
michael@0: // followup request and route. The request and route passed
michael@0: // in the method arguments will be replaced. The original
michael@0: // request is still available in 'orig'.
michael@0:
michael@0: RequestWrapper wrapper = roureq.getRequest();
michael@0: HttpRoute route = roureq.getRoute();
michael@0: response = null;
michael@0:
michael@0: // See if we have a user token bound to the execution context
michael@0: Object userToken = context.getAttribute(ClientContext.USER_TOKEN);
michael@0:
michael@0: // Allocate connection if needed
michael@0: if (managedConn == null) {
michael@0: ClientConnectionRequest connRequest = connManager.requestConnection(
michael@0: route, userToken);
michael@0: if (orig instanceof AbortableHttpRequest) {
michael@0: ((AbortableHttpRequest) orig).setConnectionRequest(connRequest);
michael@0: }
michael@0:
michael@0: long timeout = ConnManagerParams.getTimeout(params);
michael@0: try {
michael@0: managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
michael@0: } catch(InterruptedException interrupted) {
michael@0: InterruptedIOException iox = new InterruptedIOException();
michael@0: iox.initCause(interrupted);
michael@0: throw iox;
michael@0: }
michael@0:
michael@0: if (HttpConnectionParams.isStaleCheckingEnabled(params)) {
michael@0: // validate connection
michael@0: if (managedConn.isOpen()) {
michael@0: this.log.debug("Stale connection check");
michael@0: if (managedConn.isStale()) {
michael@0: this.log.debug("Stale connection detected");
michael@0: managedConn.close();
michael@0: }
michael@0: }
michael@0: }
michael@0: }
michael@0:
michael@0: if (orig instanceof AbortableHttpRequest) {
michael@0: ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn);
michael@0: }
michael@0:
michael@0: try {
michael@0: tryConnect(roureq, context);
michael@0: } catch (TunnelRefusedException ex) {
michael@0: if (this.log.isDebugEnabled()) {
michael@0: this.log.debug(ex.getMessage());
michael@0: }
michael@0: response = ex.getResponse();
michael@0: break;
michael@0: }
michael@0:
michael@0: // Reset headers on the request wrapper
michael@0: wrapper.resetHeaders();
michael@0:
michael@0: // Re-write request URI if needed
michael@0: rewriteRequestURI(wrapper, route);
michael@0:
michael@0: // Use virtual host if set
michael@0: target = virtualHost;
michael@0:
michael@0: if (target == null) {
michael@0: target = route.getTargetHost();
michael@0: }
michael@0:
michael@0: HttpHost proxy = route.getProxyHost();
michael@0:
michael@0: // Populate the execution context
michael@0: context.setAttribute(ExecutionContext.HTTP_TARGET_HOST,
michael@0: target);
michael@0: context.setAttribute(ExecutionContext.HTTP_PROXY_HOST,
michael@0: proxy);
michael@0: context.setAttribute(ExecutionContext.HTTP_CONNECTION,
michael@0: managedConn);
michael@0: context.setAttribute(ClientContext.TARGET_AUTH_STATE,
michael@0: targetAuthState);
michael@0: context.setAttribute(ClientContext.PROXY_AUTH_STATE,
michael@0: proxyAuthState);
michael@0:
michael@0: // Run request protocol interceptors
michael@0: requestExec.preProcess(wrapper, httpProcessor, context);
michael@0:
michael@0: response = tryExecute(roureq, context);
michael@0: if (response == null) {
michael@0: // Need to start over
michael@0: continue;
michael@0: }
michael@0:
michael@0: // Run response protocol interceptors
michael@0: response.setParams(params);
michael@0: requestExec.postProcess(response, httpProcessor, context);
michael@0:
michael@0:
michael@0: // The connection is in or can be brought to a re-usable state.
michael@0: reuse = reuseStrategy.keepAlive(response, context);
michael@0: if (reuse) {
michael@0: // Set the idle duration of this connection
michael@0: long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
michael@0: if (this.log.isDebugEnabled()) {
michael@0: String s;
michael@0: if (duration > 0) {
michael@0: s = "for " + duration + " " + TimeUnit.MILLISECONDS;
michael@0: } else {
michael@0: s = "indefinitely";
michael@0: }
michael@0: this.log.debug("Connection can be kept alive " + s);
michael@0: }
michael@0: managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS);
michael@0: }
michael@0:
michael@0: RoutedRequest followup = handleResponse(roureq, response, context);
michael@0: if (followup == null) {
michael@0: done = true;
michael@0: } else {
michael@0: if (reuse) {
michael@0: // Make sure the response body is fully consumed, if present
michael@0: HttpEntity entity = response.getEntity();
michael@0: EntityUtils.consume(entity);
michael@0: // entity consumed above is not an auto-release entity,
michael@0: // need to mark the connection re-usable explicitly
michael@0: managedConn.markReusable();
michael@0: } else {
michael@0: managedConn.close();
michael@0: invalidateAuthIfSuccessful(this.proxyAuthState);
michael@0: invalidateAuthIfSuccessful(this.targetAuthState);
michael@0: }
michael@0: // check if we can use the same connection for the followup
michael@0: if (!followup.getRoute().equals(roureq.getRoute())) {
michael@0: releaseConnection();
michael@0: }
michael@0: roureq = followup;
michael@0: }
michael@0:
michael@0: if (managedConn != null && userToken == null) {
michael@0: userToken = userTokenHandler.getUserToken(context);
michael@0: context.setAttribute(ClientContext.USER_TOKEN, userToken);
michael@0: if (userToken != null) {
michael@0: managedConn.setState(userToken);
michael@0: }
michael@0: }
michael@0:
michael@0: } // while not done
michael@0:
michael@0:
michael@0: // check for entity, release connection if possible
michael@0: if ((response == null) || (response.getEntity() == null) ||
michael@0: !response.getEntity().isStreaming()) {
michael@0: // connection not needed and (assumed to be) in re-usable state
michael@0: if (reuse)
michael@0: managedConn.markReusable();
michael@0: releaseConnection();
michael@0: } else {
michael@0: // install an auto-release entity
michael@0: HttpEntity entity = response.getEntity();
michael@0: entity = new BasicManagedEntity(entity, managedConn, reuse);
michael@0: response.setEntity(entity);
michael@0: }
michael@0:
michael@0: return response;
michael@0:
michael@0: } catch (ConnectionShutdownException ex) {
michael@0: InterruptedIOException ioex = new InterruptedIOException(
michael@0: "Connection has been shut down");
michael@0: ioex.initCause(ex);
michael@0: throw ioex;
michael@0: } catch (HttpException ex) {
michael@0: abortConnection();
michael@0: throw ex;
michael@0: } catch (IOException ex) {
michael@0: abortConnection();
michael@0: throw ex;
michael@0: } catch (RuntimeException ex) {
michael@0: abortConnection();
michael@0: throw ex;
michael@0: }
michael@0: } // execute
michael@0:
michael@0: /**
michael@0: * Establish connection either directly or through a tunnel and retry in case of
michael@0: * a recoverable I/O failure
michael@0: */
michael@0: private void tryConnect(
michael@0: final RoutedRequest req, final HttpContext context) throws HttpException, IOException {
michael@0: HttpRoute route = req.getRoute();
michael@0:
michael@0: int connectCount = 0;
michael@0: for (;;) {
michael@0: // Increment connect count
michael@0: connectCount++;
michael@0: try {
michael@0: if (!managedConn.isOpen()) {
michael@0: managedConn.open(route, context, params);
michael@0: } else {
michael@0: managedConn.setSocketTimeout(HttpConnectionParams.getSoTimeout(params));
michael@0: }
michael@0: establishRoute(route, context);
michael@0: break;
michael@0: } catch (IOException ex) {
michael@0: try {
michael@0: managedConn.close();
michael@0: } catch (IOException ignore) {
michael@0: }
michael@0: if (retryHandler.retryRequest(ex, connectCount, context)) {
michael@0: if (this.log.isInfoEnabled()) {
michael@0: this.log.info("I/O exception ("+ ex.getClass().getName() +
michael@0: ") caught when connecting to the target host: "
michael@0: + ex.getMessage());
michael@0: }
michael@0: if (this.log.isDebugEnabled()) {
michael@0: this.log.debug(ex.getMessage(), ex);
michael@0: }
michael@0: this.log.info("Retrying connect");
michael@0: } else {
michael@0: throw ex;
michael@0: }
michael@0: }
michael@0: }
michael@0: }
michael@0:
michael@0: /**
michael@0: * Execute request and retry in case of a recoverable I/O failure
michael@0: */
michael@0: private HttpResponse tryExecute(
michael@0: final RoutedRequest req, final HttpContext context) throws HttpException, IOException {
michael@0: RequestWrapper wrapper = req.getRequest();
michael@0: HttpRoute route = req.getRoute();
michael@0: HttpResponse response = null;
michael@0:
michael@0: Exception retryReason = null;
michael@0: for (;;) {
michael@0: // Increment total exec count (with redirects)
michael@0: execCount++;
michael@0: // Increment exec count for this particular request
michael@0: wrapper.incrementExecCount();
michael@0: if (!wrapper.isRepeatable()) {
michael@0: this.log.debug("Cannot retry non-repeatable request");
michael@0: if (retryReason != null) {
michael@0: throw new NonRepeatableRequestException("Cannot retry request " +
michael@0: "with a non-repeatable request entity. The cause lists the " +
michael@0: "reason the original request failed.", retryReason);
michael@0: } else {
michael@0: throw new NonRepeatableRequestException("Cannot retry request " +
michael@0: "with a non-repeatable request entity.");
michael@0: }
michael@0: }
michael@0:
michael@0: try {
michael@0: if (!managedConn.isOpen()) {
michael@0: // If we have a direct route to the target host
michael@0: // just re-open connection and re-try the request
michael@0: if (!route.isTunnelled()) {
michael@0: this.log.debug("Reopening the direct connection.");
michael@0: managedConn.open(route, context, params);
michael@0: } else {
michael@0: // otherwise give up
michael@0: this.log.debug("Proxied connection. Need to start over.");
michael@0: break;
michael@0: }
michael@0: }
michael@0:
michael@0: if (this.log.isDebugEnabled()) {
michael@0: this.log.debug("Attempt " + execCount + " to execute request");
michael@0: }
michael@0: response = requestExec.execute(wrapper, managedConn, context);
michael@0: break;
michael@0:
michael@0: } catch (IOException ex) {
michael@0: this.log.debug("Closing the connection.");
michael@0: try {
michael@0: managedConn.close();
michael@0: } catch (IOException ignore) {
michael@0: }
michael@0: if (retryHandler.retryRequest(ex, wrapper.getExecCount(), context)) {
michael@0: if (this.log.isInfoEnabled()) {
michael@0: this.log.info("I/O exception ("+ ex.getClass().getName() +
michael@0: ") caught when processing request: "
michael@0: + ex.getMessage());
michael@0: }
michael@0: if (this.log.isDebugEnabled()) {
michael@0: this.log.debug(ex.getMessage(), ex);
michael@0: }
michael@0: this.log.info("Retrying request");
michael@0: retryReason = ex;
michael@0: } else {
michael@0: throw ex;
michael@0: }
michael@0: }
michael@0: }
michael@0: return response;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Returns the connection back to the connection manager
michael@0: * and prepares for retrieving a new connection during
michael@0: * the next request.
michael@0: */
michael@0: protected void releaseConnection() {
michael@0: // Release the connection through the ManagedConnection instead of the
michael@0: // ConnectionManager directly. This lets the connection control how
michael@0: // it is released.
michael@0: try {
michael@0: managedConn.releaseConnection();
michael@0: } catch(IOException ignored) {
michael@0: this.log.debug("IOException releasing connection", ignored);
michael@0: }
michael@0: managedConn = null;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Determines the route for a request.
michael@0: * Called by {@link #execute}
michael@0: * to determine the route for either the original or a followup request.
michael@0: *
michael@0: * @param target the target host for the request.
michael@0: * Implementations may accept null
michael@0: * if they can still determine a route, for example
michael@0: * to a default target or by inspecting the request.
michael@0: * @param request the request to execute
michael@0: * @param context the context to use for the execution,
michael@0: * never null
michael@0: *
michael@0: * @return the route the request should take
michael@0: *
michael@0: * @throws HttpException in case of a problem
michael@0: */
michael@0: protected HttpRoute determineRoute(HttpHost target,
michael@0: HttpRequest request,
michael@0: HttpContext context)
michael@0: throws HttpException {
michael@0:
michael@0: if (target == null) {
michael@0: target = (HttpHost) request.getParams().getParameter(
michael@0: ClientPNames.DEFAULT_HOST);
michael@0: }
michael@0: if (target == null) {
michael@0: throw new IllegalStateException
michael@0: ("Target host must not be null, or set in parameters.");
michael@0: }
michael@0:
michael@0: return this.routePlanner.determineRoute(target, request, context);
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Establishes the target route.
michael@0: *
michael@0: * @param route the route to establish
michael@0: * @param context the context for the request execution
michael@0: *
michael@0: * @throws HttpException in case of a problem
michael@0: * @throws IOException in case of an IO problem
michael@0: */
michael@0: protected void establishRoute(HttpRoute route, HttpContext context)
michael@0: throws HttpException, IOException {
michael@0:
michael@0: HttpRouteDirector rowdy = new BasicRouteDirector();
michael@0: int step;
michael@0: do {
michael@0: HttpRoute fact = managedConn.getRoute();
michael@0: step = rowdy.nextStep(route, fact);
michael@0:
michael@0: switch (step) {
michael@0:
michael@0: case HttpRouteDirector.CONNECT_TARGET:
michael@0: case HttpRouteDirector.CONNECT_PROXY:
michael@0: managedConn.open(route, context, this.params);
michael@0: break;
michael@0:
michael@0: case HttpRouteDirector.TUNNEL_TARGET: {
michael@0: boolean secure = createTunnelToTarget(route, context);
michael@0: this.log.debug("Tunnel to target created.");
michael@0: managedConn.tunnelTarget(secure, this.params);
michael@0: } break;
michael@0:
michael@0: case HttpRouteDirector.TUNNEL_PROXY: {
michael@0: // The most simple example for this case is a proxy chain
michael@0: // of two proxies, where P1 must be tunnelled to P2.
michael@0: // route: Source -> P1 -> P2 -> Target (3 hops)
michael@0: // fact: Source -> P1 -> Target (2 hops)
michael@0: final int hop = fact.getHopCount()-1; // the hop to establish
michael@0: boolean secure = createTunnelToProxy(route, hop, context);
michael@0: this.log.debug("Tunnel to proxy created.");
michael@0: managedConn.tunnelProxy(route.getHopTarget(hop),
michael@0: secure, this.params);
michael@0: } break;
michael@0:
michael@0:
michael@0: case HttpRouteDirector.LAYER_PROTOCOL:
michael@0: managedConn.layerProtocol(context, this.params);
michael@0: break;
michael@0:
michael@0: case HttpRouteDirector.UNREACHABLE:
michael@0: throw new HttpException("Unable to establish route: " +
michael@0: "planned = " + route + "; current = " + fact);
michael@0: case HttpRouteDirector.COMPLETE:
michael@0: // do nothing
michael@0: break;
michael@0: default:
michael@0: throw new IllegalStateException("Unknown step indicator "
michael@0: + step + " from RouteDirector.");
michael@0: }
michael@0:
michael@0: } while (step > HttpRouteDirector.COMPLETE);
michael@0:
michael@0: } // establishConnection
michael@0:
michael@0:
michael@0: /**
michael@0: * Creates a tunnel to the target server.
michael@0: * The connection must be established to the (last) proxy.
michael@0: * A CONNECT request for tunnelling through the proxy will
michael@0: * be created and sent, the response received and checked.
michael@0: * This method does not update the connection with
michael@0: * information about the tunnel, that is left to the caller.
michael@0: *
michael@0: * @param route the route to establish
michael@0: * @param context the context for request execution
michael@0: *
michael@0: * @return true
if the tunnelled route is secure,
michael@0: * false
otherwise.
michael@0: * The implementation here always returns false
,
michael@0: * but derived classes may override.
michael@0: *
michael@0: * @throws HttpException in case of a problem
michael@0: * @throws IOException in case of an IO problem
michael@0: */
michael@0: protected boolean createTunnelToTarget(HttpRoute route,
michael@0: HttpContext context)
michael@0: throws HttpException, IOException {
michael@0:
michael@0: HttpHost proxy = route.getProxyHost();
michael@0: HttpHost target = route.getTargetHost();
michael@0: HttpResponse response = null;
michael@0:
michael@0: boolean done = false;
michael@0: while (!done) {
michael@0:
michael@0: done = true;
michael@0:
michael@0: if (!this.managedConn.isOpen()) {
michael@0: this.managedConn.open(route, context, this.params);
michael@0: }
michael@0:
michael@0: HttpRequest connect = createConnectRequest(route, context);
michael@0: connect.setParams(this.params);
michael@0:
michael@0: // Populate the execution context
michael@0: context.setAttribute(ExecutionContext.HTTP_TARGET_HOST,
michael@0: target);
michael@0: context.setAttribute(ExecutionContext.HTTP_PROXY_HOST,
michael@0: proxy);
michael@0: context.setAttribute(ExecutionContext.HTTP_CONNECTION,
michael@0: managedConn);
michael@0: context.setAttribute(ClientContext.TARGET_AUTH_STATE,
michael@0: targetAuthState);
michael@0: context.setAttribute(ClientContext.PROXY_AUTH_STATE,
michael@0: proxyAuthState);
michael@0: context.setAttribute(ExecutionContext.HTTP_REQUEST,
michael@0: connect);
michael@0:
michael@0: this.requestExec.preProcess(connect, this.httpProcessor, context);
michael@0:
michael@0: response = this.requestExec.execute(connect, this.managedConn, context);
michael@0:
michael@0: response.setParams(this.params);
michael@0: this.requestExec.postProcess(response, this.httpProcessor, context);
michael@0:
michael@0: int status = response.getStatusLine().getStatusCode();
michael@0: if (status < 200) {
michael@0: throw new HttpException("Unexpected response to CONNECT request: " +
michael@0: response.getStatusLine());
michael@0: }
michael@0:
michael@0: CredentialsProvider credsProvider = (CredentialsProvider)
michael@0: context.getAttribute(ClientContext.CREDS_PROVIDER);
michael@0:
michael@0: if (credsProvider != null && HttpClientParams.isAuthenticating(params)) {
michael@0: if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) {
michael@0:
michael@0: this.log.debug("Proxy requested authentication");
michael@0: Map challenges = this.proxyAuthHandler.getChallenges(
michael@0: response, context);
michael@0: try {
michael@0: processChallenges(
michael@0: challenges, this.proxyAuthState, this.proxyAuthHandler,
michael@0: response, context);
michael@0: } catch (AuthenticationException ex) {
michael@0: if (this.log.isWarnEnabled()) {
michael@0: this.log.warn("Authentication error: " + ex.getMessage());
michael@0: break;
michael@0: }
michael@0: }
michael@0: updateAuthState(this.proxyAuthState, proxy, credsProvider);
michael@0:
michael@0: if (this.proxyAuthState.getCredentials() != null) {
michael@0: done = false;
michael@0:
michael@0: // Retry request
michael@0: if (this.reuseStrategy.keepAlive(response, context)) {
michael@0: this.log.debug("Connection kept alive");
michael@0: // Consume response content
michael@0: HttpEntity entity = response.getEntity();
michael@0: EntityUtils.consume(entity);
michael@0: } else {
michael@0: this.managedConn.close();
michael@0: }
michael@0:
michael@0: }
michael@0:
michael@0: } else {
michael@0: // Reset proxy auth scope
michael@0: this.proxyAuthState.setAuthScope(null);
michael@0: }
michael@0: }
michael@0: }
michael@0:
michael@0: int status = response.getStatusLine().getStatusCode(); // can't be null
michael@0:
michael@0: if (status > 299) {
michael@0:
michael@0: // Buffer response content
michael@0: HttpEntity entity = response.getEntity();
michael@0: if (entity != null) {
michael@0: response.setEntity(new BufferedHttpEntity(entity));
michael@0: }
michael@0:
michael@0: this.managedConn.close();
michael@0: throw new TunnelRefusedException("CONNECT refused by proxy: " +
michael@0: response.getStatusLine(), response);
michael@0: }
michael@0:
michael@0: this.managedConn.markReusable();
michael@0:
michael@0: // How to decide on security of the tunnelled connection?
michael@0: // The socket factory knows only about the segment to the proxy.
michael@0: // Even if that is secure, the hop to the target may be insecure.
michael@0: // Leave it to derived classes, consider insecure by default here.
michael@0: return false;
michael@0:
michael@0: } // createTunnelToTarget
michael@0:
michael@0:
michael@0:
michael@0: /**
michael@0: * Creates a tunnel to an intermediate proxy.
michael@0: * This method is not implemented in this class.
michael@0: * It just throws an exception here.
michael@0: *
michael@0: * @param route the route to establish
michael@0: * @param hop the hop in the route to establish now.
michael@0: * route.getHopTarget(hop)
michael@0: * will return the proxy to tunnel to.
michael@0: * @param context the context for request execution
michael@0: *
michael@0: * @return true
if the partially tunnelled connection
michael@0: * is secure, false
otherwise.
michael@0: *
michael@0: * @throws HttpException in case of a problem
michael@0: * @throws IOException in case of an IO problem
michael@0: */
michael@0: protected boolean createTunnelToProxy(HttpRoute route, int hop,
michael@0: HttpContext context)
michael@0: throws HttpException, IOException {
michael@0:
michael@0: // Have a look at createTunnelToTarget and replicate the parts
michael@0: // you need in a custom derived class. If your proxies don't require
michael@0: // authentication, it is not too hard. But for the stock version of
michael@0: // HttpClient, we cannot make such simplifying assumptions and would
michael@0: // have to include proxy authentication code. The HttpComponents team
michael@0: // is currently not in a position to support rarely used code of this
michael@0: // complexity. Feel free to submit patches that refactor the code in
michael@0: // createTunnelToTarget to facilitate re-use for proxy tunnelling.
michael@0:
michael@0: throw new HttpException("Proxy chains are not supported.");
michael@0: }
michael@0:
michael@0:
michael@0:
michael@0: /**
michael@0: * Creates the CONNECT request for tunnelling.
michael@0: * Called by {@link #createTunnelToTarget createTunnelToTarget}.
michael@0: *
michael@0: * @param route the route to establish
michael@0: * @param context the context for request execution
michael@0: *
michael@0: * @return the CONNECT request for tunnelling
michael@0: */
michael@0: protected HttpRequest createConnectRequest(HttpRoute route,
michael@0: HttpContext context) {
michael@0: // see RFC 2817, section 5.2 and
michael@0: // INTERNET-DRAFT: Tunneling TCP based protocols through
michael@0: // Web proxy servers
michael@0:
michael@0: HttpHost target = route.getTargetHost();
michael@0:
michael@0: String host = target.getHostName();
michael@0: int port = target.getPort();
michael@0: if (port < 0) {
michael@0: Scheme scheme = connManager.getSchemeRegistry().
michael@0: getScheme(target.getSchemeName());
michael@0: port = scheme.getDefaultPort();
michael@0: }
michael@0:
michael@0: StringBuilder buffer = new StringBuilder(host.length() + 6);
michael@0: buffer.append(host);
michael@0: buffer.append(':');
michael@0: buffer.append(Integer.toString(port));
michael@0:
michael@0: String authority = buffer.toString();
michael@0: ProtocolVersion ver = HttpProtocolParams.getVersion(params);
michael@0: HttpRequest req = new BasicHttpRequest
michael@0: ("CONNECT", authority, ver);
michael@0:
michael@0: return req;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Analyzes a response to check need for a followup.
michael@0: *
michael@0: * @param roureq the request and route.
michael@0: * @param response the response to analayze
michael@0: * @param context the context used for the current request execution
michael@0: *
michael@0: * @return the followup request and route if there is a followup, or
michael@0: * null
if the response should be returned as is
michael@0: *
michael@0: * @throws HttpException in case of a problem
michael@0: * @throws IOException in case of an IO problem
michael@0: */
michael@0: protected RoutedRequest handleResponse(RoutedRequest roureq,
michael@0: HttpResponse response,
michael@0: HttpContext context)
michael@0: throws HttpException, IOException {
michael@0:
michael@0: HttpRoute route = roureq.getRoute();
michael@0: RequestWrapper request = roureq.getRequest();
michael@0:
michael@0: HttpParams params = request.getParams();
michael@0: if (HttpClientParams.isRedirecting(params) &&
michael@0: this.redirectStrategy.isRedirected(request, response, context)) {
michael@0:
michael@0: if (redirectCount >= maxRedirects) {
michael@0: throw new RedirectException("Maximum redirects ("
michael@0: + maxRedirects + ") exceeded");
michael@0: }
michael@0: redirectCount++;
michael@0:
michael@0: // Virtual host cannot be used any longer
michael@0: virtualHost = null;
michael@0:
michael@0: HttpUriRequest redirect = redirectStrategy.getRedirect(request, response, context);
michael@0: HttpRequest orig = request.getOriginal();
michael@0: redirect.setHeaders(orig.getAllHeaders());
michael@0:
michael@0: URI uri = redirect.getURI();
michael@0: if (uri.getHost() == null) {
michael@0: throw new ProtocolException("Redirect URI does not specify a valid host name: " + uri);
michael@0: }
michael@0:
michael@0: HttpHost newTarget = new HttpHost(
michael@0: uri.getHost(),
michael@0: uri.getPort(),
michael@0: uri.getScheme());
michael@0:
michael@0: // Unset auth scope
michael@0: targetAuthState.setAuthScope(null);
michael@0: proxyAuthState.setAuthScope(null);
michael@0:
michael@0: // Invalidate auth states if redirecting to another host
michael@0: if (!route.getTargetHost().equals(newTarget)) {
michael@0: targetAuthState.invalidate();
michael@0: AuthScheme authScheme = proxyAuthState.getAuthScheme();
michael@0: if (authScheme != null && authScheme.isConnectionBased()) {
michael@0: proxyAuthState.invalidate();
michael@0: }
michael@0: }
michael@0:
michael@0: RequestWrapper wrapper = wrapRequest(redirect);
michael@0: wrapper.setParams(params);
michael@0:
michael@0: HttpRoute newRoute = determineRoute(newTarget, wrapper, context);
michael@0: RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute);
michael@0:
michael@0: if (this.log.isDebugEnabled()) {
michael@0: this.log.debug("Redirecting to '" + uri + "' via " + newRoute);
michael@0: }
michael@0:
michael@0: return newRequest;
michael@0: }
michael@0:
michael@0: CredentialsProvider credsProvider = (CredentialsProvider)
michael@0: context.getAttribute(ClientContext.CREDS_PROVIDER);
michael@0:
michael@0: if (credsProvider != null && HttpClientParams.isAuthenticating(params)) {
michael@0:
michael@0: if (this.targetAuthHandler.isAuthenticationRequested(response, context)) {
michael@0:
michael@0: HttpHost target = (HttpHost)
michael@0: context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
michael@0: if (target == null) {
michael@0: target = route.getTargetHost();
michael@0: }
michael@0:
michael@0: this.log.debug("Target requested authentication");
michael@0: Map challenges = this.targetAuthHandler.getChallenges(
michael@0: response, context);
michael@0: try {
michael@0: processChallenges(challenges,
michael@0: this.targetAuthState, this.targetAuthHandler,
michael@0: response, context);
michael@0: } catch (AuthenticationException ex) {
michael@0: if (this.log.isWarnEnabled()) {
michael@0: this.log.warn("Authentication error: " + ex.getMessage());
michael@0: return null;
michael@0: }
michael@0: }
michael@0: updateAuthState(this.targetAuthState, target, credsProvider);
michael@0:
michael@0: if (this.targetAuthState.getCredentials() != null) {
michael@0: // Re-try the same request via the same route
michael@0: return roureq;
michael@0: } else {
michael@0: return null;
michael@0: }
michael@0: } else {
michael@0: // Reset target auth scope
michael@0: this.targetAuthState.setAuthScope(null);
michael@0: }
michael@0:
michael@0: if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) {
michael@0:
michael@0: HttpHost proxy = route.getProxyHost();
michael@0:
michael@0: this.log.debug("Proxy requested authentication");
michael@0: Map challenges = this.proxyAuthHandler.getChallenges(
michael@0: response, context);
michael@0: try {
michael@0: processChallenges(challenges,
michael@0: this.proxyAuthState, this.proxyAuthHandler,
michael@0: response, context);
michael@0: } catch (AuthenticationException ex) {
michael@0: if (this.log.isWarnEnabled()) {
michael@0: this.log.warn("Authentication error: " + ex.getMessage());
michael@0: return null;
michael@0: }
michael@0: }
michael@0: updateAuthState(this.proxyAuthState, proxy, credsProvider);
michael@0:
michael@0: if (this.proxyAuthState.getCredentials() != null) {
michael@0: // Re-try the same request via the same route
michael@0: return roureq;
michael@0: } else {
michael@0: return null;
michael@0: }
michael@0: } else {
michael@0: // Reset proxy auth scope
michael@0: this.proxyAuthState.setAuthScope(null);
michael@0: }
michael@0: }
michael@0: return null;
michael@0: } // handleResponse
michael@0:
michael@0:
michael@0: /**
michael@0: * Shuts down the connection.
michael@0: * This method is called from a catch
block in
michael@0: * {@link #execute execute} during exception handling.
michael@0: */
michael@0: private void abortConnection() {
michael@0: ManagedClientConnection mcc = managedConn;
michael@0: if (mcc != null) {
michael@0: // we got here as the result of an exception
michael@0: // no response will be returned, release the connection
michael@0: managedConn = null;
michael@0: try {
michael@0: mcc.abortConnection();
michael@0: } catch (IOException ex) {
michael@0: if (this.log.isDebugEnabled()) {
michael@0: this.log.debug(ex.getMessage(), ex);
michael@0: }
michael@0: }
michael@0: // ensure the connection manager properly releases this connection
michael@0: try {
michael@0: mcc.releaseConnection();
michael@0: } catch(IOException ignored) {
michael@0: this.log.debug("Error releasing connection", ignored);
michael@0: }
michael@0: }
michael@0: } // abortConnection
michael@0:
michael@0:
michael@0: private void processChallenges(
michael@0: final Map challenges,
michael@0: final AuthState authState,
michael@0: final AuthenticationHandler authHandler,
michael@0: final HttpResponse response,
michael@0: final HttpContext context)
michael@0: throws MalformedChallengeException, AuthenticationException {
michael@0:
michael@0: AuthScheme authScheme = authState.getAuthScheme();
michael@0: if (authScheme == null) {
michael@0: // Authentication not attempted before
michael@0: authScheme = authHandler.selectScheme(challenges, response, context);
michael@0: authState.setAuthScheme(authScheme);
michael@0: }
michael@0: String id = authScheme.getSchemeName();
michael@0:
michael@0: Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH));
michael@0: if (challenge == null) {
michael@0: throw new AuthenticationException(id +
michael@0: " authorization challenge expected, but not found");
michael@0: }
michael@0: authScheme.processChallenge(challenge);
michael@0: this.log.debug("Authorization challenge processed");
michael@0: }
michael@0:
michael@0:
michael@0: private void updateAuthState(
michael@0: final AuthState authState,
michael@0: final HttpHost host,
michael@0: final CredentialsProvider credsProvider) {
michael@0:
michael@0: if (!authState.isValid()) {
michael@0: return;
michael@0: }
michael@0:
michael@0: String hostname = host.getHostName();
michael@0: int port = host.getPort();
michael@0: if (port < 0) {
michael@0: Scheme scheme = connManager.getSchemeRegistry().getScheme(host);
michael@0: port = scheme.getDefaultPort();
michael@0: }
michael@0:
michael@0: AuthScheme authScheme = authState.getAuthScheme();
michael@0: AuthScope authScope = new AuthScope(
michael@0: hostname,
michael@0: port,
michael@0: authScheme.getRealm(),
michael@0: authScheme.getSchemeName());
michael@0:
michael@0: if (this.log.isDebugEnabled()) {
michael@0: this.log.debug("Authentication scope: " + authScope);
michael@0: }
michael@0: Credentials creds = authState.getCredentials();
michael@0: if (creds == null) {
michael@0: creds = credsProvider.getCredentials(authScope);
michael@0: if (this.log.isDebugEnabled()) {
michael@0: if (creds != null) {
michael@0: this.log.debug("Found credentials");
michael@0: } else {
michael@0: this.log.debug("Credentials not found");
michael@0: }
michael@0: }
michael@0: } else {
michael@0: if (authScheme.isComplete()) {
michael@0: this.log.debug("Authentication failed");
michael@0: creds = null;
michael@0: }
michael@0: }
michael@0: authState.setAuthScope(authScope);
michael@0: authState.setCredentials(creds);
michael@0: }
michael@0:
michael@0: private void invalidateAuthIfSuccessful(final AuthState authState) {
michael@0: AuthScheme authscheme = authState.getAuthScheme();
michael@0: if (authscheme != null
michael@0: && authscheme.isConnectionBased()
michael@0: && authscheme.isComplete()
michael@0: && authState.getCredentials() != null) {
michael@0: authState.invalidate();
michael@0: }
michael@0: }
michael@0:
michael@0: } // class DefaultClientRequestDirector