Issue 821:retry on close_notify SSLException

This commit is contained in:
Adrian Cole 2012-01-30 16:10:01 -08:00
parent 7b17255d95
commit 32d4dbac8a
2 changed files with 52 additions and 28 deletions

View File

@ -18,23 +18,7 @@
*/ */
package org.jclouds.http.internal; package org.jclouds.http.internal;
import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.io.NullOutputStream;
import static com.google.common.io.ByteStreams.copy;
import static org.jclouds.http.HttpUtils.checkRequestHasContentLengthOrChunkedEncoding;
import static org.jclouds.http.HttpUtils.wirePayloadIfEnabled;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.net.ssl.SSLException;
import org.jclouds.Constants; import org.jclouds.Constants;
import org.jclouds.http.HttpCommand; import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpCommandExecutorService; import org.jclouds.http.HttpCommandExecutorService;
@ -47,10 +31,22 @@ import org.jclouds.http.IOExceptionRetryHandler;
import org.jclouds.http.handlers.DelegatingErrorHandler; import org.jclouds.http.handlers.DelegatingErrorHandler;
import org.jclouds.http.handlers.DelegatingRetryHandler; import org.jclouds.http.handlers.DelegatingRetryHandler;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.util.Throwables2; import org.jclouds.util.Throwables2;
import com.google.common.io.NullOutputStream; import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.io.ByteStreams.copy;
import static org.jclouds.http.HttpUtils.checkRequestHasContentLengthOrChunkedEncoding;
import static org.jclouds.http.HttpUtils.wirePayloadIfEnabled;
/** /**
* *
@ -171,18 +167,13 @@ public abstract class BaseHttpCommandExecutorService<Q> implements HttpCommandEx
} }
} catch (Exception e) { } catch (Exception e) {
IOException ioe = Throwables2.getFirstThrowableOfType(e, IOException.class); IOException ioe = Throwables2.getFirstThrowableOfType(e, IOException.class);
if (ioe != null) { if (ioe != null && ioRetryHandler.shouldRetryRequest(command, ioe)) {
if (ioe instanceof SSLException) { continue;
command.setException(new AuthorizationException(e.getMessage() + " connecting to "
+ command.getCurrentRequest().getRequestLine(), e));
break;
} else if (ioRetryHandler.shouldRetryRequest(command, ioe)) {
continue;
}
} }
command.setException(new HttpResponseException(e.getMessage() + " connecting to " command.setException(new HttpResponseException(e.getMessage() + " connecting to "
+ command.getCurrentRequest().getRequestLine(), command, null, e)); + command.getCurrentRequest().getRequestLine(), command, null, e));
break; break;
} finally { } finally {
cleanup(nativeRequest); cleanup(nativeRequest);
} }

View File

@ -18,14 +18,20 @@
*/ */
package org.jclouds.http; package org.jclouds.http;
import static com.google.common.base.Throwables.propagate;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import java.net.URI; import java.net.URI;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLException;
import org.jclouds.rest.BaseRestClientExpectTest; import org.jclouds.rest.BaseRestClientExpectTest;
import org.jclouds.rest.BaseRestClientExpectTest.RegisterContext; import org.jclouds.rest.BaseRestClientExpectTest.RegisterContext;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.base.Function;
/** /**
* *
* Allows us to test a client via its side effects. * Allows us to test a client via its side effects.
@ -36,7 +42,34 @@ import org.testng.annotations.Test;
// only needed as IntegrationTestClient is not registered in rest.properties // only needed as IntegrationTestClient is not registered in rest.properties
@RegisterContext(sync = IntegrationTestClient.class, async = IntegrationTestAsyncClient.class) @RegisterContext(sync = IntegrationTestClient.class, async = IntegrationTestAsyncClient.class)
public class IntegrationTestClientExpectTest extends BaseRestClientExpectTest<IntegrationTestClient> { public class IntegrationTestClientExpectTest extends BaseRestClientExpectTest<IntegrationTestClient> {
public void testRetryOnSSLExceptionClose() {
// keeps track of request count
final AtomicInteger counter = new AtomicInteger(0);
IntegrationTestClient client = createClient(new Function<HttpRequest, HttpResponse>() {
@Override
public HttpResponse apply(HttpRequest input) {
// on first request, throw an SSL close_notify exception
if (counter.getAndIncrement() == 0)
throw propagate(new SSLException("Received close_notify during handshake"));
// on other requests, just validate and return 200
assertEquals(renderRequest(input), renderRequest(HttpRequest.builder().method("HEAD").endpoint(
URI.create("http://mock/objects/rabbit")).build()));
return HttpResponse.builder().statusCode(200).build();
}
});
// try three times, first should fail quietly due to retry logic
for (int i = 0; i < 3; i++)
assertEquals(client.exists("rabbit"), true);
// should be an extra request relating to the retry
assertEquals(counter.get(), 4);
}
public void testWhenResponseIs2xxExistsReturnsTrue() { public void testWhenResponseIs2xxExistsReturnsTrue() {
IntegrationTestClient client = requestSendsResponse(HttpRequest.builder().method("HEAD").endpoint( IntegrationTestClient client = requestSendsResponse(HttpRequest.builder().method("HEAD").endpoint(