Issue 380: handle redirects with relative paths

This commit is contained in:
Adrian Cole 2010-11-13 07:28:02 +01:00
parent 08b8bb1f00
commit 5b27c07eaf
4 changed files with 217 additions and 23 deletions

View File

@ -19,10 +19,15 @@
package org.jclouds.http;
import static com.google.common.base.Preconditions.checkNotNull;
import javax.annotation.Nullable;
import org.jclouds.io.Payload;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
/**
* Represents a response produced from {@link HttpCommandExecutorService}
*
@ -34,9 +39,14 @@ public class HttpResponse extends HttpMessage {
private final String message;
public HttpResponse(int statusCode, String message, @Nullable Payload payload) {
this(statusCode, message, payload, ImmutableMultimap.<String, String> of());
}
public HttpResponse(int statusCode, String message, @Nullable Payload payload, Multimap<String, String> headers) {
super(payload);
this.statusCode = statusCode;
this.message = message;
this.headers.putAll(checkNotNull(headers));
}
public int getStatusCode() {
@ -49,8 +59,8 @@ public class HttpResponse extends HttpMessage {
@Override
public String toString() {
return "[message=" + message + ", statusCode=" + statusCode + ", headers=" + headers
+ ", payload=" + payload + "]";
return "[message=" + message + ", statusCode=" + statusCode + ", headers=" + headers + ", payload=" + payload
+ "]";
}
public String getStatusLine() {

View File

@ -19,8 +19,8 @@
package org.jclouds.http.handlers;
import static org.jclouds.http.HttpUtils.changePathTo;
import static org.jclouds.http.HttpUtils.changeSchemeHostAndPortTo;
import static java.util.Collections.singletonList;
import static javax.ws.rs.core.HttpHeaders.HOST;
import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
import java.net.URI;
@ -58,8 +58,7 @@ public class RedirectionRetryHandler implements HttpRetryHandler {
protected final Provider<UriBuilder> uriBuilderProvider;
@Inject
protected RedirectionRetryHandler(Provider<UriBuilder> uriBuilderProvider,
BackoffLimitedRetryHandler backoffHandler) {
protected RedirectionRetryHandler(Provider<UriBuilder> uriBuilderProvider, BackoffLimitedRetryHandler backoffHandler) {
this.backoffHandler = backoffHandler;
this.uriBuilderProvider = uriBuilderProvider;
}
@ -67,25 +66,36 @@ public class RedirectionRetryHandler implements HttpRetryHandler {
public boolean shouldRetryRequest(HttpCommand command, HttpResponse response) {
closeClientButKeepContentStream(response);
String hostHeader = response.getFirstHeaderOrNull(HttpHeaders.LOCATION);
if (hostHeader != null && command.incrementRedirectCount() < retryCountLimit) {
URI redirectionUrl = uriBuilderProvider.get().uri(URI.create(hostHeader)).build();
if (redirectionUrl.getScheme().equals(command.getRequest().getEndpoint().getScheme())
&& redirectionUrl.getHost().equals(command.getRequest().getEndpoint().getHost())
&& redirectionUrl.getPort() == command.getRequest().getEndpoint().getPort()) {
if (!redirectionUrl.getPath().equals(command.getRequest().getEndpoint().getPath())) {
changePathTo(command.getRequest(), redirectionUrl.getPath(), uriBuilderProvider
.get());
} else {
return backoffHandler.shouldRetryRequest(command, response);
}
} else {
changeSchemeHostAndPortTo(command.getRequest(), redirectionUrl.getScheme(),
redirectionUrl.getHost(), redirectionUrl.getPort(), uriBuilderProvider.get());
if (command.incrementRedirectCount() < retryCountLimit && hostHeader != null) {
URI redirectionUrl = URI.create(hostHeader);
// if you are sent the same uri, assume there's a transient problem and retry.
if (redirectionUrl.equals(command.getRequest().getEndpoint()))
return backoffHandler.shouldRetryRequest(command, response);
UriBuilder builder = uriBuilderProvider.get().uri(command.getRequest().getEndpoint());
assert redirectionUrl.getPath() != null : "no path in redirect header from: " + response;
builder.replacePath(redirectionUrl.getPath());
if (redirectionUrl.getScheme() != null)
builder.scheme(redirectionUrl.getScheme());
if (redirectionUrl.getHost() != null) {
builder.host(redirectionUrl.getHost());
if (command.getRequest().getFirstHeaderOrNull(HOST) != null)
command.getRequest().getHeaders().replaceValues(HOST, singletonList(redirectionUrl.getHost()));
}
if (redirectionUrl.getPort() != command.getRequest().getEndpoint().getPort())
builder.port(redirectionUrl.getPort());
if (redirectionUrl.getQuery() != null)
builder.replaceQuery(redirectionUrl.getQuery());
command.getRequest().setEndpoint(builder.build());
return true;
} else {
return false;
}
}
}
}

View File

@ -0,0 +1,171 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.
* ====================================================================
*/
package org.jclouds.http.handlers;
import static org.easymock.EasyMock.expect;
import static org.easymock.classextension.EasyMock.createMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.verify;
import static org.testng.Assert.assertEquals;
import java.net.URI;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.rest.BaseRestClientTest.MockModule;
import org.jclouds.rest.config.RestModule;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMultimap;
import com.google.inject.Guice;
/**
* Tests behavior of {@code RedirectionRetryHandler}
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "http.RedirectionRetryHandlerTest")
public class RedirectionRetryHandlerTest {
@Test
public void test302DoesNotRetry() {
HttpCommand command = createMock(HttpCommand.class);
HttpResponse response = new HttpResponse(302, "HTTP/1.1 302 Found", null);
expect(command.incrementRedirectCount()).andReturn(0);
replay(command);
RedirectionRetryHandler retry = Guice.createInjector(new MockModule(), new RestModule()).getInstance(
RedirectionRetryHandler.class);
assert !retry.shouldRetryRequest(command, response);
verify(command);
}
@Test
public void test302DoesNotRetryAfterLimit() {
HttpCommand command = createMock(HttpCommand.class);
HttpResponse response = new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(
HttpHeaders.LOCATION, "/api/v0.8b-ext2.5/Error.aspx?aspxerrorpath=/api/v0.8b-ext2.5/org.svc/1906645"));
expect(command.incrementRedirectCount()).andReturn(5);
replay(command);
RedirectionRetryHandler retry = Guice.createInjector(new MockModule(), new RestModule()).getInstance(
RedirectionRetryHandler.class);
assert !retry.shouldRetryRequest(command, response);
verify(command);
}
@Test
public void test302WithPathOnlyHeader() {
verifyRedirectRoutes(
new HttpRequest("GET",
URI.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"/api/v0.8b-ext2.5/Error.aspx?aspxerrorpath=/api/v0.8b-ext2.5/org.svc/1906645")),
new HttpRequest(
"GET",
URI.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/Error.aspx?aspxerrorpath=/api/v0.8b-ext2.5/org.svc/1906645")));
}
@Test
public void test302ToHttps() {
verifyRedirectRoutes(
new HttpRequest("GET",
URI.create("http://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),//
new HttpRequest("GET", URI
.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")));
}
@Test
public void test302ToDifferentPort() {
verifyRedirectRoutes(
new HttpRequest("GET",
URI.create("http://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"http://services.enterprisecloud.terremark.com:3030/api/v0.8b-ext2.5/org/1906645")),//
new HttpRequest("GET", URI
.create("http://services.enterprisecloud.terremark.com:3030/api/v0.8b-ext2.5/org/1906645")));
}
@Test
public void test302WithHeader() {
verifyRedirectRoutes(
new HttpRequest("GET",
URI.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")), new HttpRequest(
"GET", URI.create("https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")));
}
@Test
public void test302WithHeaderReplacesHostHeader() {
verifyRedirectRoutes(
new HttpRequest("GET",
URI.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645"),
ImmutableMultimap.of(HttpHeaders.HOST, "services.enterprisecloud.terremark.com")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),//
new HttpRequest("GET", URI
.create("https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645"),
ImmutableMultimap.of(HttpHeaders.HOST, "services1.enterprisecloud.terremark.com")));
}
protected void verifyRedirectRoutes(HttpRequest request, HttpResponse response, HttpRequest expected) {
HttpCommand command = createMock(HttpCommand.class);
expect(command.incrementRedirectCount()).andReturn(0);
expect(command.getRequest()).andReturn(request).atLeastOnce();
replay(command);
RedirectionRetryHandler retry = Guice.createInjector(new MockModule(), new RestModule()).getInstance(
RedirectionRetryHandler.class);
assert retry.shouldRetryRequest(command, response);
assertEquals(command.getRequest(), expected);
verify(command);
}
}

View File

@ -29,12 +29,13 @@ import static org.testng.Assert.assertNull;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.concurrent.ExecutorService;
import javax.annotation.Nullable;
import org.jclouds.Constants;
import org.jclouds.concurrent.MoreExecutors;
import org.jclouds.concurrent.config.ConfiguresExecutorService;
import org.jclouds.concurrent.config.ExecutorServiceModule;
import org.jclouds.crypto.Crypto;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.http.HttpRequest;
@ -47,6 +48,7 @@ import org.jclouds.rest.internal.RestAnnotationProcessor;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.name.Names;
public abstract class BaseRestClientTest {
@ -69,7 +71,8 @@ public abstract class BaseRestClientTest {
@Override
protected void configure() {
install(new ExecutorServiceModule(MoreExecutors.sameThreadExecutor(), MoreExecutors.sameThreadExecutor()));
bind(ExecutorService.class).annotatedWith(Names.named(Constants.PROPERTY_USER_THREADS)).toInstance(MoreExecutors.sameThreadExecutor());
bind(ExecutorService.class).annotatedWith(Names.named(Constants.PROPERTY_IO_WORKER_THREADS)).toInstance(MoreExecutors.sameThreadExecutor());
bind(TransformingHttpCommandExecutorService.class).toInstance(mock);
}
}