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:
parent
9a534e7e8d
commit
c04bd63e47
|
@ -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;
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue