diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | 417f3b92ba4549b2f22340e3107d869d2b9c5bb8 (patch) | |
tree | 2e08a2a91d6d14995df54490e3667f7943fbc6d6 /src/org/apache/http/impl/client | |
download | android_external_apache-http-417f3b92ba4549b2f22340e3107d869d2b9c5bb8.tar.gz android_external_apache-http-417f3b92ba4549b2f22340e3107d869d2b9c5bb8.tar.bz2 android_external_apache-http-417f3b92ba4549b2f22340e3107d869d2b9c5bb8.zip |
Initial Contribution
Diffstat (limited to 'src/org/apache/http/impl/client')
20 files changed, 4026 insertions, 0 deletions
diff --git a/src/org/apache/http/impl/client/AbstractAuthenticationHandler.java b/src/org/apache/http/impl/client/AbstractAuthenticationHandler.java new file mode 100644 index 0000000..57699d5 --- /dev/null +++ b/src/org/apache/http/impl/client/AbstractAuthenticationHandler.java @@ -0,0 +1,165 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractAuthenticationHandler.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 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.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.FormattedHeader; +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthSchemeRegistry; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.client.AuthenticationHandler; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.CharArrayBuffer; + +/** + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public abstract class AbstractAuthenticationHandler implements AuthenticationHandler { + + private final Log log = LogFactory.getLog(getClass()); + + private static final List<String> DEFAULT_SCHEME_PRIORITY = Arrays.asList(new String[] { + "ntlm", + "digest", + "basic" + }); + + public AbstractAuthenticationHandler() { + super(); + } + + protected Map<String, Header> parseChallenges( + final Header[] headers) throws MalformedChallengeException { + + Map<String, Header> map = new HashMap<String, Header>(headers.length); + for (Header header : headers) { + CharArrayBuffer buffer; + int pos; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + pos = ((FormattedHeader) header).getValuePos(); + } else { + String s = header.getValue(); + if (s == null) { + throw new MalformedChallengeException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + pos = 0; + } + while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + int beginIndex = pos; + while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + int endIndex = pos; + String s = buffer.substring(beginIndex, endIndex); + map.put(s.toLowerCase(Locale.ENGLISH), header); + } + return map; + } + + protected List<String> getAuthPreferences() { + return DEFAULT_SCHEME_PRIORITY; + } + + public AuthScheme selectScheme( + final Map<String, Header> challenges, + final HttpResponse response, + final HttpContext context) throws AuthenticationException { + + AuthSchemeRegistry registry = (AuthSchemeRegistry) context.getAttribute( + ClientContext.AUTHSCHEME_REGISTRY); + if (registry == null) { + throw new IllegalStateException("AuthScheme registry not set in HTTP context"); + } + + List<?> authPrefs = (List<?>) context.getAttribute( + ClientContext.AUTH_SCHEME_PREF); + if (authPrefs == null) { + authPrefs = getAuthPreferences(); + } + + if (this.log.isDebugEnabled()) { + this.log.debug("Authentication schemes in the order of preference: " + + authPrefs); + } + + AuthScheme authScheme = null; + for (int i = 0; i < authPrefs.size(); i++) { + String id = (String) authPrefs.get(i); + Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); + + if (challenge != null) { + if (this.log.isDebugEnabled()) { + this.log.debug(id + " authentication scheme selected"); + } + try { + authScheme = registry.getAuthScheme(id, response.getParams()); + break; + } catch (IllegalStateException e) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication scheme " + id + " not supported"); + // Try again + } + } + } else { + if (this.log.isDebugEnabled()) { + this.log.debug("Challenge for " + id + " authentication scheme not available"); + // Try again + } + } + } + if (authScheme == null) { + // If none selected, something is wrong + throw new AuthenticationException( + "Unable to respond to any of these challenges: " + + challenges); + } + return authScheme; + } + +} diff --git a/src/org/apache/http/impl/client/AbstractHttpClient.java b/src/org/apache/http/impl/client/AbstractHttpClient.java new file mode 100644 index 0000000..3a1b838 --- /dev/null +++ b/src/org/apache/http/impl/client/AbstractHttpClient.java @@ -0,0 +1,697 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java $ + * $Revision: 677250 $ + * $Date: 2008-07-16 04:45:47 -0700 (Wed, 16 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.net.URI; +import java.lang.reflect.UndeclaredThrowableException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.HttpEntity; +import org.apache.http.auth.AuthSchemeRegistry; +import org.apache.http.client.AuthenticationHandler; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.RequestDirector; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.CookieStore; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.RedirectHandler; +import org.apache.http.client.UserTokenHandler; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.cookie.CookieSpecRegistry; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.BasicHttpProcessor; +import org.apache.http.protocol.DefaultedHttpContext; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpProcessor; +import org.apache.http.protocol.HttpRequestExecutor; + +/** + * Convenience base class for HTTP client implementations. + * + * @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: 677250 $ + * + * @since 4.0 + */ +public abstract class AbstractHttpClient implements HttpClient { + + private final Log log = LogFactory.getLog(getClass()); + + /** The parameters. */ + private HttpParams defaultParams; + + /** The request executor. */ + private HttpRequestExecutor requestExec; + + /** The connection manager. */ + private ClientConnectionManager connManager; + + /** The connection re-use strategy. */ + private ConnectionReuseStrategy reuseStrategy; + + /** The connection keep-alive strategy. */ + private ConnectionKeepAliveStrategy keepAliveStrategy; + + /** The cookie spec registry. */ + private CookieSpecRegistry supportedCookieSpecs; + + /** The authentication scheme registry. */ + private AuthSchemeRegistry supportedAuthSchemes; + + /** The HTTP processor. */ + private BasicHttpProcessor httpProcessor; + + /** The request retry handler. */ + private HttpRequestRetryHandler retryHandler; + + /** The redirect handler. */ + private RedirectHandler redirectHandler; + + /** The target authentication handler. */ + private AuthenticationHandler targetAuthHandler; + + /** The proxy authentication handler. */ + private AuthenticationHandler proxyAuthHandler; + + /** The cookie store. */ + private CookieStore cookieStore; + + /** The credentials provider. */ + private CredentialsProvider credsProvider; + + /** The route planner. */ + private HttpRoutePlanner routePlanner; + + /** The user token handler. */ + private UserTokenHandler userTokenHandler; + + + /** + * Creates a new HTTP client. + * + * @param conman the connection manager + * @param params the parameters + */ + protected AbstractHttpClient( + final ClientConnectionManager conman, + final HttpParams params) { + defaultParams = params; + connManager = conman; + } // constructor + + protected abstract HttpParams createHttpParams(); + + + protected abstract HttpContext createHttpContext(); + + + protected abstract HttpRequestExecutor createRequestExecutor(); + + + protected abstract ClientConnectionManager createClientConnectionManager(); + + + protected abstract AuthSchemeRegistry createAuthSchemeRegistry(); + + + protected abstract CookieSpecRegistry createCookieSpecRegistry(); + + + protected abstract ConnectionReuseStrategy createConnectionReuseStrategy(); + + + protected abstract ConnectionKeepAliveStrategy createConnectionKeepAliveStrategy(); + + + protected abstract BasicHttpProcessor createHttpProcessor(); + + + protected abstract HttpRequestRetryHandler createHttpRequestRetryHandler(); + + + protected abstract RedirectHandler createRedirectHandler(); + + + protected abstract AuthenticationHandler createTargetAuthenticationHandler(); + + + protected abstract AuthenticationHandler createProxyAuthenticationHandler(); + + + protected abstract CookieStore createCookieStore(); + + + protected abstract CredentialsProvider createCredentialsProvider(); + + + protected abstract HttpRoutePlanner createHttpRoutePlanner(); + + + protected abstract UserTokenHandler createUserTokenHandler(); + + + // non-javadoc, see interface HttpClient + public synchronized final HttpParams getParams() { + if (defaultParams == null) { + defaultParams = createHttpParams(); + } + return defaultParams; + } + + + /** + * Replaces the parameters. + * The implementation here does not update parameters of dependent objects. + * + * @param params the new default parameters + */ + public synchronized void setParams(HttpParams params) { + defaultParams = params; + } + + + public synchronized final ClientConnectionManager getConnectionManager() { + if (connManager == null) { + connManager = createClientConnectionManager(); + } + return connManager; + } + + + public synchronized final HttpRequestExecutor getRequestExecutor() { + if (requestExec == null) { + requestExec = createRequestExecutor(); + } + return requestExec; + } + + + public synchronized final AuthSchemeRegistry getAuthSchemes() { + if (supportedAuthSchemes == null) { + supportedAuthSchemes = createAuthSchemeRegistry(); + } + return supportedAuthSchemes; + } + + + public synchronized void setAuthSchemes(final AuthSchemeRegistry authSchemeRegistry) { + supportedAuthSchemes = authSchemeRegistry; + } + + + public synchronized final CookieSpecRegistry getCookieSpecs() { + if (supportedCookieSpecs == null) { + supportedCookieSpecs = createCookieSpecRegistry(); + } + return supportedCookieSpecs; + } + + + public synchronized void setCookieSpecs(final CookieSpecRegistry cookieSpecRegistry) { + supportedCookieSpecs = cookieSpecRegistry; + } + + + public synchronized final ConnectionReuseStrategy getConnectionReuseStrategy() { + if (reuseStrategy == null) { + reuseStrategy = createConnectionReuseStrategy(); + } + return reuseStrategy; + } + + + public synchronized void setReuseStrategy(final ConnectionReuseStrategy reuseStrategy) { + this.reuseStrategy = reuseStrategy; + } + + + public synchronized final ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() { + if (keepAliveStrategy == null) { + keepAliveStrategy = createConnectionKeepAliveStrategy(); + } + return keepAliveStrategy; + } + + + public synchronized void setKeepAliveStrategy(final ConnectionKeepAliveStrategy keepAliveStrategy) { + this.keepAliveStrategy = keepAliveStrategy; + } + + + public synchronized final HttpRequestRetryHandler getHttpRequestRetryHandler() { + if (retryHandler == null) { + retryHandler = createHttpRequestRetryHandler(); + } + return retryHandler; + } + + + public synchronized void setHttpRequestRetryHandler(final HttpRequestRetryHandler retryHandler) { + this.retryHandler = retryHandler; + } + + + public synchronized final RedirectHandler getRedirectHandler() { + if (redirectHandler == null) { + redirectHandler = createRedirectHandler(); + } + return redirectHandler; + } + + + public synchronized void setRedirectHandler(final RedirectHandler redirectHandler) { + this.redirectHandler = redirectHandler; + } + + + public synchronized final AuthenticationHandler getTargetAuthenticationHandler() { + if (targetAuthHandler == null) { + targetAuthHandler = createTargetAuthenticationHandler(); + } + return targetAuthHandler; + } + + + public synchronized void setTargetAuthenticationHandler( + final AuthenticationHandler targetAuthHandler) { + this.targetAuthHandler = targetAuthHandler; + } + + + public synchronized final AuthenticationHandler getProxyAuthenticationHandler() { + if (proxyAuthHandler == null) { + proxyAuthHandler = createProxyAuthenticationHandler(); + } + return proxyAuthHandler; + } + + + public synchronized void setProxyAuthenticationHandler( + final AuthenticationHandler proxyAuthHandler) { + this.proxyAuthHandler = proxyAuthHandler; + } + + + public synchronized final CookieStore getCookieStore() { + if (cookieStore == null) { + cookieStore = createCookieStore(); + } + return cookieStore; + } + + + public synchronized void setCookieStore(final CookieStore cookieStore) { + this.cookieStore = cookieStore; + } + + + public synchronized final CredentialsProvider getCredentialsProvider() { + if (credsProvider == null) { + credsProvider = createCredentialsProvider(); + } + return credsProvider; + } + + + public synchronized void setCredentialsProvider(final CredentialsProvider credsProvider) { + this.credsProvider = credsProvider; + } + + + public synchronized final HttpRoutePlanner getRoutePlanner() { + if (this.routePlanner == null) { + this.routePlanner = createHttpRoutePlanner(); + } + return this.routePlanner; + } + + + public synchronized void setRoutePlanner(final HttpRoutePlanner routePlanner) { + this.routePlanner = routePlanner; + } + + + public synchronized final UserTokenHandler getUserTokenHandler() { + if (this.userTokenHandler == null) { + this.userTokenHandler = createUserTokenHandler(); + } + return this.userTokenHandler; + } + + + public synchronized void setUserTokenHandler(final UserTokenHandler userTokenHandler) { + this.userTokenHandler = userTokenHandler; + } + + + protected synchronized final BasicHttpProcessor getHttpProcessor() { + if (httpProcessor == null) { + httpProcessor = createHttpProcessor(); + } + return httpProcessor; + } + + + public synchronized void addResponseInterceptor(final HttpResponseInterceptor itcp) { + getHttpProcessor().addInterceptor(itcp); + } + + + public synchronized void addResponseInterceptor(final HttpResponseInterceptor itcp, int index) { + getHttpProcessor().addInterceptor(itcp, index); + } + + + public synchronized HttpResponseInterceptor getResponseInterceptor(int index) { + return getHttpProcessor().getResponseInterceptor(index); + } + + + public synchronized int getResponseInterceptorCount() { + return getHttpProcessor().getResponseInterceptorCount(); + } + + + public synchronized void clearResponseInterceptors() { + getHttpProcessor().clearResponseInterceptors(); + } + + + public void removeResponseInterceptorByClass(Class<? extends HttpResponseInterceptor> clazz) { + getHttpProcessor().removeResponseInterceptorByClass(clazz); + } + + + public synchronized void addRequestInterceptor(final HttpRequestInterceptor itcp) { + getHttpProcessor().addInterceptor(itcp); + } + + + public synchronized void addRequestInterceptor(final HttpRequestInterceptor itcp, int index) { + getHttpProcessor().addInterceptor(itcp, index); + } + + + public synchronized HttpRequestInterceptor getRequestInterceptor(int index) { + return getHttpProcessor().getRequestInterceptor(index); + } + + + public synchronized int getRequestInterceptorCount() { + return getHttpProcessor().getRequestInterceptorCount(); + } + + + public synchronized void clearRequestInterceptors() { + getHttpProcessor().clearRequestInterceptors(); + } + + + public void removeRequestInterceptorByClass(Class<? extends HttpRequestInterceptor> clazz) { + getHttpProcessor().removeRequestInterceptorByClass(clazz); + } + + + // non-javadoc, see interface HttpClient + public final HttpResponse execute(HttpUriRequest request) + throws IOException, ClientProtocolException { + + return execute(request, (HttpContext) null); + } + + + /** + * Maps to {@link HttpClient#execute(HttpHost,HttpRequest,HttpContext) + * execute(target, request, context)}. + * The target is determined from the URI of the request. + * + * @param request the request to execute + * @param context the request-specific execution context, + * or <code>null</code> to use a default context + */ + public final HttpResponse execute(HttpUriRequest request, + HttpContext context) + throws IOException, ClientProtocolException { + + if (request == null) { + throw new IllegalArgumentException + ("Request must not be null."); + } + + return execute(determineTarget(request), request, context); + } + + private HttpHost determineTarget(HttpUriRequest request) { + // A null target may be acceptable if there is a default target. + // Otherwise, the null target is detected in the director. + HttpHost target = null; + + URI requestURI = request.getURI(); + if (requestURI.isAbsolute()) { + target = new HttpHost( + requestURI.getHost(), + requestURI.getPort(), + requestURI.getScheme()); + } + return target; + } + + // non-javadoc, see interface HttpClient + public final HttpResponse execute(HttpHost target, HttpRequest request) + throws IOException, ClientProtocolException { + + return execute(target, request, (HttpContext) null); + } + + + // non-javadoc, see interface HttpClient + public final HttpResponse execute(HttpHost target, HttpRequest request, + HttpContext context) + throws IOException, ClientProtocolException { + + if (request == null) { + throw new IllegalArgumentException + ("Request must not be null."); + } + // a null target may be acceptable, this depends on the route planner + // a null context is acceptable, default context created below + + HttpContext execContext = null; + RequestDirector director = null; + + // Initialize the request execution context making copies of + // all shared objects that are potentially threading unsafe. + synchronized (this) { + + HttpContext defaultContext = createHttpContext(); + if (context == null) { + execContext = defaultContext; + } else { + execContext = new DefaultedHttpContext(context, defaultContext); + } + // Create a director for this request + director = createClientRequestDirector( + getRequestExecutor(), + getConnectionManager(), + getConnectionReuseStrategy(), + getConnectionKeepAliveStrategy(), + getRoutePlanner(), + getHttpProcessor().copy(), + getHttpRequestRetryHandler(), + getRedirectHandler(), + getTargetAuthenticationHandler(), + getProxyAuthenticationHandler(), + getUserTokenHandler(), + determineParams(request)); + } + + try { + return director.execute(target, request, execContext); + } catch(HttpException httpException) { + throw new ClientProtocolException(httpException); + } + } // execute + + + protected RequestDirector createClientRequestDirector( + 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 stateHandler, + final HttpParams params) { + return new DefaultRequestDirector( + requestExec, + conman, + reustrat, + kastrat, + rouplan, + httpProcessor, + retryHandler, + redirectHandler, + targetAuthHandler, + proxyAuthHandler, + stateHandler, + params); + } + + /** + * Obtains parameters for executing a request. + * The default implementation in this class creates a new + * {@link ClientParamsStack} from the request parameters + * and the client parameters. + * <br/> + * This method is called by the default implementation of + * {@link #execute(HttpHost,HttpRequest,HttpContext)} + * to obtain the parameters for the + * {@link DefaultRequestDirector}. + * + * @param req the request that will be executed + * + * @return the parameters to use + */ + protected HttpParams determineParams(HttpRequest req) { + return new ClientParamsStack + (null, getParams(), req.getParams(), null); + } + + + // non-javadoc, see interface HttpClient + public <T> T execute( + final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler) + throws IOException, ClientProtocolException { + return execute(request, responseHandler, null); + } + + + // non-javadoc, see interface HttpClient + public <T> T execute( + final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler, + final HttpContext context) + throws IOException, ClientProtocolException { + HttpHost target = determineTarget(request); + return execute(target, request, responseHandler, context); + } + + + // non-javadoc, see interface HttpClient + public <T> T execute( + final HttpHost target, + final HttpRequest request, + final ResponseHandler<? extends T> responseHandler) + throws IOException, ClientProtocolException { + return execute(target, request, responseHandler, null); + } + + + // non-javadoc, see interface HttpClient + public <T> T execute( + final HttpHost target, + final HttpRequest request, + final ResponseHandler<? extends T> responseHandler, + final HttpContext context) + throws IOException, ClientProtocolException { + if (responseHandler == null) { + throw new IllegalArgumentException + ("Response handler must not be null."); + } + + HttpResponse response = execute(target, request, context); + + T result; + try { + result = responseHandler.handleResponse(response); + } catch (Throwable t) { + HttpEntity entity = response.getEntity(); + if (entity != null) { + try { + entity.consumeContent(); + } catch (Throwable t2) { + // Log this exception. The original exception is more + // important and will be thrown to the caller. + this.log.warn("Error consuming content after an exception.", t2); + } + } + + if (t instanceof Error) { + throw (Error) t; + } + + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + + if (t instanceof IOException) { + throw (IOException) t; + } + + throw new UndeclaredThrowableException(t); + } + + // Handling the response was successful. Ensure that the content has + // been fully consumed. + HttpEntity entity = response.getEntity(); + if (entity != null) { + // Let this exception go to the caller. + entity.consumeContent(); + } + + return result; + } + + +} // class AbstractHttpClient diff --git a/src/org/apache/http/impl/client/BasicCookieStore.java b/src/org/apache/http/impl/client/BasicCookieStore.java new file mode 100644 index 0000000..9970961 --- /dev/null +++ b/src/org/apache/http/impl/client/BasicCookieStore.java @@ -0,0 +1,162 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/BasicCookieStore.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 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.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.apache.http.client.CookieStore; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieIdentityComparator; + +/** + * Default implementation of {@link CookieStore} + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author Rodney Waldhoff + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author Sean C. Sullivan + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:adrian@intencha.com">Adrian Sutton</a> + * + * @since 4.0 + */ +public class BasicCookieStore implements CookieStore { + + private final ArrayList<Cookie> cookies; + + private final Comparator<Cookie> cookieComparator; + + // -------------------------------------------------------- Class Variables + + /** + * Default constructor. + */ + public BasicCookieStore() { + super(); + this.cookies = new ArrayList<Cookie>(); + this.cookieComparator = new CookieIdentityComparator(); + } + + /** + * Adds an {@link Cookie HTTP cookie}, replacing any existing equivalent cookies. + * If the given cookie has already expired it will not be added, but existing + * values will still be removed. + * + * @param cookie the {@link Cookie cookie} to be added + * + * @see #addCookies(Cookie[]) + * + */ + public synchronized void addCookie(Cookie cookie) { + if (cookie != null) { + // first remove any old cookie that is equivalent + for (Iterator<Cookie> it = cookies.iterator(); it.hasNext();) { + if (cookieComparator.compare(cookie, it.next()) == 0) { + it.remove(); + break; + } + } + if (!cookie.isExpired(new Date())) { + cookies.add(cookie); + } + } + } + + /** + * Adds an array of {@link Cookie HTTP cookies}. Cookies are added individually and + * in the given array order. If any of the given cookies has already expired it will + * not be added, but existing values will still be removed. + * + * @param cookies the {@link Cookie cookies} to be added + * + * @see #addCookie(Cookie) + * + */ + public synchronized void addCookies(Cookie[] cookies) { + if (cookies != null) { + for (Cookie cooky : cookies) { + this.addCookie(cooky); + } + } + } + + /** + * Returns an immutable array of {@link Cookie cookies} that this HTTP + * state currently contains. + * + * @return an array of {@link Cookie cookies}. + */ + public synchronized List<Cookie> getCookies() { + return Collections.unmodifiableList(this.cookies); + } + + /** + * Removes all of {@link Cookie cookies} in this HTTP state + * that have expired by the specified {@link java.util.Date date}. + * + * @return true if any cookies were purged. + * + * @see Cookie#isExpired(Date) + */ + public synchronized boolean clearExpired(final Date date) { + if (date == null) { + return false; + } + boolean removed = false; + for (Iterator<Cookie> it = cookies.iterator(); it.hasNext();) { + if (it.next().isExpired(date)) { + it.remove(); + removed = true; + } + } + return removed; + } + + @Override + public String toString() { + return cookies.toString(); + } + + /** + * Clears all cookies. + */ + public synchronized void clear() { + cookies.clear(); + } + +} diff --git a/src/org/apache/http/impl/client/BasicCredentialsProvider.java b/src/org/apache/http/impl/client/BasicCredentialsProvider.java new file mode 100644 index 0000000..02427ea --- /dev/null +++ b/src/org/apache/http/impl/client/BasicCredentialsProvider.java @@ -0,0 +1,143 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/BasicCredentialsProvider.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 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.util.HashMap; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.client.CredentialsProvider; + +/** + * Default implementation of {@link CredentialsProvider} + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author Rodney Waldhoff + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author Sean C. Sullivan + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:adrian@intencha.com">Adrian Sutton</a> + * + * @since 4.0 + */ +public class BasicCredentialsProvider implements CredentialsProvider { + + private final HashMap<AuthScope, Credentials> credMap; + + /** + * Default constructor. + */ + public BasicCredentialsProvider() { + super(); + this.credMap = new HashMap<AuthScope, Credentials>(); + } + + /** + * Sets the {@link Credentials credentials} for the given authentication + * scope. Any previous credentials for the given scope will be overwritten. + * + * @param authscope the {@link AuthScope authentication scope} + * @param credentials the authentication {@link Credentials credentials} + * for the given scope. + * + * @see #getCredentials(AuthScope) + */ + public synchronized void setCredentials( + final AuthScope authscope, + final Credentials credentials) { + if (authscope == null) { + throw new IllegalArgumentException("Authentication scope may not be null"); + } + credMap.put(authscope, credentials); + } + + /** + * Find matching {@link Credentials credentials} for the given authentication scope. + * + * @param map the credentials hash map + * @param authscope the {@link AuthScope authentication scope} + * @return the credentials + * + */ + private static Credentials matchCredentials( + final HashMap<AuthScope, Credentials> map, + final AuthScope authscope) { + // see if we get a direct hit + Credentials creds = map.get(authscope); + if (creds == null) { + // Nope. + // Do a full scan + int bestMatchFactor = -1; + AuthScope bestMatch = null; + for (AuthScope current: map.keySet()) { + int factor = authscope.match(current); + if (factor > bestMatchFactor) { + bestMatchFactor = factor; + bestMatch = current; + } + } + if (bestMatch != null) { + creds = map.get(bestMatch); + } + } + return creds; + } + + /** + * Get the {@link Credentials credentials} for the given authentication scope. + * + * @param authscope the {@link AuthScope authentication scope} + * @return the credentials + * + * @see #setCredentials(AuthScope, Credentials) + */ + public synchronized Credentials getCredentials(final AuthScope authscope) { + if (authscope == null) { + throw new IllegalArgumentException("Authentication scope may not be null"); + } + return matchCredentials(this.credMap, authscope); + } + + @Override + public String toString() { + return credMap.toString(); + } + + /** + * Clears all credentials. + */ + public synchronized void clear() { + this.credMap.clear(); + } + +} diff --git a/src/org/apache/http/impl/client/BasicResponseHandler.java b/src/org/apache/http/impl/client/BasicResponseHandler.java new file mode 100644 index 0000000..f17d30d --- /dev/null +++ b/src/org/apache/http/impl/client/BasicResponseHandler.java @@ -0,0 +1,79 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/BasicResponseHandler.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 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 org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.HttpResponseException; +import org.apache.http.util.EntityUtils; + +/** + * A {@link ResponseHandler} that returns the response body as a String + * for successful (2xx) responses. If the response code was >= 300, the response + * body is consumed and an {@link HttpResponseException} is thrown. + * + * If this is used with + * {@link org.apache.http.client.HttpClient#execute( + * org.apache.http.client.methods.HttpUriRequest, ResponseHandler), + * HttpClient may handle redirects (3xx responses) internally. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 677240 $ + * + * @since 4.0 + */ +public class BasicResponseHandler implements ResponseHandler<String> { + + /** + * Returns the response body as a String if the response was successful (a + * 2xx status code). If no response body exists, this returns null. If the + * response was unsuccessful (>= 300 status code), throws an + * {@link HttpResponseException}. + */ + public String handleResponse(final HttpResponse response) + throws HttpResponseException, IOException { + StatusLine statusLine = response.getStatusLine(); + if (statusLine.getStatusCode() >= 300) { + throw new HttpResponseException(statusLine.getStatusCode(), + statusLine.getReasonPhrase()); + } + + HttpEntity entity = response.getEntity(); + return entity == null ? null : EntityUtils.toString(entity); + } + +} diff --git a/src/org/apache/http/impl/client/ClientParamsStack.java b/src/org/apache/http/impl/client/ClientParamsStack.java new file mode 100644 index 0000000..a017e5d --- /dev/null +++ b/src/org/apache/http/impl/client/ClientParamsStack.java @@ -0,0 +1,282 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/ClientParamsStack.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 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 org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.params.HttpParams; +import org.apache.http.params.AbstractHttpParams; + + +/** + * Represents a stack of parameter collections. + * When retrieving a parameter, the stack is searched in a fixed order + * and the first match returned. Setting parameters via the stack is + * not supported. To minimize overhead, the stack has a fixed size and + * does not maintain an internal array. + * The supported stack entries, sorted by increasing priority, are: + * <ol> + * <li>Application parameters: + * expected to be the same for all clients used by an application. + * These provide "global", that is application-wide, defaults. + * </li> + * <li>Client parameters: + * specific to an instance of + * {@link org.apache.http.client.HttpClient HttpClient}. + * These provide client specific defaults. + * </li> + * <li>Request parameters: + * specific to a single request execution. + * For overriding client and global defaults. + * </li> + * <li>Override parameters: + * specific to an instance of + * {@link org.apache.http.client.HttpClient HttpClient}. + * These can be used to set parameters that cannot be overridden + * on a per-request basis. + * </li> + * </ol> + * Each stack entry may be <code>null</code>. That is preferable over + * an empty params collection, since it avoids searching the empty collection + * when looking up parameters. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * @version $Revision: 673450 $ + */ +public class ClientParamsStack extends AbstractHttpParams { + + private final Log log = LogFactory.getLog(getClass()); + + /** The application parameter collection, or <code>null</code>. */ + protected final HttpParams applicationParams; + + /** The client parameter collection, or <code>null</code>. */ + protected final HttpParams clientParams; + + /** The request parameter collection, or <code>null</code>. */ + protected final HttpParams requestParams; + + /** The override parameter collection, or <code>null</code>. */ + protected final HttpParams overrideParams; + + + /** + * Creates a new parameter stack from elements. + * The arguments will be stored as-is, there is no copying to + * prevent modification. + * + * @param aparams application parameters, or <code>null</code> + * @param cparams client parameters, or <code>null</code> + * @param rparams request parameters, or <code>null</code> + * @param oparams override parameters, or <code>null</code> + */ + public ClientParamsStack(HttpParams aparams, HttpParams cparams, + HttpParams rparams, HttpParams oparams) { + applicationParams = aparams; + clientParams = cparams; + requestParams = rparams; + overrideParams = oparams; + } + + + /** + * Creates a copy of a parameter stack. + * The new stack will have the exact same entries as the argument stack. + * There is no copying of parameters. + * + * @param stack the stack to copy + */ + public ClientParamsStack(ClientParamsStack stack) { + this(stack.getApplicationParams(), + stack.getClientParams(), + stack.getRequestParams(), + stack.getOverrideParams()); + } + + + /** + * Creates a modified copy of a parameter stack. + * The new stack will contain the explicitly passed elements. + * For elements where the explicit argument is <code>null</code>, + * the corresponding element from the argument stack is used. + * There is no copying of parameters. + * + * @param stack the stack to modify + * @param aparams application parameters, or <code>null</code> + * @param cparams client parameters, or <code>null</code> + * @param rparams request parameters, or <code>null</code> + * @param oparams override parameters, or <code>null</code> + */ + public ClientParamsStack(ClientParamsStack stack, + HttpParams aparams, HttpParams cparams, + HttpParams rparams, HttpParams oparams) { + this((aparams != null) ? aparams : stack.getApplicationParams(), + (cparams != null) ? cparams : stack.getClientParams(), + (rparams != null) ? rparams : stack.getRequestParams(), + (oparams != null) ? oparams : stack.getOverrideParams()); + } + + + /** + * Obtains the application parameters of this stack. + * + * @return the application parameters, or <code>null</code> + */ + public final HttpParams getApplicationParams() { + return applicationParams; + } + + /** + * Obtains the client parameters of this stack. + * + * @return the client parameters, or <code>null</code> + */ + public final HttpParams getClientParams() { + return clientParams; + } + + /** + * Obtains the request parameters of this stack. + * + * @return the request parameters, or <code>null</code> + */ + public final HttpParams getRequestParams() { + return requestParams; + } + + /** + * Obtains the override parameters of this stack. + * + * @return the override parameters, or <code>null</code> + */ + public final HttpParams getOverrideParams() { + return overrideParams; + } + + + /** + * Obtains a parameter from this stack. + * See class comment for search order. + * + * @param name the name of the parameter to obtain + * + * @return the highest-priority value for that parameter, or + * <code>null</code> if it is not set anywhere in this stack + */ + public Object getParameter(String name) { + if (name == null) { + throw new IllegalArgumentException + ("Parameter name must not be null."); + } + + Object result = null; + + if (overrideParams != null) { + result = overrideParams.getParameter(name); + } + if ((result == null) && (requestParams != null)) { + result = requestParams.getParameter(name); + } + if ((result == null) && (clientParams != null)) { + result = clientParams.getParameter(name); + } + if ((result == null) && (applicationParams != null)) { + result = applicationParams.getParameter(name); + } + if (this.log.isDebugEnabled()) { + this.log.debug("'" + name + "': " + result); + } + + return result; + } + + /** + * Does <i>not</i> set a parameter. + * Parameter stacks are read-only. It is possible, though discouraged, + * to access and modify specific stack entries. + * Derived classes may change this behavior. + * + * @param name ignored + * @param value ignored + * + * @return nothing + * + * @throws UnsupportedOperationException always + */ + public HttpParams setParameter(String name, Object value) + throws UnsupportedOperationException { + + throw new UnsupportedOperationException + ("Setting parameters in a stack is not supported."); + } + + + /** + * Does <i>not</i> remove a parameter. + * Parameter stacks are read-only. It is possible, though discouraged, + * to access and modify specific stack entries. + * Derived classes may change this behavior. + * + * @param name ignored + * + * @return nothing + * + * @throws UnsupportedOperationException always + */ + public boolean removeParameter(String name) { + throw new UnsupportedOperationException + ("Removing parameters in a stack is not supported."); + } + + + /** + * Does <i>not</i> copy parameters. + * Parameter stacks are lightweight objects, expected to be instantiated + * as needed and to be used only in a very specific context. On top of + * that, they are read-only. The typical copy operation to prevent + * accidental modification of parameters passed by the application to + * a framework object is therefore pointless and disabled. + * Create a new stack if you really need a copy. + * <br/> + * Derived classes may change this behavior. + * + * @return <code>this</code> parameter stack + */ + public HttpParams copy() { + return this; + } + + +} diff --git a/src/org/apache/http/impl/client/DefaultConnectionKeepAliveStrategy.java b/src/org/apache/http/impl/client/DefaultConnectionKeepAliveStrategy.java new file mode 100644 index 0000000..c7641d2 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultConnectionKeepAliveStrategy.java @@ -0,0 +1,76 @@ +/* + * $HeadURL: $ + * $Revision: $ + * $Date: $ + * + * ==================================================================== + * 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 org.apache.http.HeaderElement; +import org.apache.http.HeaderElementIterator; +import org.apache.http.HttpResponse; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.message.BasicHeaderElementIterator; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; + +/** + * Default implementation of a strategy deciding duration + * that a connection can remain idle. + * + * The default implementation looks solely at the 'Keep-Alive' + * header's timeout token. + * + * @author <a href="mailto:sberlin at gmail.com">Sam Berlin</a> + * + * @version $Revision: $ + * + * @since 4.0 + */ +public class DefaultConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy { + + public long getKeepAliveDuration(HttpResponse response, HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + HeaderElementIterator it = new BasicHeaderElementIterator( + response.headerIterator(HTTP.CONN_KEEP_ALIVE)); + while (it.hasNext()) { + HeaderElement he = it.nextElement(); + String param = he.getName(); + String value = he.getValue(); + if (value != null && param.equalsIgnoreCase("timeout")) { + try { + return Long.parseLong(value) * 1000; + } catch(NumberFormatException ignore) { + } + } + } + return -1; + } + +} diff --git a/src/org/apache/http/impl/client/DefaultHttpClient.java b/src/org/apache/http/impl/client/DefaultHttpClient.java new file mode 100644 index 0000000..f0b694e --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultHttpClient.java @@ -0,0 +1,332 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultHttpClient.java $ + * $Revision: 677250 $ + * $Date: 2008-07-16 04:45:47 -0700 (Wed, 16 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 org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpVersion; +import org.apache.http.auth.AuthSchemeRegistry; +import org.apache.http.client.AuthenticationHandler; +import org.apache.http.client.CookieStore; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.RedirectHandler; +import org.apache.http.client.UserTokenHandler; +import org.apache.http.client.params.AuthPolicy; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.client.params.CookiePolicy; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.client.protocol.RequestAddCookies; +import org.apache.http.client.protocol.RequestDefaultHeaders; +import org.apache.http.client.protocol.RequestProxyAuthentication; +import org.apache.http.client.protocol.RequestTargetAuthentication; +import org.apache.http.client.protocol.ResponseProcessCookies; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionManagerFactory; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.cookie.CookieSpecRegistry; +import org.apache.http.impl.DefaultConnectionReuseStrategy; +import org.apache.http.impl.auth.BasicSchemeFactory; +import org.apache.http.impl.auth.DigestSchemeFactory; +import org.apache.http.impl.conn.DefaultHttpRoutePlanner; +import org.apache.http.impl.conn.SingleClientConnManager; +import org.apache.http.impl.cookie.BestMatchSpecFactory; +import org.apache.http.impl.cookie.BrowserCompatSpecFactory; +import org.apache.http.impl.cookie.NetscapeDraftSpecFactory; +import org.apache.http.impl.cookie.RFC2109SpecFactory; +import org.apache.http.impl.cookie.RFC2965SpecFactory; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.BasicHttpProcessor; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpRequestExecutor; +import org.apache.http.protocol.RequestConnControl; +import org.apache.http.protocol.RequestContent; +import org.apache.http.protocol.RequestExpectContinue; +import org.apache.http.protocol.RequestTargetHost; +import org.apache.http.protocol.RequestUserAgent; +import org.apache.http.util.VersionInfo; + + + +/** + * Default implementation of an HTTP client. + * <br/> + * This class replaces <code>HttpClient</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: 677250 $ + * + * @since 4.0 + */ +public class DefaultHttpClient extends AbstractHttpClient { + + + /** + * Creates a new HTTP client from parameters and a connection manager. + * + * @param params the parameters + * @param conman the connection manager + */ + public DefaultHttpClient( + final ClientConnectionManager conman, + final HttpParams params) { + super(conman, params); + } + + + public DefaultHttpClient(final HttpParams params) { + super(null, params); + } + + + public DefaultHttpClient() { + super(null, null); + } + + + @Override + protected HttpParams createHttpParams() { + HttpParams params = new BasicHttpParams(); + HttpProtocolParams.setVersion(params, + HttpVersion.HTTP_1_1); + HttpProtocolParams.setContentCharset(params, + HTTP.DEFAULT_CONTENT_CHARSET); + HttpProtocolParams.setUseExpectContinue(params, + true); + + // determine the release version from packaged version info + final VersionInfo vi = VersionInfo.loadVersionInfo + ("org.apache.http.client", getClass().getClassLoader()); + final String release = (vi != null) ? + vi.getRelease() : VersionInfo.UNAVAILABLE; + HttpProtocolParams.setUserAgent(params, + "Apache-HttpClient/" + release + " (java 1.4)"); + + return params; + } + + + @Override + protected HttpRequestExecutor createRequestExecutor() { + return new HttpRequestExecutor(); + } + + + @Override + protected ClientConnectionManager createClientConnectionManager() { + SchemeRegistry registry = new SchemeRegistry(); + registry.register( + new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); + registry.register( + new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); + + ClientConnectionManager connManager = null; + HttpParams params = getParams(); + + ClientConnectionManagerFactory factory = null; + + // Try first getting the factory directly as an object. + factory = (ClientConnectionManagerFactory) params + .getParameter(ClientPNames.CONNECTION_MANAGER_FACTORY); + if (factory == null) { // then try getting its class name. + String className = (String) params.getParameter( + ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME); + if (className != null) { + try { + Class<?> clazz = Class.forName(className); + factory = (ClientConnectionManagerFactory) clazz.newInstance(); + } catch (ClassNotFoundException ex) { + throw new IllegalStateException("Invalid class name: " + className); + } catch (IllegalAccessException ex) { + throw new IllegalAccessError(ex.getMessage()); + } catch (InstantiationException ex) { + throw new InstantiationError(ex.getMessage()); + } + } + } + + if(factory != null) { + connManager = factory.newInstance(params, registry); + } else { + connManager = new SingleClientConnManager(getParams(), registry); + } + + return connManager; + } + + + @Override + protected HttpContext createHttpContext() { + HttpContext context = new BasicHttpContext(); + context.setAttribute( + ClientContext.AUTHSCHEME_REGISTRY, + getAuthSchemes()); + context.setAttribute( + ClientContext.COOKIESPEC_REGISTRY, + getCookieSpecs()); + context.setAttribute( + ClientContext.COOKIE_STORE, + getCookieStore()); + context.setAttribute( + ClientContext.CREDS_PROVIDER, + getCredentialsProvider()); + return context; + } + + + @Override + protected ConnectionReuseStrategy createConnectionReuseStrategy() { + return new DefaultConnectionReuseStrategy(); + } + + @Override + protected ConnectionKeepAliveStrategy createConnectionKeepAliveStrategy() { + return new DefaultConnectionKeepAliveStrategy(); + } + + + @Override + protected AuthSchemeRegistry createAuthSchemeRegistry() { + AuthSchemeRegistry registry = new AuthSchemeRegistry(); + registry.register( + AuthPolicy.BASIC, + new BasicSchemeFactory()); + registry.register( + AuthPolicy.DIGEST, + new DigestSchemeFactory()); + return registry; + } + + + @Override + protected CookieSpecRegistry createCookieSpecRegistry() { + CookieSpecRegistry registry = new CookieSpecRegistry(); + registry.register( + CookiePolicy.BEST_MATCH, + new BestMatchSpecFactory()); + registry.register( + CookiePolicy.BROWSER_COMPATIBILITY, + new BrowserCompatSpecFactory()); + registry.register( + CookiePolicy.NETSCAPE, + new NetscapeDraftSpecFactory()); + registry.register( + CookiePolicy.RFC_2109, + new RFC2109SpecFactory()); + registry.register( + CookiePolicy.RFC_2965, + new RFC2965SpecFactory()); + return registry; + } + + + @Override + protected BasicHttpProcessor createHttpProcessor() { + BasicHttpProcessor httpproc = new BasicHttpProcessor(); + httpproc.addInterceptor(new RequestDefaultHeaders()); + // Required protocol interceptors + httpproc.addInterceptor(new RequestContent()); + httpproc.addInterceptor(new RequestTargetHost()); + // Recommended protocol interceptors + httpproc.addInterceptor(new RequestConnControl()); + httpproc.addInterceptor(new RequestUserAgent()); + httpproc.addInterceptor(new RequestExpectContinue()); + // HTTP state management interceptors + httpproc.addInterceptor(new RequestAddCookies()); + httpproc.addInterceptor(new ResponseProcessCookies()); + // HTTP authentication interceptors + httpproc.addInterceptor(new RequestTargetAuthentication()); + httpproc.addInterceptor(new RequestProxyAuthentication()); + return httpproc; + } + + + @Override + protected HttpRequestRetryHandler createHttpRequestRetryHandler() { + return new DefaultHttpRequestRetryHandler(); + } + + + @Override + protected RedirectHandler createRedirectHandler() { + return new DefaultRedirectHandler(); + } + + + @Override + protected AuthenticationHandler createTargetAuthenticationHandler() { + return new DefaultTargetAuthenticationHandler(); + } + + + @Override + protected AuthenticationHandler createProxyAuthenticationHandler() { + return new DefaultProxyAuthenticationHandler(); + } + + + @Override + protected CookieStore createCookieStore() { + return new BasicCookieStore(); + } + + + @Override + protected CredentialsProvider createCredentialsProvider() { + return new BasicCredentialsProvider(); + } + + + @Override + protected HttpRoutePlanner createHttpRoutePlanner() { + return new DefaultHttpRoutePlanner + (getConnectionManager().getSchemeRegistry()); + } + + + @Override + protected UserTokenHandler createUserTokenHandler() { + return new DefaultUserTokenHandler(); + } + +} // class DefaultHttpClient diff --git a/src/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java b/src/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java new file mode 100644 index 0000000..7f66990 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java @@ -0,0 +1,134 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java $ + * $Revision: 652726 $ + * $Date: 2008-05-01 18:16:51 -0700 (Thu, 01 May 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.UnknownHostException; + +import javax.net.ssl.SSLHandshakeException; + +import org.apache.http.NoHttpResponseException; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.ExecutionContext; + +/** + * The default {@link HttpRequestRetryHandler} used by request executors. + * + * @author Michael Becke + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler { + + /** the number of times a method will be retried */ + private final int retryCount; + + /** Whether or not methods that have successfully sent their request will be retried */ + private final boolean requestSentRetryEnabled; + + /** + * Default constructor + */ + public DefaultHttpRequestRetryHandler(int retryCount, boolean requestSentRetryEnabled) { + super(); + this.retryCount = retryCount; + this.requestSentRetryEnabled = requestSentRetryEnabled; + } + + /** + * Default constructor + */ + public DefaultHttpRequestRetryHandler() { + this(3, false); + } + /** + * Used <code>retryCount</code> and <code>requestSentRetryEnabled</code> to determine + * if the given method should be retried. + */ + public boolean retryRequest( + final IOException exception, + int executionCount, + final HttpContext context) { + if (exception == null) { + throw new IllegalArgumentException("Exception parameter may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + if (executionCount > this.retryCount) { + // Do not retry if over max retry count + return false; + } + if (exception instanceof NoHttpResponseException) { + // Retry if the server dropped connection on us + return true; + } + if (exception instanceof InterruptedIOException) { + // Timeout + return false; + } + if (exception instanceof UnknownHostException) { + // Unknown host + return false; + } + if (exception instanceof SSLHandshakeException) { + // SSL handshake exception + return false; + } + Boolean b = (Boolean) + context.getAttribute(ExecutionContext.HTTP_REQ_SENT); + boolean sent = (b != null && b.booleanValue()); + if (!sent || this.requestSentRetryEnabled) { + // Retry if the request has not been sent fully or + // if it's OK to retry methods that have been sent + return true; + } + // otherwise do not retry + return false; + } + + /** + * @return <code>true</code> if this handler will retry methods that have + * successfully sent their request, <code>false</code> otherwise + */ + public boolean isRequestSentRetryEnabled() { + return requestSentRetryEnabled; + } + + /** + * @return the maximum number of times a method will be retried + */ + public int getRetryCount() { + return retryCount; + } +} diff --git a/src/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java b/src/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java new file mode 100644 index 0000000..c188da8 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java $ + * $Revision: 603615 $ + * $Date: 2007-12-12 06:03:21 -0800 (Wed, 12 Dec 2007) $ + * + * ==================================================================== + * 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.util.Map; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.protocol.HttpContext; + +/** + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class DefaultProxyAuthenticationHandler extends AbstractAuthenticationHandler { + + public DefaultProxyAuthenticationHandler() { + super(); + } + + public boolean isAuthenticationRequested( + final HttpResponse response, + final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + int status = response.getStatusLine().getStatusCode(); + return status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED; + } + + public Map<String, Header> getChallenges( + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + Header[] headers = response.getHeaders(AUTH.PROXY_AUTH); + return parseChallenges(headers); + } + +} diff --git a/src/org/apache/http/impl/client/DefaultRedirectHandler.java b/src/org/apache/http/impl/client/DefaultRedirectHandler.java new file mode 100644 index 0000000..0811b28 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultRedirectHandler.java @@ -0,0 +1,183 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultRedirectHandler.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 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.net.URI; +import java.net.URISyntaxException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.ProtocolException; +import org.apache.http.client.CircularRedirectException; +import org.apache.http.client.RedirectHandler; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.client.utils.URIUtils; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.ExecutionContext; + + +/** + * Default implementation of {@link RedirectHandler}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 673450 $ + * + * @since 4.0 + */ +public class DefaultRedirectHandler implements RedirectHandler { + + private final Log log = LogFactory.getLog(getClass()); + + private static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations"; + + public DefaultRedirectHandler() { + super(); + } + + public boolean isRedirectRequested( + final HttpResponse response, + final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + int statusCode = response.getStatusLine().getStatusCode(); + switch (statusCode) { + case HttpStatus.SC_MOVED_TEMPORARILY: + case HttpStatus.SC_MOVED_PERMANENTLY: + case HttpStatus.SC_SEE_OTHER: + case HttpStatus.SC_TEMPORARY_REDIRECT: + return true; + default: + return false; + } //end of switch + } + + public URI getLocationURI( + final HttpResponse response, + final HttpContext context) throws ProtocolException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + //get the location header to find out where to redirect to + Header locationHeader = response.getFirstHeader("location"); + if (locationHeader == null) { + // got a redirect response, but no location header + throw new ProtocolException( + "Received redirect response " + response.getStatusLine() + + " but no location header"); + } + String location = locationHeader.getValue(); + if (this.log.isDebugEnabled()) { + this.log.debug("Redirect requested to location '" + location + "'"); + } + + URI uri; + try { + uri = new URI(location); + } catch (URISyntaxException ex) { + throw new ProtocolException("Invalid redirect URI: " + location, ex); + } + + HttpParams params = response.getParams(); + // rfc2616 demands the location value be a complete URI + // Location = "Location" ":" absoluteURI + if (!uri.isAbsolute()) { + if (params.isParameterTrue(ClientPNames.REJECT_RELATIVE_REDIRECT)) { + throw new ProtocolException("Relative redirect location '" + + uri + "' not allowed"); + } + // Adjust location URI + HttpHost target = (HttpHost) context.getAttribute( + ExecutionContext.HTTP_TARGET_HOST); + if (target == null) { + throw new IllegalStateException("Target host not available " + + "in the HTTP context"); + } + + HttpRequest request = (HttpRequest) context.getAttribute( + ExecutionContext.HTTP_REQUEST); + + try { + URI requestURI = new URI(request.getRequestLine().getUri()); + URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target, true); + uri = URIUtils.resolve(absoluteRequestURI, uri); + } catch (URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + } + + if (params.isParameterFalse(ClientPNames.ALLOW_CIRCULAR_REDIRECTS)) { + + RedirectLocations redirectLocations = (RedirectLocations) context.getAttribute( + REDIRECT_LOCATIONS); + + if (redirectLocations == null) { + redirectLocations = new RedirectLocations(); + context.setAttribute(REDIRECT_LOCATIONS, redirectLocations); + } + + URI redirectURI; + if (uri.getFragment() != null) { + try { + HttpHost target = new HttpHost( + uri.getHost(), + uri.getPort(), + uri.getScheme()); + redirectURI = URIUtils.rewriteURI(uri, target, true); + } catch (URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + } else { + redirectURI = uri; + } + + if (redirectLocations.contains(redirectURI)) { + throw new CircularRedirectException("Circular redirect to '" + + redirectURI + "'"); + } else { + redirectLocations.add(redirectURI); + } + } + + return uri; + } + +} 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 diff --git a/src/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java b/src/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java new file mode 100644 index 0000000..5794549 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java $ + * $Revision: 603615 $ + * $Date: 2007-12-12 06:03:21 -0800 (Wed, 12 Dec 2007) $ + * + * ==================================================================== + * 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.util.Map; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.protocol.HttpContext; + +/** + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class DefaultTargetAuthenticationHandler extends AbstractAuthenticationHandler { + + public DefaultTargetAuthenticationHandler() { + super(); + } + + public boolean isAuthenticationRequested( + final HttpResponse response, + final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + int status = response.getStatusLine().getStatusCode(); + return status == HttpStatus.SC_UNAUTHORIZED; + } + + public Map<String, Header> getChallenges( + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + Header[] headers = response.getHeaders(AUTH.WWW_AUTH); + return parseChallenges(headers); + } + +} diff --git a/src/org/apache/http/impl/client/DefaultUserTokenHandler.java b/src/org/apache/http/impl/client/DefaultUserTokenHandler.java new file mode 100644 index 0000000..c8a409f --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultUserTokenHandler.java @@ -0,0 +1,88 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultUserTokenHandler.java $ + * $Revision: 659971 $ + * $Date: 2008-05-25 05:01:22 -0700 (Sun, 25 May 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.security.Principal; + +import javax.net.ssl.SSLSession; + +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.Credentials; +import org.apache.http.client.UserTokenHandler; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.protocol.ExecutionContext; +import org.apache.http.protocol.HttpContext; + +public class DefaultUserTokenHandler implements UserTokenHandler { + + public Object getUserToken(final HttpContext context) { + + Principal userPrincipal = null; + + AuthState targetAuthState = (AuthState) context.getAttribute( + ClientContext.TARGET_AUTH_STATE); + if (targetAuthState != null) { + userPrincipal = getAuthPrincipal(targetAuthState); + if (userPrincipal == null) { + AuthState proxyAuthState = (AuthState) context.getAttribute( + ClientContext.PROXY_AUTH_STATE); + userPrincipal = getAuthPrincipal(proxyAuthState); + } + } + + if (userPrincipal == null) { + ManagedClientConnection conn = (ManagedClientConnection) context.getAttribute( + ExecutionContext.HTTP_CONNECTION); + if (conn.isOpen()) { + SSLSession sslsession = conn.getSSLSession(); + if (sslsession != null) { + userPrincipal = sslsession.getLocalPrincipal(); + } + } + } + + return userPrincipal; + } + + private static Principal getAuthPrincipal(final AuthState authState) { + AuthScheme scheme = authState.getAuthScheme(); + if (scheme != null && scheme.isComplete() && scheme.isConnectionBased()) { + Credentials creds = authState.getCredentials(); + if (creds != null) { + return creds.getUserPrincipal(); + } + } + return null; + } + +} diff --git a/src/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java b/src/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java new file mode 100644 index 0000000..05098cf --- /dev/null +++ b/src/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java @@ -0,0 +1,83 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 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 org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.ProtocolException; +import org.apache.http.protocol.HTTP; + +/** + * A wrapper class for {@link HttpEntityEnclosingRequest}s that can + * be used to change properties of the current request without + * modifying the original object. + * </p> + * This class is also capable of resetting the request headers to + * the state of the original request. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 674186 $ + * + * @since 4.0 + */ +public class EntityEnclosingRequestWrapper extends RequestWrapper + implements HttpEntityEnclosingRequest { + + private HttpEntity entity; + + public EntityEnclosingRequestWrapper(final HttpEntityEnclosingRequest request) + throws ProtocolException { + super(request); + this.entity = request.getEntity(); + } + + public HttpEntity getEntity() { + return this.entity; + } + + public void setEntity(final HttpEntity entity) { + this.entity = entity; + } + + public boolean expectContinue() { + Header expect = getFirstHeader(HTTP.EXPECT_DIRECTIVE); + return expect != null && HTTP.EXPECT_CONTINUE.equalsIgnoreCase(expect.getValue()); + } + + @Override + public boolean isRepeatable() { + return this.entity == null || this.entity.isRepeatable(); + } + +} diff --git a/src/org/apache/http/impl/client/RedirectLocations.java b/src/org/apache/http/impl/client/RedirectLocations.java new file mode 100644 index 0000000..d5c47e7 --- /dev/null +++ b/src/org/apache/http/impl/client/RedirectLocations.java @@ -0,0 +1,71 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/RedirectLocations.java $ + * $Revision: 652020 $ + * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 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.net.URI; +import java.util.HashSet; +import java.util.Set; + +/** + * A collection of URIs that were used as redirects. + */ +public class RedirectLocations { + + private final Set<URI> uris; + + public RedirectLocations() { + super(); + this.uris = new HashSet<URI>(); + } + + /** + * Returns true if this collection contains the given URI. + */ + public boolean contains(final URI uri) { + return this.uris.contains(uri); + } + + /** + * Adds a new URI to the list of redirects. + */ + public void add(final URI uri) { + this.uris.add(uri); + } + + /** + * Removes a URI from the list of redirects. + */ + public boolean remove(final URI uri) { + return this.uris.remove(uri); + } + +} diff --git a/src/org/apache/http/impl/client/RequestWrapper.java b/src/org/apache/http/impl/client/RequestWrapper.java new file mode 100644 index 0000000..04a641d --- /dev/null +++ b/src/org/apache/http/impl/client/RequestWrapper.java @@ -0,0 +1,170 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/RequestWrapper.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 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.net.URI; +import java.net.URISyntaxException; + +import org.apache.http.HttpRequest; +import org.apache.http.ProtocolException; +import org.apache.http.ProtocolVersion; +import org.apache.http.RequestLine; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.AbstractHttpMessage; +import org.apache.http.message.BasicRequestLine; +import org.apache.http.params.HttpProtocolParams; + +/** + * A wrapper class for {@link HttpRequest}s that can be used to change + * properties of the current request without modifying the original + * object. + * </p> + * This class is also capable of resetting the request headers to + * the state of the original request. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 674186 $ + * + * @since 4.0 + */ +public class RequestWrapper extends AbstractHttpMessage implements HttpUriRequest { + + private final HttpRequest original; + + private URI uri; + private String method; + private ProtocolVersion version; + private int execCount; + + public RequestWrapper(final HttpRequest request) throws ProtocolException { + super(); + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + this.original = request; + setParams(request.getParams()); + // Make a copy of the original URI + if (request instanceof HttpUriRequest) { + this.uri = ((HttpUriRequest) request).getURI(); + this.method = ((HttpUriRequest) request).getMethod(); + this.version = null; + } else { + RequestLine requestLine = request.getRequestLine(); + try { + this.uri = new URI(requestLine.getUri()); + } catch (URISyntaxException ex) { + throw new ProtocolException("Invalid request URI: " + + requestLine.getUri(), ex); + } + this.method = requestLine.getMethod(); + this.version = request.getProtocolVersion(); + } + this.execCount = 0; + } + + public void resetHeaders() { + // Make a copy of original headers + this.headergroup.clear(); + setHeaders(this.original.getAllHeaders()); + } + + public String getMethod() { + return this.method; + } + + public void setMethod(final String method) { + if (method == null) { + throw new IllegalArgumentException("Method name may not be null"); + } + this.method = method; + } + + public ProtocolVersion getProtocolVersion() { + if (this.version != null) { + return this.version; + } else { + return HttpProtocolParams.getVersion(getParams()); + } + } + + public void setProtocolVersion(final ProtocolVersion version) { + this.version = version; + } + + + public URI getURI() { + return this.uri; + } + + public void setURI(final URI uri) { + this.uri = uri; + } + + public RequestLine getRequestLine() { + String method = getMethod(); + ProtocolVersion ver = getProtocolVersion(); + String uritext = null; + if (uri != null) { + uritext = uri.toASCIIString(); + } + if (uritext == null || uritext.length() == 0) { + uritext = "/"; + } + return new BasicRequestLine(method, uritext, ver); + } + + public void abort() throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public boolean isAborted() { + return false; + } + + public HttpRequest getOriginal() { + return this.original; + } + + public boolean isRepeatable() { + return true; + } + + public int getExecCount() { + return this.execCount; + } + + public void incrementExecCount() { + this.execCount++; + } + +} diff --git a/src/org/apache/http/impl/client/RoutedRequest.java b/src/org/apache/http/impl/client/RoutedRequest.java new file mode 100644 index 0000000..954ebe5 --- /dev/null +++ b/src/org/apache/http/impl/client/RoutedRequest.java @@ -0,0 +1,73 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/RoutedRequest.java $ + * $Revision: 645846 $ + * $Date: 2008-04-08 03:53:39 -0700 (Tue, 08 Apr 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 org.apache.http.conn.routing.HttpRoute; + + +/** + * A request with the route along which it should be sent. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 645846 $ + * + * @since 4.0 + */ +public class RoutedRequest { + + protected final RequestWrapper request; + protected final HttpRoute route; + + /** + * Creates a new routed request. + * + * @param req the request + * @param route the route + */ + public RoutedRequest(final RequestWrapper req, final HttpRoute route) { + super(); + this.request = req; + this.route = route; + } + + public final RequestWrapper getRequest() { + return request; + } + + public final HttpRoute getRoute() { + return route; + } + +} // interface RoutedRequest diff --git a/src/org/apache/http/impl/client/TunnelRefusedException.java b/src/org/apache/http/impl/client/TunnelRefusedException.java new file mode 100644 index 0000000..601626f --- /dev/null +++ b/src/org/apache/http/impl/client/TunnelRefusedException.java @@ -0,0 +1,52 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/TunnelRefusedException.java $ + * $Revision: 537650 $ + * $Date: 2007-05-13 12:58:22 -0700 (Sun, 13 May 2007) $ + * + * ==================================================================== + * 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 org.apache.http.HttpException; +import org.apache.http.HttpResponse; + +public class TunnelRefusedException extends HttpException { + + private static final long serialVersionUID = -8646722842745617323L; + + private final HttpResponse response; + + public TunnelRefusedException(final String message, final HttpResponse response) { + super(message); + this.response = response; + } + + public HttpResponse getResponse() { + return this.response; + } + +} diff --git a/src/org/apache/http/impl/client/package.html b/src/org/apache/http/impl/client/package.html new file mode 100644 index 0000000..e301283 --- /dev/null +++ b/src/org/apache/http/impl/client/package.html @@ -0,0 +1,4 @@ +<body> + +</body> + |