Initial port of the redirect handling code

git-svn-id: https://svn.apache.org/repos/asf/jakarta/httpcomponents/httpclient/trunk@537378 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2007-05-12 10:34:30 +00:00
parent 9a534e7e8d
commit c04bd63e47
8 changed files with 350 additions and 14 deletions

View File

@ -0,0 +1,78 @@
/*
* $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.client;
import java.net.URI;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException;
import org.apache.http.protocol.HttpContext;
/**
* A handler 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>
* Classes implementing this interface must synchronize access to shared
* data as methods of this interfrace may be executed from multiple threads
* </p>
*
* @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
*/
public interface RedirectHandler {
/**
* Determines if a request should be redirected to a new location
* given the response from the target server.
*
* @param response the response received from the target server
*
* @return <code>true</code> if the request should be redirected, <code>false</code>
* otherwise
*/
boolean isRedirectNeeded(HttpResponse response);
/**
* Determines the location request is expected to be redirected to
* given the response from the target server and the current request
* execution context.
*
* @param response the response received from the target server
* @param context the context for the request execution
*
* @return redirect URI
*/
URI getLocationURI(HttpResponse response, HttpContext context)
throws ProtocolException;
}

View File

@ -69,6 +69,14 @@ public class HttpClientParams {
*/
public static final String PREEMPTIVE_AUTHENTICATION = "http.authentication.preemptive";
/**
* Defines whether redirects should be handled automatically
* <p>
* This parameter expects a value of type {@link Boolean}.
* </p>
*/
public static final String HANDLE_REDIRECTS = "http.protocol.handle-redirects";
/**
* Defines whether relative redirects should be rejected.
* <p>
@ -104,6 +112,15 @@ public class HttpClientParams {
*/
public static final String COOKIE_POLICY = "http.protocol.cookie-policy";
/**
* Defines the request headers to be sent per default with each request.
* <p>
* This parameter expects a value of type {@link java.util.Collection}. The
* collection is expected to contain {@link org.apache.http.Header}s.
* </p>
*/
public static final String DEFAULT_HEADERS = "http.default-headers";
private HttpClientParams() {
super();
}

View File

@ -126,6 +126,17 @@ public final class HttpRoute {
}
/**
* Creates a new direct insecure route.
* That is a route without a proxy.
*
* @param target the host to which to route
*/
public HttpRoute(HttpHost target) {
this(target, null, null, false, false, false);
}
/**
* Creates a new route through a proxy.
* When using this constructor, the <code>proxy</code> MUST be given.

View File

@ -47,6 +47,7 @@ import org.apache.http.client.ClientRequestDirector;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.HttpState;
import org.apache.http.client.RedirectHandler;
import org.apache.http.client.RoutedRequest;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
@ -99,6 +100,9 @@ public abstract class AbstractHttpClient
/** The request retry handler. */
private HttpRequestRetryHandler retryHandler;
/** The redirect handler. */
private RedirectHandler redirectHandler;
/** The default HTTP state. */
private HttpState defaultState;
@ -140,6 +144,9 @@ public abstract class AbstractHttpClient
protected abstract HttpRequestRetryHandler createHttpRequestRetryHandler();
protected abstract RedirectHandler createRedirectHandler();
protected abstract HttpState createHttpState();
@ -231,6 +238,19 @@ public abstract class AbstractHttpClient
}
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 HttpState getState() {
if (defaultState == null) {
defaultState = createHttpState();
@ -397,6 +417,7 @@ public abstract class AbstractHttpClient
getConnectionReuseStrategy(),
getHttpProcessor().copy(),
getHttpRequestRetryHandler(),
getRedirectHandler(),
getParams());
}

View File

@ -32,12 +32,16 @@
package org.apache.http.impl.client;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Iterator;
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;
@ -48,8 +52,10 @@ import org.apache.http.HttpVersion;
import org.apache.http.ProtocolException;
import org.apache.http.client.ClientRequestDirector;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.RedirectHandler;
import org.apache.http.client.RoutedRequest;
import org.apache.http.client.methods.AbortableHttpRequest;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.BasicManagedEntity;
import org.apache.http.conn.ClientConnectionManager;
@ -57,6 +63,8 @@ import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.conn.HttpRoute;
import org.apache.http.conn.ManagedClientConnection;
import org.apache.http.conn.RouteDirector;
import org.apache.http.conn.Scheme;
import org.apache.http.conn.SchemeRegistry;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
@ -98,6 +106,9 @@ public class DefaultClientRequestDirector
/** The request retry handler. */
protected final HttpRequestRetryHandler retryHandler;
/** The redirect handler. */
protected final RedirectHandler redirectHandler;
/** The HTTP parameters. */
protected final HttpParams params;
@ -110,6 +121,7 @@ public class DefaultClientRequestDirector
final ConnectionReuseStrategy reustrat,
final HttpProcessor httpProcessor,
final HttpRequestRetryHandler retryHandler,
final RedirectHandler redirectHandler,
final HttpParams params) {
if (conman == null) {
@ -124,6 +136,9 @@ public class DefaultClientRequestDirector
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 (params == null) {
throw new IllegalArgumentException("HTTP parameters may not be null");
}
@ -131,6 +146,7 @@ public class DefaultClientRequestDirector
this.reuseStrategy = reustrat;
this.httpProcessor = httpProcessor;
this.retryHandler = retryHandler;
this.redirectHandler = redirectHandler;
this.params = params;
this.requestExec = new HttpRequestExecutor(params);
@ -206,20 +222,35 @@ public class DefaultClientRequestDirector
public HttpResponse execute(RoutedRequest roureq, HttpContext context)
throws HttpException, IOException {
RequestWrapper request = wrapRequest(roureq.getRequest());
HttpRequest orig = roureq.getRequest();
// Link parameter collections to form a hierarchy:
// request -> client
orig.getParams().setDefaults(this.params);
// Add default headers
Collection defHeaders = (Collection) orig.getParams().getParameter(
HttpClientParams.DEFAULT_HEADERS);
if (defHeaders != null) {
for (Iterator it = defHeaders.iterator(); it.hasNext(); ) {
orig.addHeader((Header) it.next());
}
}
int execCount = 0;
HttpResponse response = null;
boolean done = false;
try {
int execCount = 0;
while (!done) {
RequestWrapper request = wrapRequest(roureq.getRequest());
HttpRoute route = roureq.getRoute();
// Re-write request URI if needed
rewriteRequestURI(request, route);
if (managedConn == null) {
if (managedConn == null || !managedConn.isOpen()) {
managedConn = allocateConnection(route);
}
establishRoute(route, context);
@ -465,9 +496,9 @@ public class DefaultClientRequestDirector
* Analyzes a response to check need for a followup.
*
* @param roureq the request and route. This is the same object as
* was passed to {@link #prepareRequest prepareRequest}.
* was passed to {@link #wrapRequest(HttpRequest)}.
* @param request the request that was actually sent. This is the object
* returned by {@link #prepareRequest prepareRequest}.
* returned by {@link #wrapRequest(HttpRequest)}.
* @param response the response to analayze
* @param context the context used for the current request execution
*
@ -483,15 +514,50 @@ public class DefaultClientRequestDirector
HttpContext context)
throws HttpException, IOException {
//@@@ if there is a followup, check connection keep-alive and
//@@@ consume response body if necessary or close otherwise
//@@@ if the request needs to be re-sent with authentication,
//@@@ how to revert the modifications applied by the interceptors?
//@@@ use a wrapper when sending?
HttpParams params = request.getParams();
if (params.getBooleanParameter(HttpClientParams.HANDLE_REDIRECTS, true) &&
this.redirectHandler.isRedirectNeeded(response)) {
URI uri;
try {
uri = this.redirectHandler.getLocationURI(response, context);
} catch (ProtocolException ex) {
if (LOG.isWarnEnabled()) {
LOG.warn(ex.getMessage());
}
return null;
}
HttpRoute oldRoute = roureq.getRoute();
HttpHost newTarget = new HttpHost(
uri.getHost(),
uri.getPort(),
uri.getScheme());
SchemeRegistry schemeRegistry = this.connManager.getSchemeRegistry();
Scheme schm = schemeRegistry.getScheme(newTarget.getSchemeName());
InetAddress localAddress = oldRoute.getLocalAddress();
HttpHost proxy = oldRoute.getProxyHost();
HttpRoute newRoute = new HttpRoute(
newTarget,
localAddress,
proxy,
schm.isLayered(),
(proxy != null),
(proxy != null));
HttpGet redirect = new HttpGet(uri);
if (LOG.isDebugEnabled()) {
LOG.debug("Redirecting to '" + uri + "' via " + newRoute);
}
return new RoutedRequest.Impl(redirect, newRoute);
}
return null;
} // handleResponse

View File

@ -39,6 +39,7 @@ import org.apache.http.HttpVersion;
import org.apache.http.auth.AuthSchemeRegistry;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.HttpState;
import org.apache.http.client.RedirectHandler;
import org.apache.http.client.RoutedRequest;
import org.apache.http.client.params.AuthPolicy;
import org.apache.http.client.params.CookiePolicy;
@ -211,6 +212,11 @@ public class DefaultHttpClient extends AbstractHttpClient {
}
protected RedirectHandler createRedirectHandler() {
return new DefaultRedirectHandler();
}
protected HttpState createHttpState() {
return new HttpState();
}

View File

@ -0,0 +1,137 @@
/*
* $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 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.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ProtocolException;
import org.apache.http.client.RedirectHandler;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpExecutionContext;
/**
* Default implementation of a redirect handler.
*
* @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
*
* <!-- empty lines to avoid svn diff problems -->
* @version $Revision$
*
* @since 4.0
*/
public class DefaultRedirectHandler implements RedirectHandler {
private static final Log LOG = LogFactory.getLog(DefaultRedirectHandler.class);
public boolean isRedirectNeeded(final HttpResponse response) {
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 (LOG.isDebugEnabled()) {
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(HttpClientParams.REJECT_RELATIVE_REDIRECT)) {
throw new ProtocolException("Relative redirect location '"
+ uri + "' not allowed");
}
// Adjust location URI
HttpHost target = (HttpHost) context.getAttribute(
HttpExecutionContext.HTTP_TARGET_HOST);
if (target == null) {
throw new IllegalStateException("Target host not available " +
"in the HTTP context");
}
try {
uri = new URI(
target.getSchemeName(),
null,
target.getHostName(),
target.getPort(),
uri.getPath(),
uri.getQuery(),
uri.getFragment());
} catch (URISyntaxException ex) {
throw new ProtocolException(ex.getMessage(), ex);
}
}
return uri;
}
}

View File

@ -130,8 +130,8 @@ class RequestWrapper extends AbstractHttpMessage implements HttpUriRequest {
return new BasicRequestLine(method, uritext, ver);
}
public void resetHeaders() {
setHeaders(this.original.getAllHeaders());
public HttpRequest getOriginal() {
return this.original;
}
}