summaryrefslogtreecommitdiffstats
path: root/src/org/apache/http/impl/client/DefaultRequestDirector.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/apache/http/impl/client/DefaultRequestDirector.java')
-rw-r--r--src/org/apache/http/impl/client/DefaultRequestDirector.java1088
1 files changed, 1088 insertions, 0 deletions
diff --git a/src/org/apache/http/impl/client/DefaultRequestDirector.java b/src/org/apache/http/impl/client/DefaultRequestDirector.java
new file mode 100644
index 0000000..511f8a0
--- /dev/null
+++ b/src/org/apache/http/impl/client/DefaultRequestDirector.java
@@ -0,0 +1,1088 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultRequestDirector.java $
+ * $Revision: 676023 $
+ * $Date: 2008-07-11 09:40:56 -0700 (Fri, 11 Jul 2008) $
+ *
+ * ====================================================================
+ * 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 org.apache.http.impl.client;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.ConnectionReuseStrategy;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.ProtocolException;
+import org.apache.http.ProtocolVersion;
+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.RequestDirector;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.client.NonRepeatableRequestException;
+import org.apache.http.client.RedirectException;
+import org.apache.http.client.RedirectHandler;
+import org.apache.http.client.UserTokenHandler;
+import org.apache.http.client.methods.AbortableHttpRequest;
+import org.apache.http.client.methods.HttpGet;
+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.client.utils.URIUtils;
+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.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.HTTP;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpProcessor;
+import org.apache.http.protocol.HttpRequestExecutor;
+
+/**
+ * Default implementation of {@link RequestDirector}.
+ * <br/>
+ * This class replaces the <code>HttpMethodDirector</code> in HttpClient 3.
+ *
+ * @author <a href="mailto:rolandw at apache.org">Roland Weber</a>
+ * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
+ *
+ * <!-- empty lines to avoid svn diff problems -->
+ * @version $Revision: 676023 $
+ *
+ * @since 4.0
+ */
+public class DefaultRequestDirector implements RequestDirector {
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ /** The connection manager. */
+ protected final ClientConnectionManager connManager;
+
+ /** The route planner. */
+ protected final HttpRoutePlanner routePlanner;
+
+ /** The connection re-use strategy. */
+ protected final ConnectionReuseStrategy reuseStrategy;
+
+ /** The keep-alive duration strategy. */
+ protected final ConnectionKeepAliveStrategy keepAliveStrategy;
+
+ /** The request executor. */
+ protected final HttpRequestExecutor requestExec;
+
+ /** The HTTP protocol processor. */
+ protected final HttpProcessor httpProcessor;
+
+ /** The request retry handler. */
+ protected final HttpRequestRetryHandler retryHandler;
+
+ /** The redirect handler. */
+ protected final RedirectHandler redirectHandler;
+
+ /** The target authentication handler. */
+ private final AuthenticationHandler targetAuthHandler;
+
+ /** The proxy authentication handler. */
+ private final AuthenticationHandler proxyAuthHandler;
+
+ /** The user token handler. */
+ private final UserTokenHandler userTokenHandler;
+
+ /** The HTTP parameters. */
+ protected final HttpParams params;
+
+ /** The currently allocated connection. */
+ protected ManagedClientConnection managedConn;
+
+ private int redirectCount;
+
+ private int maxRedirects;
+
+ private final AuthState targetAuthState;
+
+ private final AuthState proxyAuthState;
+
+ public DefaultRequestDirector(
+ final HttpRequestExecutor requestExec,
+ final ClientConnectionManager conman,
+ final ConnectionReuseStrategy reustrat,
+ final ConnectionKeepAliveStrategy kastrat,
+ final HttpRoutePlanner rouplan,
+ final HttpProcessor httpProcessor,
+ final HttpRequestRetryHandler retryHandler,
+ final RedirectHandler redirectHandler,
+ final AuthenticationHandler targetAuthHandler,
+ 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 (retryHandler == null) {
+ throw new IllegalArgumentException
+ ("HTTP request retry handler may not be null.");
+ }
+ if (redirectHandler == null) {
+ throw new IllegalArgumentException
+ ("Redirect handler may not be null.");
+ }
+ if (targetAuthHandler == null) {
+ throw new IllegalArgumentException
+ ("Target authentication handler 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;
+ this.connManager = conman;
+ this.reuseStrategy = reustrat;
+ this.keepAliveStrategy = kastrat;
+ this.routePlanner = rouplan;
+ this.httpProcessor = httpProcessor;
+ this.retryHandler = retryHandler;
+ this.redirectHandler = redirectHandler;
+ this.targetAuthHandler = targetAuthHandler;
+ this.proxyAuthHandler = proxyAuthHandler;
+ this.userTokenHandler = userTokenHandler;
+ this.params = params;
+
+ this.managedConn = null;
+
+ this.redirectCount = 0;
+ this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100);
+ this.targetAuthState = new AuthState();
+ this.proxyAuthState = new AuthState();
+ } // constructor
+
+
+ private RequestWrapper wrapRequest(
+ final HttpRequest request) throws ProtocolException {
+ if (request instanceof HttpEntityEnclosingRequest) {
+ return new EntityEnclosingRequestWrapper(
+ (HttpEntityEnclosingRequest) request);
+ } else {
+ return new RequestWrapper(
+ request);
+ }
+ }
+
+
+ protected void rewriteRequestURI(
+ final RequestWrapper request,
+ final HttpRoute route) throws ProtocolException {
+ try {
+
+ URI uri = request.getURI();
+ if (route.getProxyHost() != null && !route.isTunnelled()) {
+ // Make sure the request URI is absolute
+ if (!uri.isAbsolute()) {
+ HttpHost target = route.getTargetHost();
+ uri = URIUtils.rewriteURI(uri, target);
+ request.setURI(uri);
+ }
+ } else {
+ // Make sure the request URI is relative
+ if (uri.isAbsolute()) {
+ uri = URIUtils.rewriteURI(uri, null);
+ request.setURI(uri);
+ }
+ }
+
+ } catch (URISyntaxException ex) {
+ throw new ProtocolException("Invalid URI: " +
+ request.getRequestLine().getUri(), ex);
+ }
+ }
+
+
+ // non-javadoc, see interface ClientRequestDirector
+ public HttpResponse execute(HttpHost target, HttpRequest request,
+ HttpContext context)
+ throws HttpException, IOException {
+
+ HttpRequest orig = request;
+ RequestWrapper origWrapper = wrapRequest(orig);
+ origWrapper.setParams(params);
+ HttpRoute origRoute = determineRoute(target, origWrapper, context);
+
+ RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute);
+
+ long timeout = ConnManagerParams.getTimeout(params);
+
+ int execCount = 0;
+
+ boolean reuse = false;
+ HttpResponse response = null;
+ boolean done = false;
+ try {
+ while (!done) {
+ // In this loop, the RoutedRequest may be replaced by a
+ // followup request and route. The request and route passed
+ // in the method arguments will be replaced. The original
+ // request is still available in 'orig'.
+
+ RequestWrapper wrapper = roureq.getRequest();
+ HttpRoute route = roureq.getRoute();
+
+ // 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 (orig instanceof AbortableHttpRequest) {
+ ((AbortableHttpRequest) orig).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
+ this.log.debug("Stale connection check");
+ if (managedConn.isStale()) {
+ this.log.debug("Stale connection detected");
+ managedConn.close();
+ }
+ }
+ }
+
+ if (orig instanceof AbortableHttpRequest) {
+ ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn);
+ }
+
+ // Reopen connection if needed
+ if (!managedConn.isOpen()) {
+ managedConn.open(route, context, params);
+ }
+
+ try {
+ establishRoute(route, context);
+ } catch (TunnelRefusedException ex) {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug(ex.getMessage());
+ }
+ response = ex.getResponse();
+ break;
+ }
+
+ // Reset headers on the request wrapper
+ wrapper.resetHeaders();
+
+ // Re-write request URI if needed
+ rewriteRequestURI(wrapper, route);
+
+ // Use virtual host if set
+ target = (HttpHost) wrapper.getParams().getParameter(
+ ClientPNames.VIRTUAL_HOST);
+
+ 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(wrapper, httpProcessor, context);
+
+ context.setAttribute(ExecutionContext.HTTP_REQUEST,
+ wrapper);
+
+ boolean retrying = true;
+ while (retrying) {
+ // Increment total exec count (with redirects)
+ execCount++;
+ // Increment exec count for this particular request
+ wrapper.incrementExecCount();
+ if (wrapper.getExecCount() > 1 && !wrapper.isRepeatable()) {
+ throw new NonRepeatableRequestException("Cannot retry request " +
+ "with a non-repeatable request entity");
+ }
+
+ try {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Attempt " + execCount + " to execute request");
+ }
+ response = requestExec.execute(wrapper, managedConn, context);
+ retrying = false;
+
+ } catch (IOException ex) {
+ this.log.debug("Closing the connection.");
+ managedConn.close();
+ if (retryHandler.retryRequest(ex, execCount, context)) {
+ if (this.log.isInfoEnabled()) {
+ this.log.info("I/O exception ("+ ex.getClass().getName() +
+ ") caught when processing request: "
+ + ex.getMessage());
+ }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug(ex.getMessage(), ex);
+ }
+ this.log.info("Retrying request");
+ } else {
+ throw ex;
+ }
+
+ // If we have a direct route to the target host
+ // just re-open connection and re-try the request
+ if (route.getHopCount() == 1) {
+ this.log.debug("Reopening the direct connection.");
+ managedConn.open(route, context, params);
+ } else {
+ // otherwise give up
+ retrying = false;
+ }
+
+ }
+
+ }
+
+ // 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.
+ 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);
+ }
+
+ RoutedRequest followup = handleResponse(roureq, response, context);
+ if (followup == null) {
+ done = true;
+ } else {
+ if (reuse) {
+ this.log.debug("Connection kept alive");
+ // Make sure the response body is fully consumed, if present
+ HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ entity.consumeContent();
+ }
+ // entity consumed above is not an auto-release entity,
+ // need to mark the connection re-usable explicitly
+ managedConn.markReusable();
+ } else {
+ managedConn.close();
+ }
+ // check if we can use the same connection for the followup
+ if (!followup.getRoute().equals(roureq.getRoute())) {
+ releaseConnection();
+ }
+ roureq = followup;
+ }
+
+ userToken = this.userTokenHandler.getUserToken(context);
+ context.setAttribute(ClientContext.USER_TOKEN, userToken);
+ if (managedConn != null) {
+ managedConn.setState(userToken);
+ }
+ } // while not done
+
+
+ // check for entity, release connection if possible
+ if ((response == null) || (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
+
+ /**
+ * Returns the connection back to the connection manager
+ * and prepares for retrieving a new connection during
+ * the next request.
+ */
+ protected 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) {
+ this.log.debug("IOException releasing connection", ignored);
+ }
+ managedConn = null;
+ }
+
+ /**
+ * 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
+ */
+ protected HttpRoute determineRoute(HttpHost target,
+ HttpRequest request,
+ HttpContext context)
+ throws HttpException {
+
+ 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 this.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
+ */
+ protected void establishRoute(HttpRoute route, HttpContext context)
+ throws HttpException, IOException {
+
+ //@@@ how to handle CONNECT requests for tunnelling?
+ //@@@ refuse to send external CONNECT via director? special handling?
+
+ //@@@ should the request parameters already be used below?
+ //@@@ probably yes, but they're not linked yet
+ //@@@ will linking above cause problems with linking in reqExec?
+ //@@@ probably not, because the parent is replaced
+ //@@@ just make sure we don't link parameters to themselves
+
+ 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, this.params);
+ break;
+
+ case HttpRouteDirector.TUNNEL_TARGET: {
+ boolean secure = createTunnelToTarget(route, context);
+ this.log.debug("Tunnel to target created.");
+ managedConn.tunnelTarget(secure, this.params);
+ } break;
+
+ case HttpRouteDirector.TUNNEL_PROXY: {
+ // The most simple example for this case is a proxy chain
+ // of two proxies, where P1 must be tunnelled to P2.
+ // route: Source -> P1 -> P2 -> Target (3 hops)
+ // fact: Source -> P1 -> Target (2 hops)
+ final int hop = fact.getHopCount()-1; // the hop to establish
+ boolean secure = createTunnelToProxy(route, hop, context);
+ this.log.debug("Tunnel to proxy created.");
+ managedConn.tunnelProxy(route.getHopTarget(hop),
+ secure, this.params);
+ } break;
+
+
+ case HttpRouteDirector.LAYER_PROTOCOL:
+ managedConn.layerProtocol(context, this.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
+
+
+ /**
+ * 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
+ */
+ protected 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 (!this.managedConn.isOpen()) {
+ this.managedConn.open(route, context, this.params);
+ }
+
+ HttpRequest connect = createConnectRequest(route, context);
+
+ String agent = HttpProtocolParams.getUserAgent(params);
+ if (agent != null) {
+ connect.addHeader(HTTP.USER_AGENT, agent);
+ }
+ connect.addHeader(HTTP.TARGET_HOST, target.toHostString());
+
+ AuthScheme authScheme = this.proxyAuthState.getAuthScheme();
+ AuthScope authScope = this.proxyAuthState.getAuthScope();
+ Credentials creds = this.proxyAuthState.getCredentials();
+ if (creds != null) {
+ if (authScope != null || !authScheme.isConnectionBased()) {
+ try {
+ connect.addHeader(authScheme.authenticate(creds, connect));
+ } catch (AuthenticationException ex) {
+ if (this.log.isErrorEnabled()) {
+ this.log.error("Proxy authentication error: " + ex.getMessage());
+ }
+ }
+ }
+ }
+
+ response = requestExec.execute(connect, this.managedConn, 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 (this.proxyAuthHandler.isAuthenticationRequested(response, context)) {
+
+ this.log.debug("Proxy requested authentication");
+ Map<String, Header> challenges = this.proxyAuthHandler.getChallenges(
+ response, context);
+ try {
+ processChallenges(
+ challenges, this.proxyAuthState, this.proxyAuthHandler,
+ response, context);
+ } catch (AuthenticationException ex) {
+ if (this.log.isWarnEnabled()) {
+ this.log.warn("Authentication error: " + ex.getMessage());
+ break;
+ }
+ }
+ updateAuthState(this.proxyAuthState, proxy, credsProvider);
+
+ if (this.proxyAuthState.getCredentials() != null) {
+ done = false;
+
+ // Retry request
+ if (this.reuseStrategy.keepAlive(response, context)) {
+ this.log.debug("Connection kept alive");
+ // Consume response content
+ HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ entity.consumeContent();
+ }
+ } else {
+ this.managedConn.close();
+ }
+
+ }
+
+ } else {
+ // Reset proxy auth scope
+ this.proxyAuthState.setAuthScope(null);
+ }
+ }
+ }
+
+ int status = response.getStatusLine().getStatusCode();
+
+ if (status > 299) {
+
+ // Buffer response content
+ HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ response.setEntity(new BufferedHttpEntity(entity));
+ }
+
+ this.managedConn.close();
+ throw new TunnelRefusedException("CONNECT refused by proxy: " +
+ response.getStatusLine(), response);
+ }
+
+ this.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
+
+
+
+ /**
+ * Creates a tunnel to an intermediate proxy.
+ * This method is <i>not</i> implemented in this class.
+ * It just throws an exception here.
+ *
+ * @param route the route to establish
+ * @param hop the hop in the route to establish now.
+ * <code>route.getHopTarget(hop)</code>
+ * will return the proxy to tunnel to.
+ * @param context the context for request execution
+ *
+ * @return <code>true</code> if the partially tunnelled connection
+ * is secure, <code>false</code> otherwise.
+ *
+ * @throws HttpException in case of a problem
+ * @throws IOException in case of an IO problem
+ */
+ protected boolean createTunnelToProxy(HttpRoute route, int hop,
+ HttpContext context)
+ throws HttpException, IOException {
+
+ // Have a look at createTunnelToTarget and replicate the parts
+ // you need in a custom derived class. If your proxies don't require
+ // authentication, it is not too hard. But for the stock version of
+ // HttpClient, we cannot make such simplifying assumptions and would
+ // have to include proxy authentication code. The HttpComponents team
+ // is currently not in a position to support rarely used code of this
+ // complexity. Feel free to submit patches that refactor the code in
+ // createTunnelToTarget to facilitate re-use for proxy tunnelling.
+
+ throw new UnsupportedOperationException
+ ("Proxy chains are not supported.");
+ }
+
+
+
+ /**
+ * 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
+ */
+ protected HttpRequest createConnectRequest(HttpRoute route,
+ HttpContext context) {
+ // 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;
+ }
+
+
+ /**
+ * Analyzes a response to check need for a followup.
+ *
+ * @param roureq the request and route.
+ * @param response the response to analayze
+ * @param context the context used for the current request execution
+ *
+ * @return the followup request and route if there is a followup, or
+ * <code>null</code> if the response should be returned as is
+ *
+ * @throws HttpException in case of a problem
+ * @throws IOException in case of an IO problem
+ */
+ protected RoutedRequest handleResponse(RoutedRequest roureq,
+ HttpResponse response,
+ HttpContext context)
+ throws HttpException, IOException {
+
+ HttpRoute route = roureq.getRoute();
+ HttpHost proxy = route.getProxyHost();
+ RequestWrapper request = roureq.getRequest();
+
+ HttpParams params = request.getParams();
+ if (HttpClientParams.isRedirecting(params) &&
+ this.redirectHandler.isRedirectRequested(response, context)) {
+
+ if (redirectCount >= maxRedirects) {
+ throw new RedirectException("Maximum redirects ("
+ + maxRedirects + ") exceeded");
+ }
+ redirectCount++;
+
+ URI uri = this.redirectHandler.getLocationURI(response, context);
+
+ HttpHost newTarget = new HttpHost(
+ uri.getHost(),
+ uri.getPort(),
+ uri.getScheme());
+
+ HttpGet redirect = new HttpGet(uri);
+
+ HttpRequest orig = request.getOriginal();
+ redirect.setHeaders(orig.getAllHeaders());
+
+ RequestWrapper wrapper = new RequestWrapper(redirect);
+ wrapper.setParams(params);
+
+ HttpRoute newRoute = determineRoute(newTarget, wrapper, context);
+ RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute);
+
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Redirecting to '" + uri + "' via " + newRoute);
+ }
+
+ return newRequest;
+ }
+
+ CredentialsProvider credsProvider = (CredentialsProvider)
+ context.getAttribute(ClientContext.CREDS_PROVIDER);
+
+ if (credsProvider != null && HttpClientParams.isAuthenticating(params)) {
+
+ if (this.targetAuthHandler.isAuthenticationRequested(response, context)) {
+
+ HttpHost target = (HttpHost)
+ context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
+ if (target == null) {
+ target = route.getTargetHost();
+ }
+
+ this.log.debug("Target requested authentication");
+ Map<String, Header> challenges = this.targetAuthHandler.getChallenges(
+ response, context);
+ try {
+ processChallenges(challenges,
+ this.targetAuthState, this.targetAuthHandler,
+ response, context);
+ } catch (AuthenticationException ex) {
+ if (this.log.isWarnEnabled()) {
+ this.log.warn("Authentication error: " + ex.getMessage());
+ return null;
+ }
+ }
+ updateAuthState(this.targetAuthState, target, credsProvider);
+
+ if (this.targetAuthState.getCredentials() != null) {
+ // Re-try the same request via the same route
+ return roureq;
+ } else {
+ return null;
+ }
+ } else {
+ // Reset target auth scope
+ this.targetAuthState.setAuthScope(null);
+ }
+
+ if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) {
+
+ this.log.debug("Proxy requested authentication");
+ Map<String, Header> challenges = this.proxyAuthHandler.getChallenges(
+ response, context);
+ try {
+ processChallenges(challenges,
+ this.proxyAuthState, this.proxyAuthHandler,
+ response, context);
+ } catch (AuthenticationException ex) {
+ if (this.log.isWarnEnabled()) {
+ this.log.warn("Authentication error: " + ex.getMessage());
+ return null;
+ }
+ }
+ updateAuthState(this.proxyAuthState, proxy, credsProvider);
+
+ if (this.proxyAuthState.getCredentials() != null) {
+ // Re-try the same request via the same route
+ return roureq;
+ } else {
+ return null;
+ }
+ } else {
+ // Reset proxy auth scope
+ this.proxyAuthState.setAuthScope(null);
+ }
+ }
+ return null;
+ } // handleResponse
+
+
+ /**
+ * 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) {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug(ex.getMessage(), ex);
+ }
+ }
+ // ensure the connection manager properly releases this connection
+ try {
+ mcc.releaseConnection();
+ } catch(IOException ignored) {
+ this.log.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);
+ this.log.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());
+
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Authentication scope: " + authScope);
+ }
+ Credentials creds = authState.getCredentials();
+ if (creds == null) {
+ creds = credsProvider.getCredentials(authScope);
+ if (this.log.isDebugEnabled()) {
+ if (creds != null) {
+ this.log.debug("Found credentials");
+ } else {
+ this.log.debug("Credentials not found");
+ }
+ }
+ } else {
+ if (authScheme.isComplete()) {
+ this.log.debug("Authentication failed");
+ creds = null;
+ }
+ }
+ authState.setAuthScope(authScope);
+ authState.setCredentials(creds);
+ }
+
+} // class DefaultClientRequestDirector