HTTPCLIENT-862: Extended client's redirect handling interface to allow control of the content of the redirect

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@883855 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2009-11-24 20:27:18 +00:00
parent f6699b27ff
commit 51645b1cdf
9 changed files with 436 additions and 49 deletions

View File

@ -1,6 +1,10 @@
Changes since 4.0
-------------------
* [HTTPCLIENT-862] Extended client's redirect handling interface to allow
control of the content of the redirect.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-872] HttpClient can now persist authentication data between request
executions as long as they share the same execution context. It has also become
much easier to make HttpClient authenticate preemptively by pre-populating

View File

@ -43,7 +43,10 @@ import org.apache.http.protocol.HttpContext;
* from multiple threads.
*
* @since 4.0
*
* @deprecated use {@link RedirectStrategy}
*/
@Deprecated
public interface RedirectHandler {
/**

View File

@ -0,0 +1,81 @@
/*
* ====================================================================
* 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.client;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.protocol.HttpContext;
/**
* A strategy for determining if an HTTP request should be redirected to
* a new location in response to an HTTP response received from the target
* server.
* <p>
* Implementations of this interface must be thread-safe. Access to shared
* data must be synchronized as methods of this interface may be executed
* from multiple threads.
*
* @since 4.1
*/
public interface RedirectStrategy {
/**
* Determines if a request should be redirected to a new location
* given the response from the target server.
*
* @param request the executed request
* @param response the response received from the target server
* @param context the context for the request execution
*
* @return <code>true</code> if the request should be redirected, <code>false</code>
* otherwise
*/
boolean isRedirected(
HttpRequest request,
HttpResponse response,
HttpContext context) throws ProtocolException;
/**
* Determines the redirect location given the response from the target
* server and the current request execution context and generates a new
* request to be sent to the location.
*
* @param request the executed request
* @param response the response received from the target server
* @param context the context for the request execution
*
* @return redirected request
*/
HttpUriRequest getRedirect(
HttpRequest request,
HttpResponse response,
HttpContext context) throws ProtocolException;
}

View File

@ -47,13 +47,13 @@ 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.RedirectStrategy;
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;
@ -127,10 +127,10 @@ import org.apache.http.protocol.ImmutableHttpProcessor;
* may involve multiple intermediate hops.
* The {@link #createHttpRoutePlanner()} must be implemented
* by concrete super classes to instantiate this object.
* <li>{@link RedirectHandler}</li> object used to determine if an HTTP
* <li>{@link RedirectStrategy}</li> object used to determine if an HTTP
* request should be redirected to a new location in response to an HTTP
* response received from the target server.
* The {@link #createRedirectHandler()} must be implemented
* The {@link #createRedirectStrategy()} must be implemented
* by concrete super classes to instantiate this object.
* <li>{@link UserTokenHandler}</li> object used to determine if the
* execution context is user identity specific.
@ -200,7 +200,7 @@ public abstract class AbstractHttpClient implements HttpClient {
/** The redirect handler. */
@GuardedBy("this")
private RedirectHandler redirectHandler;
private RedirectStrategy redirectStrategy;
/** The target authentication handler. */
@GuardedBy("this")
@ -270,9 +270,15 @@ public abstract class AbstractHttpClient implements HttpClient {
protected abstract HttpRequestRetryHandler createHttpRequestRetryHandler();
protected abstract RedirectHandler createRedirectHandler();
@Deprecated
protected abstract org.apache.http.client.RedirectHandler createRedirectHandler();
/**
* @since 4.1
*/
protected abstract RedirectStrategy createRedirectStrategy();
protected abstract AuthenticationHandler createTargetAuthenticationHandler();
@ -392,16 +398,32 @@ public abstract class AbstractHttpClient implements HttpClient {
}
public synchronized final RedirectHandler getRedirectHandler() {
if (redirectHandler == null) {
redirectHandler = createRedirectHandler();
}
return redirectHandler;
@Deprecated
public synchronized final org.apache.http.client.RedirectHandler getRedirectHandler() {
return createRedirectHandler();
}
public synchronized void setRedirectHandler(final RedirectHandler redirectHandler) {
this.redirectHandler = redirectHandler;
@Deprecated
public synchronized void setRedirectHandler(final org.apache.http.client.RedirectHandler redirectHandler) {
this.redirectStrategy = new DefaultRedirectStrategyAdaptor(redirectHandler);
}
/**
* @since 4.1
*/
public synchronized final RedirectStrategy getRedirectStrategy() {
if (redirectStrategy == null) {
redirectStrategy = createRedirectStrategy();
}
return redirectStrategy;
}
/**
* @since 4.1
*/
public synchronized void setRedirectStrategy(final RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
@ -677,6 +699,7 @@ public abstract class AbstractHttpClient implements HttpClient {
}
}
@Deprecated
protected RequestDirector createClientRequestDirector(
final HttpRequestExecutor requestExec,
final ClientConnectionManager conman,
@ -685,7 +708,7 @@ public abstract class AbstractHttpClient implements HttpClient {
final HttpRoutePlanner rouplan,
final HttpProcessor httpProcessor,
final HttpRequestRetryHandler retryHandler,
final RedirectHandler redirectHandler,
final org.apache.http.client.RedirectHandler redirectHandler,
final AuthenticationHandler targetAuthHandler,
final AuthenticationHandler proxyAuthHandler,
final UserTokenHandler stateHandler,
@ -705,6 +728,36 @@ public abstract class AbstractHttpClient implements HttpClient {
params);
}
/**
* @since 4.1
*/
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 RedirectStrategy redirectStrategy,
final AuthenticationHandler targetAuthHandler,
final AuthenticationHandler proxyAuthHandler,
final UserTokenHandler stateHandler,
final HttpParams params) {
return new DefaultRequestDirector(
requestExec,
conman,
reustrat,
kastrat,
rouplan,
httpProcessor,
retryHandler,
redirectStrategy,
targetAuthHandler,
proxyAuthHandler,
stateHandler,
params);
}
/**
* Obtains parameters for executing a request.
* The default implementation in this class creates a new

View File

@ -36,7 +36,7 @@ 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.RedirectStrategy;
import org.apache.http.client.UserTokenHandler;
import org.apache.http.client.params.AuthPolicy;
import org.apache.http.client.params.ClientPNames;
@ -354,10 +354,19 @@ public class DefaultHttpClient extends AbstractHttpClient {
@Override
protected RedirectHandler createRedirectHandler() {
@Deprecated
protected org.apache.http.client.RedirectHandler createRedirectHandler() {
return new DefaultRedirectHandler();
}
/**
* @since 4.1
*/
@Override
protected RedirectStrategy createRedirectStrategy() {
return new DefaultRedirectStrategy();
}
@Override
protected AuthenticationHandler createTargetAuthenticationHandler() {

View File

@ -54,8 +54,11 @@ import org.apache.http.protocol.ExecutionContext;
* Default implementation of {@link RedirectHandler}.
*
* @since 4.0
*
* @deprecated use {@link DefaultRedirectStrategy}.
*/
@Immutable
@Deprecated
public class DefaultRedirectHandler implements RedirectHandler {
private final Log log = LogFactory.getLog(getClass());

View File

@ -0,0 +1,193 @@
/*
* ====================================================================
* 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.annotation.Immutable;
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.RedirectStrategy;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpUriRequest;
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 RedirectStrategy}.
*
* @since 4.1
*/
@Immutable
public class DefaultRedirectStrategy implements RedirectStrategy {
private final Log log = LogFactory.getLog(getClass());
private static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations";
public DefaultRedirectStrategy() {
super();
}
public boolean isRedirected(
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws ProtocolException {
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_TEMPORARY_REDIRECT:
String method = request.getRequestLine().getMethod();
return method.equalsIgnoreCase(HttpGet.METHOD_NAME)
|| method.equalsIgnoreCase(HttpHead.METHOD_NAME);
case HttpStatus.SC_SEE_OTHER:
return true;
default:
return false;
} //end of switch
}
public URI getLocationURI(
final HttpRequest request,
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");
}
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;
}
public HttpUriRequest getRedirect(
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws ProtocolException {
URI uri = getLocationURI(request, response, context);
String method = request.getRequestLine().getMethod();
if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) {
return new HttpHead(uri);
} else {
return new HttpGet(uri);
}
}
}

View File

@ -29,35 +29,49 @@ package org.apache.http.impl.client;
import java.net.URI;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException;
import org.apache.http.annotation.Immutable;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.protocol.HttpContext;
/**
* Redirect request (can be either GET or HEAD).
*
* @since 4.0
* @since 4.1
*/
@NotThreadSafe
class HttpRedirect extends HttpRequestBase {
@Immutable
@Deprecated
class DefaultRedirectStrategyAdaptor implements RedirectStrategy {
private String method;
private final org.apache.http.client.RedirectHandler handler;
public HttpRedirect(final String method, final URI uri) {
@Deprecated
public DefaultRedirectStrategyAdaptor(final org.apache.http.client.RedirectHandler handler) {
super();
if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) {
this.method = HttpHead.METHOD_NAME;
} else {
this.method = HttpGet.METHOD_NAME;
}
setURI(uri);
this.handler = handler;
}
@Override
public String getMethod() {
return this.method;
public boolean isRedirected(
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws ProtocolException {
return this.handler.isRedirectRequested(response, context);
}
public HttpUriRequest getRedirect(
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws ProtocolException {
URI uri = this.handler.getLocationURI(response, context);
String method = request.getRequestLine().getMethod();
if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) {
return new HttpHead(uri);
} else {
return new HttpGet(uri);
}
}
}

View File

@ -56,14 +56,15 @@ 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.RedirectStrategy;
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.HttpUriRequest;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.client.protocol.ClientContext;
@ -158,7 +159,11 @@ public class DefaultRequestDirector implements RequestDirector {
protected final HttpRequestRetryHandler retryHandler;
/** The redirect handler. */
protected final RedirectHandler redirectHandler;
@Deprecated
protected final org.apache.http.client.RedirectHandler redirectHandler = null;
/** The redirect strategy. */
protected final RedirectStrategy redirectStrategy;
/** The target authentication handler. */
protected final AuthenticationHandler targetAuthHandler;
@ -184,7 +189,8 @@ public class DefaultRequestDirector implements RequestDirector {
private int maxRedirects;
private HttpHost virtualHost;
@Deprecated
public DefaultRequestDirector(
final HttpRequestExecutor requestExec,
final ClientConnectionManager conman,
@ -193,7 +199,29 @@ public class DefaultRequestDirector implements RequestDirector {
final HttpRoutePlanner rouplan,
final HttpProcessor httpProcessor,
final HttpRequestRetryHandler retryHandler,
final RedirectHandler redirectHandler,
final org.apache.http.client.RedirectHandler redirectHandler,
final AuthenticationHandler targetAuthHandler,
final AuthenticationHandler proxyAuthHandler,
final UserTokenHandler userTokenHandler,
final HttpParams params) {
this(requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler,
new DefaultRedirectStrategyAdaptor(redirectHandler),
targetAuthHandler, proxyAuthHandler, userTokenHandler, params);
}
/**
* @since 4.1
*/
public DefaultRequestDirector(
final HttpRequestExecutor requestExec,
final ClientConnectionManager conman,
final ConnectionReuseStrategy reustrat,
final ConnectionKeepAliveStrategy kastrat,
final HttpRoutePlanner rouplan,
final HttpProcessor httpProcessor,
final HttpRequestRetryHandler retryHandler,
final RedirectStrategy redirectStrategy,
final AuthenticationHandler targetAuthHandler,
final AuthenticationHandler proxyAuthHandler,
final UserTokenHandler userTokenHandler,
@ -227,9 +255,9 @@ public class DefaultRequestDirector implements RequestDirector {
throw new IllegalArgumentException
("HTTP request retry handler may not be null.");
}
if (redirectHandler == null) {
if (redirectStrategy == null) {
throw new IllegalArgumentException
("Redirect handler may not be null.");
("Redirect strategy may not be null.");
}
if (targetAuthHandler == null) {
throw new IllegalArgumentException
@ -254,7 +282,7 @@ public class DefaultRequestDirector implements RequestDirector {
this.routePlanner = rouplan;
this.httpProcessor = httpProcessor;
this.retryHandler = retryHandler;
this.redirectHandler = redirectHandler;
this.redirectStrategy = redirectStrategy;
this.targetAuthHandler = targetAuthHandler;
this.proxyAuthHandler = proxyAuthHandler;
this.userTokenHandler = userTokenHandler;
@ -810,7 +838,6 @@ public class DefaultRequestDirector implements RequestDirector {
}
}
@SuppressWarnings("null")
int status = response.getStatusLine().getStatusCode(); // can't be null
if (status > 299) {
@ -936,7 +963,7 @@ public class DefaultRequestDirector implements RequestDirector {
HttpParams params = request.getParams();
if (HttpClientParams.isRedirecting(params) &&
this.redirectHandler.isRedirectRequested(response, context)) {
this.redirectStrategy.isRedirected(request, response, context)) {
if (redirectCount >= maxRedirects) {
throw new RedirectException("Maximum redirects ("
@ -947,7 +974,11 @@ public class DefaultRequestDirector implements RequestDirector {
// Virtual host cannot be used any longer
virtualHost = null;
URI uri = this.redirectHandler.getLocationURI(response, context);
HttpUriRequest redirect = redirectStrategy.getRedirect(request, response, context);
HttpRequest orig = request.getOriginal();
redirect.setHeaders(orig.getAllHeaders());
URI uri = redirect.getURI();
HttpHost newTarget = new HttpHost(
uri.getHost(),
@ -967,10 +998,6 @@ public class DefaultRequestDirector implements RequestDirector {
}
}
HttpRedirect redirect = new HttpRedirect(request.getMethod(), uri);
HttpRequest orig = request.getOriginal();
redirect.setHeaders(orig.getAllHeaders());
RequestWrapper wrapper = new RequestWrapper(redirect);
wrapper.setParams(params);