mirror of https://github.com/apache/jclouds.git
Issue 380: handle redirects with relative paths
This commit is contained in:
parent
08b8bb1f00
commit
5b27c07eaf
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue