diff --git a/src/java/org/apache/http/client/RedirectHandler.java b/src/java/org/apache/http/client/RedirectHandler.java new file mode 100644 index 000000000..7b3cbe0f7 --- /dev/null +++ b/src/java/org/apache/http/client/RedirectHandler.java @@ -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 + * . + * + */ + +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. + * + *

+ * Classes implementing this interface must synchronize access to shared + * data as methods of this interfrace may be executed from multiple threads + *

+ * + * @author Oleg Kalnichevski + */ +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 true if the request should be redirected, false + * 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; + +} diff --git a/src/java/org/apache/http/client/params/HttpClientParams.java b/src/java/org/apache/http/client/params/HttpClientParams.java index 46e4adfb9..9dbf0cc7a 100644 --- a/src/java/org/apache/http/client/params/HttpClientParams.java +++ b/src/java/org/apache/http/client/params/HttpClientParams.java @@ -69,6 +69,14 @@ public class HttpClientParams { */ public static final String PREEMPTIVE_AUTHENTICATION = "http.authentication.preemptive"; + /** + * Defines whether redirects should be handled automatically + *

+ * This parameter expects a value of type {@link Boolean}. + *

+ */ + public static final String HANDLE_REDIRECTS = "http.protocol.handle-redirects"; + /** * Defines whether relative redirects should be rejected. *

@@ -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. + *

+ * This parameter expects a value of type {@link java.util.Collection}. The + * collection is expected to contain {@link org.apache.http.Header}s. + *

+ */ + public static final String DEFAULT_HEADERS = "http.default-headers"; + private HttpClientParams() { super(); } diff --git a/src/java/org/apache/http/conn/HttpRoute.java b/src/java/org/apache/http/conn/HttpRoute.java index b3457f7ae..f1f117791 100644 --- a/src/java/org/apache/http/conn/HttpRoute.java +++ b/src/java/org/apache/http/conn/HttpRoute.java @@ -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 proxy MUST be given. diff --git a/src/java/org/apache/http/impl/client/AbstractHttpClient.java b/src/java/org/apache/http/impl/client/AbstractHttpClient.java index b4016d8d8..56f88c0bb 100644 --- a/src/java/org/apache/http/impl/client/AbstractHttpClient.java +++ b/src/java/org/apache/http/impl/client/AbstractHttpClient.java @@ -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()); } diff --git a/src/java/org/apache/http/impl/client/DefaultClientRequestDirector.java b/src/java/org/apache/http/impl/client/DefaultClientRequestDirector.java index a9a2a7b8f..4ec97b536 100644 --- a/src/java/org/apache/http/impl/client/DefaultClientRequestDirector.java +++ b/src/java/org/apache/http/impl/client/DefaultClientRequestDirector.java @@ -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 + HttpParams params = request.getParams(); + if (params.getBooleanParameter(HttpClientParams.HANDLE_REDIRECTS, true) && + this.redirectHandler.isRedirectNeeded(response)) { - //@@@ if the request needs to be re-sent with authentication, - //@@@ how to revert the modifications applied by the interceptors? - //@@@ use a wrapper when sending? + 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 diff --git a/src/java/org/apache/http/impl/client/DefaultHttpClient.java b/src/java/org/apache/http/impl/client/DefaultHttpClient.java index 21f80f45e..fc9d51ddf 100644 --- a/src/java/org/apache/http/impl/client/DefaultHttpClient.java +++ b/src/java/org/apache/http/impl/client/DefaultHttpClient.java @@ -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(); } diff --git a/src/java/org/apache/http/impl/client/DefaultRedirectHandler.java b/src/java/org/apache/http/impl/client/DefaultRedirectHandler.java new file mode 100644 index 000000000..f529939b8 --- /dev/null +++ b/src/java/org/apache/http/impl/client/DefaultRedirectHandler.java @@ -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 + * . + * + */ + +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 Oleg Kalnichevski + * + * + * @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; + } + +} diff --git a/src/java/org/apache/http/impl/client/RequestWrapper.java b/src/java/org/apache/http/impl/client/RequestWrapper.java index fc5c30599..88b3cf0b7 100644 --- a/src/java/org/apache/http/impl/client/RequestWrapper.java +++ b/src/java/org/apache/http/impl/client/RequestWrapper.java @@ -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; } }