diff --git a/core/sdk-core/pom.xml b/core/sdk-core/pom.xml index 3fb0d3ff66a3..390f91f78449 100644 --- a/core/sdk-core/pom.xml +++ b/core/sdk-core/pom.xml @@ -106,12 +106,6 @@ reactive-streams - - software.amazon.awssdk - apache-client - ${awsjavasdk.version} - test - org.junit.jupiter junit-jupiter @@ -213,12 +207,6 @@ hamcrest-core test - - software.amazon.awssdk - netty-nio-client - ${awsjavasdk.version} - test - io.reactivex.rxjava2 rxjava diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/AmazonHttpClientWireMockTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/AmazonHttpClientWireMockTest.java deleted file mode 100644 index a43e20d74caa..000000000000 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/AmazonHttpClientWireMockTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.http; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.any; -import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.matching; -import static com.github.tomakehurst.wiremock.client.WireMock.optionsRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static com.github.tomakehurst.wiremock.client.WireMock.verify; -import static software.amazon.awssdk.core.internal.http.timers.ClientExecutionAndRequestTimerTestUtils.executionContext; -import static software.amazon.awssdk.core.internal.util.ResponseHandlerTestUtils.combinedSyncResponseHandler; - -import org.junit.Before; -import org.junit.Test; -import software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient; -import software.amazon.awssdk.core.internal.http.response.NullErrorResponseHandler; -import software.amazon.awssdk.http.SdkHttpFullRequest; -import software.amazon.awssdk.http.SdkHttpMethod; -import utils.HttpTestUtils; -import utils.http.WireMockTestBase; - -public class AmazonHttpClientWireMockTest extends WireMockTestBase { - private static final String OPERATION = "/some-operation"; - private static final String HEADER = "Some-Header"; - private static final String CONFIG_HEADER_VALUE = "client config header value"; - private static final String REQUEST_HEADER_VALUE = "request header value"; - - @Before - public void setUp() { - stubFor(any(urlPathEqualTo(OPERATION)).willReturn(aResponse())); - } - - @Test - public void headersSpecifiedInClientConfigurationArePutOnRequest() { - SdkHttpFullRequest request = newGetRequest(OPERATION).build(); - - AmazonSyncHttpClient sut = createClient(HEADER, CONFIG_HEADER_VALUE); - sendRequest(request, sut); - - verify(getRequestedFor(urlPathEqualTo(OPERATION)).withHeader(HEADER, matching(CONFIG_HEADER_VALUE))); - } - - @Test - public void headersOnRequestsWinOverClientConfigurationHeaders() { - SdkHttpFullRequest request = newGetRequest(OPERATION) - .putHeader(HEADER, REQUEST_HEADER_VALUE) - .build(); - - AmazonSyncHttpClient sut = createClient(HEADER, CONFIG_HEADER_VALUE); - sendRequest(request, sut); - - verify(getRequestedFor(urlPathEqualTo(OPERATION)).withHeader(HEADER, matching(REQUEST_HEADER_VALUE))); - } - - @Test - public void canHandleOptionsRequest() { - SdkHttpFullRequest request = newRequest(OPERATION) - .method(SdkHttpMethod.OPTIONS) - .build(); - - AmazonSyncHttpClient sut = HttpTestUtils.testAmazonHttpClient(); - sendRequest(request, sut); - - verify(optionsRequestedFor(urlPathEqualTo(OPERATION))); - } - - private void sendRequest(SdkHttpFullRequest request, AmazonSyncHttpClient sut) { - sut.requestExecutionBuilder() - .request(request) - .originalRequest(NoopTestRequest.builder().build()) - .executionContext(executionContext(request)) - .execute(combinedSyncResponseHandler(null, new NullErrorResponseHandler())); - } - - private AmazonSyncHttpClient createClient(String headerName, String headerValue) { - return HttpTestUtils.testClientBuilder().additionalHeader(headerName, headerValue).build(); - } -} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/ContentStreamProviderWireMockTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/ContentStreamProviderWireMockTest.java deleted file mode 100644 index c544254dfb38..000000000000 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/ContentStreamProviderWireMockTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.http; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.any; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; -import static software.amazon.awssdk.core.internal.http.timers.ClientExecutionAndRequestTimerTestUtils.executionContext; -import static software.amazon.awssdk.core.internal.util.ResponseHandlerTestUtils.combinedSyncResponseHandler; -import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import org.junit.Test; -import software.amazon.awssdk.core.exception.SdkServiceException; -import software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient; -import software.amazon.awssdk.core.internal.http.response.NullErrorResponseHandler; -import software.amazon.awssdk.core.io.SdkFilterInputStream; -import software.amazon.awssdk.http.ContentStreamProvider; -import software.amazon.awssdk.http.SdkHttpFullRequest; -import software.amazon.awssdk.http.SdkHttpMethod; -import utils.HttpTestUtils; -import utils.http.WireMockTestBase; - -/** - * WireMock tests related to {@link ContentStreamProvider} usage. - */ -public class ContentStreamProviderWireMockTest extends WireMockTestBase { - private static final String OPERATION = "/some-operation"; - - @Test - public void closesAllCreatedInputStreamsFromProvider() { - stubFor(any(urlPathEqualTo(OPERATION)).willReturn(aResponse().withStatus(500))); - - TestContentStreamProvider provider = new TestContentStreamProvider(); - SdkHttpFullRequest request = newRequest(OPERATION) - .contentStreamProvider(provider) - .method(SdkHttpMethod.PUT) - .build(); - - AmazonSyncHttpClient testClient = HttpTestUtils.testAmazonHttpClient(); - try { - sendRequest(request, testClient); - fail("Should have thrown SdkServiceException"); - } catch (SdkServiceException ignored) { - } - - // The test client uses the default retry policy so there should be 4 - // total attempts and an equal number created streams - assertThat(provider.getCreatedStreams().size()).isEqualTo(4); - for (CloseTrackingInputStream is : provider.getCreatedStreams()) { - assertThat(is.isClosed()).isTrue(); - } - } - - private void sendRequest(SdkHttpFullRequest request, AmazonSyncHttpClient sut) { - sut.requestExecutionBuilder() - .request(request) - .originalRequest(NoopTestRequest.builder().build()) - .executionContext(executionContext(request)) - .execute(combinedSyncResponseHandler(null, new NullErrorResponseHandler())); - } - - private static class TestContentStreamProvider implements ContentStreamProvider { - private static final byte[] CONTENT_BYTES = "Hello".getBytes(StandardCharsets.UTF_8); - private List createdStreams = new ArrayList<>(); - - @Override - public InputStream newStream() { - closeCurrentStream(); - CloseTrackingInputStream s = newContentStream(); - createdStreams.add(s); - return s; - } - - List getCreatedStreams() { - return createdStreams; - } - - private CloseTrackingInputStream newContentStream() { - return new CloseTrackingInputStream(new ByteArrayInputStream(CONTENT_BYTES)); - } - - private void closeCurrentStream() { - if (createdStreams.isEmpty()) { - return; - } - invokeSafely(() -> createdStreams.get(createdStreams.size() - 1).close()); - } - } - - private static class CloseTrackingInputStream extends SdkFilterInputStream { - private boolean isClosed = false; - - CloseTrackingInputStream(InputStream in) { - super(in); - } - - @Override - public void close() throws IOException { - super.close(); - isClosed = true; - } - - boolean isClosed() { - return isClosed; - } - } -} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/SdkTransactionIdInHeaderTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/SdkTransactionIdInHeaderTest.java deleted file mode 100644 index e2977ccea4ba..000000000000 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/SdkTransactionIdInHeaderTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.http; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.findAll; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; -import static software.amazon.awssdk.core.internal.util.ResponseHandlerTestUtils.combinedSyncResponseHandler; - -import com.github.tomakehurst.wiremock.verification.LoggedRequest; -import org.junit.Test; -import software.amazon.awssdk.core.exception.SdkServiceException; -import software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient; -import software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyTransactionIdStage; -import software.amazon.awssdk.core.internal.http.timers.ClientExecutionAndRequestTimerTestUtils; -import software.amazon.awssdk.http.SdkHttpFullRequest; -import utils.HttpTestUtils; -import utils.http.WireMockTestBase; - -public class SdkTransactionIdInHeaderTest extends WireMockTestBase { - - private static final String RESOURCE_PATH = "/transaction-id/"; - - @Test - public void retriedRequest_HasSameTransactionIdForAllRetries() throws Exception { - stubFor(get(urlEqualTo(RESOURCE_PATH)).willReturn(aResponse().withStatus(500))); - executeRequest(); - assertTransactionIdIsUnchangedAcrossRetries(); - } - - private void assertTransactionIdIsUnchangedAcrossRetries() { - String previousTransactionId = null; - for (LoggedRequest request : findAll(getRequestedFor(urlEqualTo(RESOURCE_PATH)))) { - final String currentTransactionId = request.getHeader(ApplyTransactionIdStage.HEADER_SDK_TRANSACTION_ID); - // Transaction ID should always be set - assertNotNull(currentTransactionId); - // Transaction ID should be the same across retries - if (previousTransactionId != null) { - assertEquals(previousTransactionId, currentTransactionId); - } - previousTransactionId = currentTransactionId; - } - } - - private void executeRequest() throws Exception { - AmazonSyncHttpClient httpClient = HttpTestUtils.testAmazonHttpClient(); - try { - SdkHttpFullRequest request = newGetRequest(RESOURCE_PATH).build(); - httpClient.requestExecutionBuilder() - .request(request) - .originalRequest(NoopTestRequest.builder().build()) - .executionContext(ClientExecutionAndRequestTimerTestUtils.executionContext(request)) - .execute(combinedSyncResponseHandler(null, stubErrorHandler())); - fail("Expected exception"); - } catch (SdkServiceException expected) { - // Ignored or expected. - } - } - -} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/server/MockServer.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/server/MockServer.java deleted file mode 100644 index 6b9dfe538d52..000000000000 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/server/MockServer.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.http.server; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.net.URI; -import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.ProtocolVersion; -import org.apache.http.entity.BasicHttpEntity; -import org.apache.http.message.BasicHttpResponse; -import org.apache.http.message.BasicStatusLine; -import software.amazon.awssdk.http.SdkHttpFullRequest; -import software.amazon.awssdk.utils.IoUtils; -import software.amazon.awssdk.utils.StringInputStream; - -/** - * MockServer implementation with several different configurable behaviors - */ -public class MockServer { - - private final ServerBehaviorStrategy serverBehaviorStrategy; - /** - * The server socket which the test service will listen to. - */ - private ServerSocket serverSocket; - private Thread listenerThread; - - public MockServer(final ServerBehaviorStrategy serverBehaviorStrategy) { - this.serverBehaviorStrategy = serverBehaviorStrategy; - } - - public static MockServer createMockServer(ServerBehavior serverBehavior) { - switch (serverBehavior) { - case UNRESPONSIVE: - return new MockServer(new UnresponsiveServerBehavior()); - case OVERLOADED: - return new MockServer(new OverloadedServerBehavior()); - default: - throw new IllegalArgumentException("Unsupported implementation for server issue: " + serverBehavior); - } - } - - public void startServer() { - try { - serverSocket = new ServerSocket(0); // auto-assign a port at localhost - System.out.println("Listening on port " + serverSocket.getLocalPort()); - } catch (IOException e) { - throw new RuntimeException("Unable to start the server socker.", e); - } - - listenerThread = new MockServerListenerThread(serverSocket, serverBehaviorStrategy); - listenerThread.setDaemon(true); - listenerThread.start(); - } - - public void stopServer() { - listenerThread.interrupt(); - try { - listenerThread.join(10 * 1000); - } catch (InterruptedException e1) { - System.err.println("The listener thread didn't terminate " + "after waiting for 10 seconds."); - } - - if (serverSocket != null) { - try { - serverSocket.close(); - } catch (IOException e) { - throw new RuntimeException("Unable to stop the server socket.", e); - } - } - } - - public int getPort() { - return serverSocket.getLocalPort(); - } - - public SdkHttpFullRequest.Builder configureHttpsEndpoint(SdkHttpFullRequest.Builder request) { - return request.uri(URI.create("https://localhost")) - .port(getPort()); - } - - public SdkHttpFullRequest.Builder configureHttpEndpoint(SdkHttpFullRequest.Builder request) { - return request.uri(URI.create("http://localhost")) - .port(getPort()); - } - - public enum ServerBehavior { - UNRESPONSIVE, - OVERLOADED, - DUMMY_RESPONSE; - } - - public interface ServerBehaviorStrategy { - void runServer(ServerSocket serverSocket); - } - - private static class MockServerListenerThread extends Thread { - /** The server socket which this thread listens and responds to. */ - private final ServerSocket serverSocket; - private final ServerBehaviorStrategy behaviorStrategy; - - public MockServerListenerThread(ServerSocket serverSocket, ServerBehaviorStrategy behaviorStrategy) { - super(behaviorStrategy.getClass().getName()); - this.serverSocket = serverSocket; - this.behaviorStrategy = behaviorStrategy; - setDaemon(true); - } - - @Override - public void run() { - this.behaviorStrategy.runServer(serverSocket); - } - } - - /** - * A daemon thread which runs a simple server that listens to a specific server socket. Whenever - * a connection is created, the server simply keeps holding the connection open while - * periodically writing data. The test client talking to this server is expected to timeout - * appropriately, instead of hanging and waiting for the response forever. - */ - public static class OverloadedServerBehavior implements ServerBehaviorStrategy { - - @Override - public void runServer(ServerSocket serverSocket) { - try { - while (true) { - Socket socket = null; - try { - socket = serverSocket.accept(); - try (DataOutputStream out = new DataOutputStream(socket.getOutputStream())) { - out.writeBytes("HTTP/1.1 200 OK\r\n"); - out.writeBytes("Content-Type: text/html\r\n"); - out.writeBytes("Content-Length: 500\r\n\r\n"); - out.writeBytes("

Hello."); - while (true) { - Thread.sleep(1 * 1000); - out.writeBytes("Hi."); - } - } - } catch (SocketException se) { - // Ignored or expected. - } finally { - if (socket != null) { - socket.close(); - } - } - } - } catch (IOException e) { - throw new RuntimeException("Error when waiting for new socket connection.", e); - } catch (InterruptedException e) { - System.err.println("Socket listener thread interrupted. Terminating the thread..."); - return; - } - } - } - - /** - * A daemon thread which runs a simple server that listens to a specific server socket. Whenever - * a connection is created, the server simply keeps holding the connection open and no byte will - * be written to the socket. The test client talking to this server is expected to timeout - * appropriately, instead of hanging and waiting for the response forever. - */ - public static class UnresponsiveServerBehavior implements ServerBehaviorStrategy { - @Override - public void runServer(ServerSocket serverSocket) { - Socket socket = null; - try { - socket = serverSocket.accept(); - System.out.println("Socket created on port " + socket.getLocalPort()); - while (true) { - System.out.println("I don't want to talk."); - Thread.sleep(10 * 1000); - } - } catch (IOException e) { - throw new RuntimeException("Error when waiting for new socket connection.", e); - } catch (InterruptedException e) { - System.err.println("Socket listener thread interrupted. Terminating the thread..."); - return; - } finally { - try { - if (socket != null) { - socket.close(); - } - } catch (IOException e) { - throw new RuntimeException("Fail to close the socket", e); - } - } - } - } - - public static class DummyResponseServerBehavior implements ServerBehaviorStrategy { - - private final HttpResponse response; - private String content; - - public DummyResponseServerBehavior(HttpResponse response) { - this.response = response; - try { - this.content = IoUtils.toUtf8String(response.getEntity().getContent()); - } catch (Exception e) { - // Ignored or expected. - } - } - - public static DummyResponseServerBehavior build(int statusCode, String statusMessage, String content) { - HttpResponse response = new BasicHttpResponse( - new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), statusCode, statusMessage)); - setEntity(response, content); - response.addHeader("Content-Length", String.valueOf(content.getBytes().length)); - response.addHeader("Connection", "close"); - return new DummyResponseServerBehavior(response); - } - - private static void setEntity(HttpResponse response, String content) { - BasicHttpEntity entity = new BasicHttpEntity(); - entity.setContent(new StringInputStream(content)); - response.setEntity(entity); - } - - @Override - public void runServer(ServerSocket serverSocket) { - try { - while (true) { - Socket socket = null; - try { - socket = serverSocket.accept(); - try (DataOutputStream out = new DataOutputStream(socket.getOutputStream())) { - StringBuilder builder = new StringBuilder(); - builder.append(response.getStatusLine().toString() + "\r\n"); - for (Header header : response.getAllHeaders()) { - builder.append(header.getName() + ":" + header.getValue() + "\r\n"); - } - builder.append("\r\n"); - builder.append(content); - System.out.println(builder.toString()); - out.writeBytes(builder.toString()); - } - } catch (SocketException se) { - // Ignored or expected. - } finally { - if (socket != null) { - socket.close(); - } - } - } - } catch (IOException e) { - throw new RuntimeException("Error when waiting for new socket connection.", e); - } - } - - } - -} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/loader/ClasspathSdkHttpServiceProviderTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/loader/ClasspathSdkHttpServiceProviderTest.java index 03e25c388625..b39bfbc2b093 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/loader/ClasspathSdkHttpServiceProviderTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/loader/ClasspathSdkHttpServiceProviderTest.java @@ -19,24 +19,34 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static software.amazon.awssdk.core.internal.http.loader.ClasspathSdkHttpServiceProvider.ASYNC_HTTP_SERVICES_PRIORITY; -import static software.amazon.awssdk.core.internal.http.loader.ClasspathSdkHttpServiceProvider.SYNC_HTTP_SERVICES_PRIORITY; import java.util.Arrays; import java.util.Iterator; +import java.util.Map; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.SdkHttpService; -import software.amazon.awssdk.http.apache.ApacheSdkHttpService; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; import software.amazon.awssdk.http.async.SdkAsyncHttpService; -import software.amazon.awssdk.http.nio.netty.NettySdkAsyncHttpService; +import software.amazon.awssdk.utils.ImmutableMap; @RunWith(MockitoJUnitRunner.class) public class ClasspathSdkHttpServiceProviderTest { + private static final Map TEST_SYNC_PRIORITY = + ImmutableMap.builder() + .put(HighPrioritySyncHttpService.class.getName(), 1) + .build(); + + private static final Map TEST_ASYNC_PRIORITY = + ImmutableMap.builder() + .put(HighPriorityAsyncHttpService.class.getName(), 1) + .build(); + @Mock private SdkServiceLoader serviceLoader; @@ -48,11 +58,11 @@ public class ClasspathSdkHttpServiceProviderTest { public void setup() { provider = new ClasspathSdkHttpServiceProvider<>(serviceLoader, SdkHttpService.class, - SYNC_HTTP_SERVICES_PRIORITY); + TEST_SYNC_PRIORITY); asyncProvider = new ClasspathSdkHttpServiceProvider<>(serviceLoader, SdkAsyncHttpService.class, - ASYNC_HTTP_SERVICES_PRIORITY); + TEST_ASYNC_PRIORITY); } @Test @@ -71,12 +81,12 @@ public void oneImplementationsFound_ReturnsFulfilledOptional() { @Test public void multipleSyncImplementationsFound_ReturnHighestPriority() { - ApacheSdkHttpService apacheSdkHttpService = new ApacheSdkHttpService(); + HighPrioritySyncHttpService highPrioritySyncHttpService = new HighPrioritySyncHttpService(); SdkHttpService mock = mock(SdkHttpService.class); when(serviceLoader.loadServices(SdkHttpService.class)) - .thenReturn(iteratorOf(mock, apacheSdkHttpService)); - assertThat(provider.loadService()).contains(apacheSdkHttpService); + .thenReturn(iteratorOf(mock, highPrioritySyncHttpService)); + assertThat(provider.loadService()).contains(highPrioritySyncHttpService); SdkHttpService mock1 = mock(SdkHttpService.class); SdkHttpService mock2 = mock(SdkHttpService.class); @@ -87,12 +97,12 @@ public void multipleSyncImplementationsFound_ReturnHighestPriority() { @Test public void multipleAsyncImplementationsFound_ReturnHighestPriority() { - NettySdkAsyncHttpService netty = new NettySdkAsyncHttpService(); + HighPriorityAsyncHttpService highPriorityAsyncHttpService = new HighPriorityAsyncHttpService(); SdkAsyncHttpService mock = mock(SdkAsyncHttpService.class); when(serviceLoader.loadServices(SdkAsyncHttpService.class)) - .thenReturn(iteratorOf(mock, netty)); - assertThat(asyncProvider.loadService()).contains(netty); + .thenReturn(iteratorOf(mock, highPriorityAsyncHttpService)); + assertThat(asyncProvider.loadService()).contains(highPriorityAsyncHttpService); SdkAsyncHttpService mock1 = mock(SdkAsyncHttpService.class); SdkAsyncHttpService mock2 = mock(SdkAsyncHttpService.class); @@ -105,4 +115,26 @@ public void multipleAsyncImplementationsFound_ReturnHighestPriority() { private final Iterator iteratorOf(T... items) { return Arrays.asList(items).iterator(); } + + /** + * Test-local sync HTTP service whose class name is registered in {@link #TEST_SYNC_PRIORITY}. The provider only ever + * inspects the class name, so the factory method is never invoked. + */ + private static final class HighPrioritySyncHttpService implements SdkHttpService { + @Override + public SdkHttpClient.Builder createHttpClientBuilder() { + throw new UnsupportedOperationException(); + } + } + + /** + * Test-local async HTTP service whose class name is registered in {@link #TEST_ASYNC_PRIORITY}. The provider only ever + * inspects the class name, so the factory method is never invoked. + */ + private static final class HighPriorityAsyncHttpService implements SdkAsyncHttpService { + @Override + public SdkAsyncHttpClient.Builder createAsyncHttpClientFactory() { + throw new UnsupportedOperationException(); + } + } } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/AsyncHttpClientApiCallTimeoutTests.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/AsyncHttpClientApiCallTimeoutTests.java index 8f1ce6a9f437..a04c0fcf1d9f 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/AsyncHttpClientApiCallTimeoutTests.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/AsyncHttpClientApiCallTimeoutTests.java @@ -15,10 +15,6 @@ package software.amazon.awssdk.core.internal.http.timers; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static software.amazon.awssdk.core.internal.http.timers.TimeoutTestConstants.API_CALL_TIMEOUT; import static software.amazon.awssdk.core.internal.http.timers.TimeoutTestConstants.SLOW_REQUEST_HANDLER_TIMEOUT; @@ -27,16 +23,12 @@ import static software.amazon.awssdk.core.internal.util.AsyncResponseHandlerTestUtils.superSlowResponseHandler; import static utils.HttpTestUtils.testAsyncClientBuilder; -import com.github.tomakehurst.wiremock.junit.WireMockRule; import java.io.ByteArrayInputStream; -import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.concurrent.CompletableFuture; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import software.amazon.awssdk.core.exception.ApiCallAttemptTimeoutException; import software.amazon.awssdk.core.exception.ApiCallTimeoutException; import software.amazon.awssdk.core.exception.SdkServiceException; import software.amazon.awssdk.core.http.ExecutionContext; @@ -51,13 +43,11 @@ import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.retries.DefaultRetryStrategy; +import utils.StubSdkAsyncHttpClient; import utils.ValidSdkObjects; public class AsyncHttpClientApiCallTimeoutTests { - @Rule - public WireMockRule wireMock = new WireMockRule(0); - private AmazonAsyncHttpClient httpClient; @Before @@ -65,36 +55,44 @@ public void setup() { httpClient = testAsyncClientBuilder() .retryStrategy(DefaultRetryStrategy.doNotRetry()) .apiCallTimeout(API_CALL_TIMEOUT) + .asyncHttpClient(StubSdkAsyncHttpClient.create()) .build(); } @Test public void errorResponse_SlowErrorResponseHandler_ThrowsApiCallTimeoutException() { - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(500).withBody("{}"))); + AmazonAsyncHttpClient errorClient = testAsyncClientBuilder() + .retryStrategy(DefaultRetryStrategy.doNotRetry()) + .apiCallTimeout(API_CALL_TIMEOUT) + .asyncHttpClient(StubSdkAsyncHttpClient.create(500)) + .build(); ExecutionContext executionContext = ClientExecutionAndRequestTimerTestUtils.executionContext(null); - CompletableFuture future = httpClient.requestExecutionBuilder() - .originalRequest(NoopTestRequest.builder().build()) - .executionContext(executionContext) - .request(generateRequest()) - .execute(combinedAsyncResponseHandler(noOpResponseHandler(), - superSlowResponseHandler(API_CALL_TIMEOUT.toMillis()))); + CompletableFuture future = errorClient.requestExecutionBuilder() + .originalRequest(NoopTestRequest.builder().build()) + .executionContext(executionContext) + .request(generateRequest()) + .execute(combinedAsyncResponseHandler(noOpResponseHandler(), + superSlowResponseHandler(API_CALL_TIMEOUT.toMillis()))); assertThatThrownBy(future::join).hasCauseInstanceOf(ApiCallTimeoutException.class); } @Test public void errorResponse_SlowAfterErrorRequestHandler_ThrowsApiCallTimeoutException() { - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(500).withBody("{}"))); + AmazonAsyncHttpClient errorClient = testAsyncClientBuilder() + .retryStrategy(DefaultRetryStrategy.doNotRetry()) + .apiCallTimeout(API_CALL_TIMEOUT) + .asyncHttpClient(StubSdkAsyncHttpClient.create(500)) + .build(); + ExecutionInterceptorChain interceptors = new ExecutionInterceptorChain( Collections.singletonList(new SlowExecutionInterceptor().onExecutionFailureWaitInSeconds(SLOW_REQUEST_HANDLER_TIMEOUT))); SdkHttpFullRequest request = generateRequest(); - InterceptorContext incerceptorContext = + InterceptorContext interceptorContext = InterceptorContext.builder() .request(NoopTestRequest.builder().build()) .httpRequest(request) @@ -104,25 +102,23 @@ public void errorResponse_SlowAfterErrorRequestHandler_ThrowsApiCallTimeoutExcep .signer(new NoOpSigner()) .interceptorChain(interceptors) .executionAttributes(new ExecutionAttributes()) - .interceptorContext(incerceptorContext) + .interceptorContext(interceptorContext) .metricCollector(MetricCollector.create("ApiCall")) .build(); CompletableFuture future = - httpClient.requestExecutionBuilder() - .originalRequest(NoopTestRequest.builder().build()) - .request(request) - .executionContext(executionContext) - .execute(combinedAsyncResponseHandler(noOpResponseHandler(), - noOpResponseHandler(SdkServiceException.builder().build()))); + errorClient.requestExecutionBuilder() + .originalRequest(NoopTestRequest.builder().build()) + .request(request) + .executionContext(executionContext) + .execute(combinedAsyncResponseHandler(noOpResponseHandler(), + noOpResponseHandler(SdkServiceException.builder().build()))); assertThatThrownBy(future::join).hasCauseInstanceOf(ApiCallTimeoutException.class); } @Test public void successfulResponse_SlowBeforeRequestRequestHandler_ThrowsApiCallTimeoutException() { - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(200).withBody("{}"))); ExecutionInterceptor interceptor = new SlowExecutionInterceptor().beforeTransmissionWaitInSeconds(SLOW_REQUEST_HANDLER_TIMEOUT); @@ -133,25 +129,10 @@ public void successfulResponse_SlowBeforeRequestRequestHandler_ThrowsApiCallTime @Test public void successfulResponse_SlowResponseHandler_ThrowsApiCallTimeoutException() { - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(200).withBody("{}"))); CompletableFuture future = requestBuilder().execute(superSlowResponseHandler(API_CALL_TIMEOUT.toMillis())); assertThatThrownBy(future::join).hasCauseInstanceOf(ApiCallTimeoutException.class); } - @Test - public void slowApiAttempt_ThrowsApiCallAttemptTimeoutException() { - httpClient = testAsyncClientBuilder() - .apiCallTimeout(API_CALL_TIMEOUT) - .apiCallAttemptTimeout(Duration.ofMillis(1)) - .build(); - - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(200).withBody("{}").withFixedDelay(1_000))); - CompletableFuture future = requestBuilder().execute(noOpResponseHandler()); - assertThatThrownBy(future::join).hasCauseInstanceOf(ApiCallAttemptTimeoutException.class); - } - private AmazonAsyncHttpClient.RequestExecutionBuilder requestBuilder() { return httpClient.requestExecutionBuilder() .request(generateRequest()) @@ -160,7 +141,7 @@ private AmazonAsyncHttpClient.RequestExecutionBuilder requestBuilder() { } private SdkHttpFullRequest generateRequest() { - return ValidSdkObjects.sdkHttpFullRequest(wireMock.port()) + return ValidSdkObjects.sdkHttpFullRequest() .host("localhost") .contentStreamProvider(() -> new ByteArrayInputStream("test".getBytes())).build(); } @@ -170,7 +151,7 @@ private ExecutionContext withInterceptors(ExecutionInterceptor... requestHandler ExecutionInterceptorChain interceptors = new ExecutionInterceptorChain(Arrays.asList(requestHandlers)); - InterceptorContext incerceptorContext = + InterceptorContext interceptorContext = InterceptorContext.builder() .request(NoopTestRequest.builder().build()) .httpRequest(generateRequest()) @@ -179,7 +160,7 @@ private ExecutionContext withInterceptors(ExecutionInterceptor... requestHandler .signer(new NoOpSigner()) .interceptorChain(interceptors) .executionAttributes(new ExecutionAttributes()) - .interceptorContext(incerceptorContext) + .interceptorContext(interceptorContext) .metricCollector(MetricCollector.create("ApiCall")) .build(); } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/ClientExecutionAndRequestTimerTestUtils.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/ClientExecutionAndRequestTimerTestUtils.java index b4923e2b363e..64f271a6963b 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/ClientExecutionAndRequestTimerTestUtils.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/ClientExecutionAndRequestTimerTestUtils.java @@ -107,7 +107,7 @@ public static void execute(AmazonSyncHttpClient httpClient, SdkHttpFullRequest r } public static ExecutionContext executionContext(SdkHttpFullRequest request) { - InterceptorContext incerceptorContext = + InterceptorContext interceptorContext = InterceptorContext.builder() .request(NoopTestRequest.builder().build()) .httpRequest(request) @@ -116,7 +116,7 @@ public static ExecutionContext executionContext(SdkHttpFullRequest request) { .signer(new NoOpSigner()) .interceptorChain(new ExecutionInterceptorChain(Collections.emptyList())) .executionAttributes(new ExecutionAttributes()) - .interceptorContext(incerceptorContext) + .interceptorContext(interceptorContext) .metricCollector(MetricCollector.create("ApiCall")) .build(); } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallAttemptTimeoutTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallAttemptTimeoutTest.java index 2dcf349495a6..308eb660328e 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallAttemptTimeoutTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallAttemptTimeoutTest.java @@ -15,10 +15,6 @@ package software.amazon.awssdk.core.internal.http.timers; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static software.amazon.awssdk.core.internal.http.timers.TimeoutTestConstants.API_CALL_TIMEOUT; import static software.amazon.awssdk.core.internal.http.timers.TimeoutTestConstants.SLOW_REQUEST_HANDLER_TIMEOUT; @@ -27,11 +23,9 @@ import static software.amazon.awssdk.core.internal.util.ResponseHandlerTestUtils.superSlowResponseHandler; import static utils.HttpTestUtils.testClientBuilder; -import com.github.tomakehurst.wiremock.junit.WireMockRule; import java.io.ByteArrayInputStream; import java.util.Arrays; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import software.amazon.awssdk.core.exception.ApiCallAttemptTimeoutException; import software.amazon.awssdk.core.http.ExecutionContext; @@ -46,14 +40,12 @@ import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.retries.DefaultRetryStrategy; +import utils.StubSdkHttpClient; import utils.ValidSdkObjects; public class HttpClientApiCallAttemptTimeoutTest { - @Rule - public WireMockRule wireMock = new WireMockRule(0); - private AmazonSyncHttpClient httpClient; @Before @@ -61,14 +53,12 @@ public void setup() { httpClient = testClientBuilder() .retryStrategy(DefaultRetryStrategy.doNotRetry()) .apiCallAttemptTimeout(API_CALL_TIMEOUT) + .httpClient(StubSdkHttpClient.create()) .build(); } @Test public void successfulResponse_SlowResponseHandler_ThrowsApiCallTimeoutException() { - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(200).withBody("{}"))); - assertThatThrownBy(() -> requestBuilder().execute(combinedSyncResponseHandler( superSlowResponseHandler(API_CALL_TIMEOUT.toMillis()), null))) .isInstanceOf(ApiCallAttemptTimeoutException.class); @@ -76,24 +66,25 @@ public void successfulResponse_SlowResponseHandler_ThrowsApiCallTimeoutException @Test public void errorResponse_SlowErrorResponseHandler_ThrowsApiCallTimeoutException() { - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(500).withBody("{}"))); + AmazonSyncHttpClient errorClient = testClientBuilder() + .retryStrategy(DefaultRetryStrategy.doNotRetry()) + .apiCallAttemptTimeout(API_CALL_TIMEOUT) + .httpClient(StubSdkHttpClient.create(500)) + .build(); ExecutionContext executionContext = ClientExecutionAndRequestTimerTestUtils.executionContext(null); - assertThatThrownBy(() -> httpClient.requestExecutionBuilder() - .originalRequest(NoopTestRequest.builder().build()) - .executionContext(executionContext) - .request(generateRequest()) - .execute(combinedSyncResponseHandler(null, - superSlowResponseHandler(API_CALL_TIMEOUT.toMillis())))) + assertThatThrownBy(() -> errorClient.requestExecutionBuilder() + .originalRequest(NoopTestRequest.builder().build()) + .executionContext(executionContext) + .request(generateRequest()) + .execute(combinedSyncResponseHandler(null, + superSlowResponseHandler(API_CALL_TIMEOUT.toMillis())))) .isInstanceOf(ApiCallAttemptTimeoutException.class); } @Test public void successfulResponse_SlowBeforeTransmissionExecutionInterceptor_ThrowsApiCallTimeoutException() { - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(200).withBody("{}"))); ExecutionInterceptor interceptor = new SlowExecutionInterceptor().beforeTransmissionWaitInSeconds(SLOW_REQUEST_HANDLER_TIMEOUT); @@ -104,8 +95,6 @@ public void successfulResponse_SlowBeforeTransmissionExecutionInterceptor_Throws @Test public void successfulResponse_SlowAfterResponseExecutionInterceptor_ThrowsApiCallTimeoutException() { - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(200).withBody("{}"))); ExecutionInterceptor interceptor = new SlowExecutionInterceptor().afterTransmissionWaitInSeconds(SLOW_REQUEST_HANDLER_TIMEOUT); assertThatThrownBy(() -> requestBuilder().executionContext(withInterceptors(interceptor)) @@ -121,7 +110,7 @@ private AmazonSyncHttpClient.RequestExecutionBuilder requestBuilder() { } private SdkHttpFullRequest generateRequest() { - return ValidSdkObjects.sdkHttpFullRequest(wireMock.port()) + return ValidSdkObjects.sdkHttpFullRequest() .host("localhost") .contentStreamProvider(() -> new ByteArrayInputStream("test".getBytes())).build(); } @@ -131,7 +120,7 @@ private ExecutionContext withInterceptors(ExecutionInterceptor... requestHandler ExecutionInterceptorChain interceptors = new ExecutionInterceptorChain(Arrays.asList(requestHandlers)); - InterceptorContext incerceptorContext = + InterceptorContext interceptorContext = InterceptorContext.builder() .request(NoopTestRequest.builder().build()) .httpRequest(generateRequest()) @@ -140,7 +129,7 @@ private ExecutionContext withInterceptors(ExecutionInterceptor... requestHandler .signer(new NoOpSigner()) .interceptorChain(interceptors) .executionAttributes(new ExecutionAttributes()) - .interceptorContext(incerceptorContext) + .interceptorContext(interceptorContext) .metricCollector(MetricCollector.create("ApiCall")) .build(); } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallTimeoutTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallTimeoutTest.java index 9a610485a4d9..fb5a8d20a700 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallTimeoutTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallTimeoutTest.java @@ -15,10 +15,6 @@ package software.amazon.awssdk.core.internal.http.timers; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static software.amazon.awssdk.core.internal.http.timers.TimeoutTestConstants.API_CALL_TIMEOUT; import static software.amazon.awssdk.core.internal.http.timers.TimeoutTestConstants.SLOW_REQUEST_HANDLER_TIMEOUT; @@ -27,11 +23,9 @@ import static software.amazon.awssdk.core.internal.util.ResponseHandlerTestUtils.superSlowResponseHandler; import static utils.HttpTestUtils.testClientBuilder; -import com.github.tomakehurst.wiremock.junit.WireMockRule; import java.io.ByteArrayInputStream; import java.util.Arrays; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import software.amazon.awssdk.core.exception.ApiCallTimeoutException; import software.amazon.awssdk.core.http.ExecutionContext; @@ -46,14 +40,12 @@ import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.retries.DefaultRetryStrategy; +import utils.StubSdkHttpClient; import utils.ValidSdkObjects; public class HttpClientApiCallTimeoutTest { - @Rule - public WireMockRule wireMock = new WireMockRule(0); - private AmazonSyncHttpClient httpClient; @Before @@ -61,14 +53,12 @@ public void setup() { httpClient = testClientBuilder() .retryStrategy(DefaultRetryStrategy.doNotRetry()) .apiCallTimeout(API_CALL_TIMEOUT) + .httpClient(StubSdkHttpClient.create()) .build(); } @Test public void successfulResponse_SlowResponseHandler_ThrowsApiCallTimeoutException() { - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(200).withBody("{}"))); - assertThatThrownBy(() -> requestBuilder().execute(combinedSyncResponseHandler( superSlowResponseHandler(API_CALL_TIMEOUT.toMillis() * 2), null))) .isInstanceOf(ApiCallTimeoutException.class); @@ -76,24 +66,25 @@ public void successfulResponse_SlowResponseHandler_ThrowsApiCallTimeoutException @Test public void errorResponse_SlowErrorResponseHandler_ThrowsApiCallTimeoutException() { - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(500).withBody("{}"))); + AmazonSyncHttpClient errorClient = testClientBuilder() + .retryStrategy(DefaultRetryStrategy.doNotRetry()) + .apiCallTimeout(API_CALL_TIMEOUT) + .httpClient(StubSdkHttpClient.create(500)) + .build(); ExecutionContext executionContext = ClientExecutionAndRequestTimerTestUtils.executionContext(null); - assertThatThrownBy(() -> httpClient.requestExecutionBuilder() - .originalRequest(NoopTestRequest.builder().build()) - .executionContext(executionContext) - .request(generateRequest()) - .execute(combinedSyncResponseHandler(noOpSyncResponseHandler(), - superSlowResponseHandler(API_CALL_TIMEOUT.toMillis())))) + assertThatThrownBy(() -> errorClient.requestExecutionBuilder() + .originalRequest(NoopTestRequest.builder().build()) + .executionContext(executionContext) + .request(generateRequest()) + .execute(combinedSyncResponseHandler(noOpSyncResponseHandler(), + superSlowResponseHandler(API_CALL_TIMEOUT.toMillis())))) .isInstanceOf(ApiCallTimeoutException.class); } @Test public void successfulResponse_SlowBeforeTransmissionExecutionInterceptor_ThrowsApiCallTimeoutException() { - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(200).withBody("{}"))); ExecutionInterceptor interceptor = new SlowExecutionInterceptor().beforeTransmissionWaitInSeconds(SLOW_REQUEST_HANDLER_TIMEOUT); @@ -104,8 +95,6 @@ public void successfulResponse_SlowBeforeTransmissionExecutionInterceptor_Throws @Test public void successfulResponse_SlowAfterResponseExecutionInterceptor_ThrowsApiCallTimeoutException() { - stubFor(get(anyUrl()) - .willReturn(aResponse().withStatus(200).withBody("{}"))); ExecutionInterceptor interceptor = new SlowExecutionInterceptor().afterTransmissionWaitInSeconds(SLOW_REQUEST_HANDLER_TIMEOUT); assertThatThrownBy(() -> requestBuilder().executionContext(withInterceptors(interceptor)) @@ -121,7 +110,7 @@ private AmazonSyncHttpClient.RequestExecutionBuilder requestBuilder() { } private SdkHttpFullRequest generateRequest() { - return ValidSdkObjects.sdkHttpFullRequest(wireMock.port()) + return ValidSdkObjects.sdkHttpFullRequest() .host("localhost") .contentStreamProvider(() -> new ByteArrayInputStream("test".getBytes())).build(); } @@ -131,7 +120,7 @@ private ExecutionContext withInterceptors(ExecutionInterceptor... requestHandler ExecutionInterceptorChain interceptors = new ExecutionInterceptorChain(Arrays.asList(requestHandlers)); - InterceptorContext incerceptorContext = + InterceptorContext interceptorContext = InterceptorContext.builder() .request(NoopTestRequest.builder().build()) .httpRequest(generateRequest()) @@ -140,7 +129,7 @@ private ExecutionContext withInterceptors(ExecutionInterceptor... requestHandler .signer(new NoOpSigner()) .interceptorChain(interceptors) .executionAttributes(new ExecutionAttributes()) - .interceptorContext(incerceptorContext) + .interceptorContext(interceptorContext) .metricCollector(MetricCollector.create("ApiCall")) .build(); } diff --git a/core/sdk-core/src/test/java/utils/HttpTestUtils.java b/core/sdk-core/src/test/java/utils/HttpTestUtils.java index 127584c67e5a..e809cb15d575 100644 --- a/core/sdk-core/src/test/java/utils/HttpTestUtils.java +++ b/core/sdk-core/src/test/java/utils/HttpTestUtils.java @@ -31,25 +31,19 @@ import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.internal.http.AmazonAsyncHttpClient; import software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient; -import software.amazon.awssdk.core.internal.http.loader.DefaultSdkAsyncHttpClientBuilder; -import software.amazon.awssdk.core.internal.http.loader.DefaultSdkHttpClientBuilder; import software.amazon.awssdk.core.internal.retry.SdkDefaultRetryStrategy; import software.amazon.awssdk.core.signer.NoOpSigner; import software.amazon.awssdk.http.SdkHttpClient; -import software.amazon.awssdk.http.SdkHttpConfigurationOption; import software.amazon.awssdk.http.async.SdkAsyncHttpClient; import software.amazon.awssdk.retries.api.RetryStrategy; -import software.amazon.awssdk.utils.AttributeMap; public class HttpTestUtils { public static SdkHttpClient testSdkHttpClient() { - return new DefaultSdkHttpClientBuilder().buildWithDefaults( - AttributeMap.empty().merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS)); + return StubSdkHttpClient.create(); } public static SdkAsyncHttpClient testSdkAsyncHttpClient() { - return new DefaultSdkAsyncHttpClientBuilder().buildWithDefaults( - AttributeMap.empty().merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS)); + return StubSdkAsyncHttpClient.create(); } public static AmazonSyncHttpClient testAmazonHttpClient() { diff --git a/core/sdk-core/src/test/java/utils/StubSdkAsyncHttpClient.java b/core/sdk-core/src/test/java/utils/StubSdkAsyncHttpClient.java new file mode 100644 index 000000000000..09c8502b805e --- /dev/null +++ b/core/sdk-core/src/test/java/utils/StubSdkAsyncHttpClient.java @@ -0,0 +1,83 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 utils; + +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.http.async.AsyncExecuteRequest; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.async.SdkAsyncHttpResponseHandler; +import software.amazon.awssdk.utils.async.SimplePublisher; + +/** + * A minimal in-memory {@link SdkAsyncHttpClient} for sdk-core tests that do not need a real transport: it immediately delivers + * a response with a configurable status code and an empty body to the response handler, without touching the network. Tests + * that induce a timeout via a slow response handler or interceptor only need the transport to respond promptly with the right + * status (200 vs 5xx) so the success or error path is selected. + */ +public final class StubSdkAsyncHttpClient implements SdkAsyncHttpClient { + + /** + * A small deliberate transport latency. The real async HTTP client talking to a WireMock server (which this stub + * replaced) always took some milliseconds to deliver a response. Tests that assert an API call timeout fires while a + * response handler sleeps for exactly the timeout duration depend on that latency to push the total elapsed time past + * the timeout, so the stub reproduces it rather than responding instantly. + */ + private static final long RESPONSE_LATENCY_MILLIS = 50; + + private final int statusCode; + + private StubSdkAsyncHttpClient(int statusCode) { + this.statusCode = statusCode; + } + + public static StubSdkAsyncHttpClient create() { + return new StubSdkAsyncHttpClient(200); + } + + public static StubSdkAsyncHttpClient create(int statusCode) { + return new StubSdkAsyncHttpClient(statusCode); + } + + @Override + public CompletableFuture execute(AsyncExecuteRequest request) { + SdkAsyncHttpResponseHandler responseHandler = request.responseHandler(); + simulateTransportLatency(); + responseHandler.onHeaders(SdkHttpResponse.builder().statusCode(statusCode).build()); + SimplePublisher bodyPublisher = new SimplePublisher<>(); + responseHandler.onStream(bodyPublisher); + bodyPublisher.complete(); + return CompletableFuture.completedFuture(null); + } + + @Override + public void close() { + } + + @Override + public String clientName() { + return "StubAsync"; + } + + private static void simulateTransportLatency() { + try { + Thread.sleep(RESPONSE_LATENCY_MILLIS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/core/sdk-core/src/test/java/utils/StubSdkHttpClient.java b/core/sdk-core/src/test/java/utils/StubSdkHttpClient.java new file mode 100644 index 000000000000..249731760f5f --- /dev/null +++ b/core/sdk-core/src/test/java/utils/StubSdkHttpClient.java @@ -0,0 +1,90 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 utils; + +import java.io.ByteArrayInputStream; +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.http.ExecutableHttpRequest; +import software.amazon.awssdk.http.HttpExecuteRequest; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpResponse; + +/** + * A minimal in-memory {@link SdkHttpClient} for sdk-core tests that do not need a real transport: it immediately returns a + * response with a configurable status code and an empty body, without touching the network. Tests that induce a timeout via a + * slow response handler or interceptor only need the transport to return promptly with the right status (200 vs 5xx) so the + * success or error path is selected. + */ +public final class StubSdkHttpClient implements SdkHttpClient { + + /** + * A small deliberate transport latency. The real HTTP client talking to a WireMock server (which this stub replaced) + * always took some milliseconds to return a response. Tests that assert an API call timeout fires while a response + * handler sleeps for exactly the timeout duration depend on that latency to push the total elapsed time past the + * timeout, so the stub reproduces it rather than returning instantly. + */ + private static final long RESPONSE_LATENCY_MILLIS = 50; + + private final int statusCode; + + private StubSdkHttpClient(int statusCode) { + this.statusCode = statusCode; + } + + public static StubSdkHttpClient create() { + return new StubSdkHttpClient(200); + } + + public static StubSdkHttpClient create(int statusCode) { + return new StubSdkHttpClient(statusCode); + } + + @Override + public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) { + return new ExecutableHttpRequest() { + @Override + public HttpExecuteResponse call() { + simulateTransportLatency(); + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(statusCode).build()) + .responseBody(AbortableInputStream.create(new ByteArrayInputStream(new byte[0]))) + .build(); + } + + @Override + public void abort() { + } + }; + } + + @Override + public void close() { + } + + @Override + public String clientName() { + return "Stub"; + } + + private static void simulateTransportLatency() { + try { + Thread.sleep(RESPONSE_LATENCY_MILLIS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheConnectionPoolMaxConnectionsTest.java b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheConnectionPoolMaxConnectionsTest.java new file mode 100644 index 000000000000..a0011ce57686 --- /dev/null +++ b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheConnectionPoolMaxConnectionsTest.java @@ -0,0 +1,37 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http.apache; + +import java.time.Duration; +import org.apache.http.conn.ConnectionPoolTimeoutException; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpClientConnectionPoolTestSuite; + +public class ApacheConnectionPoolMaxConnectionsTest extends SdkHttpClientConnectionPoolTestSuite { + + @Override + protected SdkHttpClient createSdkHttpClient(int maxConnections, Duration connectionTimeout) { + return ApacheHttpClient.builder() + .connectionTimeout(connectionTimeout) + .maxConnections(maxConnections) + .build(); + } + + @Override + protected Class expectedPoolTimeoutCause() { + return ConnectionPoolTimeoutException.class; + } +} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/UnresponsiveMockServerTestBase.java b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheHttpClientSdkPipelineBehaviorTest.java similarity index 61% rename from core/sdk-core/src/test/java/software/amazon/awssdk/core/http/UnresponsiveMockServerTestBase.java rename to http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheHttpClientSdkPipelineBehaviorTest.java index 5f8451bda352..833483813962 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/UnresponsiveMockServerTestBase.java +++ b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheHttpClientSdkPipelineBehaviorTest.java @@ -13,13 +13,14 @@ * permissions and limitations under the License. */ -package software.amazon.awssdk.core.http; +package software.amazon.awssdk.http.apache; -import software.amazon.awssdk.core.http.server.MockServer; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpClientSdkPipelineBehaviorTestSuite; -public abstract class UnresponsiveMockServerTestBase extends MockServerTestBase { +public class ApacheHttpClientSdkPipelineBehaviorTest extends SdkHttpClientSdkPipelineBehaviorTestSuite { @Override - protected MockServer buildMockServer() { - return MockServer.createMockServer(MockServer.ServerBehavior.UNRESPONSIVE); + protected SdkHttpClient createSdkHttpClient() { + return ApacheHttpClient.builder().build(); } } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/MockServerTestBase.java b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheHttpClientSslHandshakeTimeoutTest.java similarity index 50% rename from core/sdk-core/src/test/java/software/amazon/awssdk/core/http/MockServerTestBase.java rename to http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheHttpClientSslHandshakeTimeoutTest.java index b4b38e646c85..3285c1998785 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/MockServerTestBase.java +++ b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheHttpClientSslHandshakeTimeoutTest.java @@ -13,29 +13,18 @@ * permissions and limitations under the License. */ -package software.amazon.awssdk.core.http; +package software.amazon.awssdk.http.apache; -import org.junit.After; -import org.junit.Before; -import software.amazon.awssdk.core.http.server.MockServer; +import java.time.Duration; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpClientSslHandshakeTimeoutTestSuite; -public abstract class MockServerTestBase { +public class ApacheHttpClientSslHandshakeTimeoutTest extends SdkHttpClientSslHandshakeTimeoutTestSuite { - protected MockServer server; - - @Before - public void setupBaseFixture() { - server = buildMockServer(); - server.startServer(); - } - - @After - public void tearDownBaseFixture() { - server.stopServer(); + @Override + protected SdkHttpClient createSdkHttpClient(Duration socketTimeout) { + return ApacheHttpClient.builder() + .socketTimeout(socketTimeout) + .build(); } - - /** - * Implemented by test subclasses to build the correct type of {@link MockServer} - */ - protected abstract MockServer buildMockServer(); } diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5ConnectionPoolMaxConnectionsTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5ConnectionPoolMaxConnectionsTest.java new file mode 100644 index 000000000000..c6442201c4af --- /dev/null +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5ConnectionPoolMaxConnectionsTest.java @@ -0,0 +1,37 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http.apache5; + +import java.time.Duration; +import org.apache.hc.core5.http.ConnectionRequestTimeoutException; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpClientConnectionPoolTestSuite; + +public class Apache5ConnectionPoolMaxConnectionsTest extends SdkHttpClientConnectionPoolTestSuite { + + @Override + protected SdkHttpClient createSdkHttpClient(int maxConnections, Duration connectionTimeout) { + return Apache5HttpClient.builder() + .connectionTimeout(connectionTimeout) + .maxConnections(maxConnections) + .build(); + } + + @Override + protected Class expectedPoolTimeoutCause() { + return ConnectionRequestTimeoutException.class; + } +} diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSdkPipelineBehaviorTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSdkPipelineBehaviorTest.java new file mode 100644 index 000000000000..3a9d9c951316 --- /dev/null +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSdkPipelineBehaviorTest.java @@ -0,0 +1,26 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http.apache5; + +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpClientSdkPipelineBehaviorTestSuite; + +public class Apache5HttpClientSdkPipelineBehaviorTest extends SdkHttpClientSdkPipelineBehaviorTestSuite { + @Override + protected SdkHttpClient createSdkHttpClient() { + return Apache5HttpClient.builder().build(); + } +} diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSslHandshakeTimeoutTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSslHandshakeTimeoutTest.java new file mode 100644 index 000000000000..055e7f850ad5 --- /dev/null +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSslHandshakeTimeoutTest.java @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http.apache5; + +import java.time.Duration; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpClientSslHandshakeTimeoutTestSuite; + +public class Apache5HttpClientSslHandshakeTimeoutTest extends SdkHttpClientSslHandshakeTimeoutTestSuite { + + @Override + protected SdkHttpClient createSdkHttpClient(Duration socketTimeout) { + return Apache5HttpClient.builder() + .socketTimeout(socketTimeout) + .build(); + } +} diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientApiCallAttemptTimeoutTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientApiCallAttemptTimeoutTest.java new file mode 100644 index 000000000000..e941d80b3e7a --- /dev/null +++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientApiCallAttemptTimeoutTest.java @@ -0,0 +1,26 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http.nio.netty; + +import software.amazon.awssdk.http.SdkAsyncHttpClientApiCallAttemptTimeoutTestSuite; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; + +public class NettyNioAsyncHttpClientApiCallAttemptTimeoutTest extends SdkAsyncHttpClientApiCallAttemptTimeoutTestSuite { + @Override + protected SdkAsyncHttpClient createSdkAsyncHttpClient() { + return NettyNioAsyncHttpClient.builder().build(); + } +} diff --git a/test/http-client-tests/pom.xml b/test/http-client-tests/pom.xml index e4a9eedc73d5..41f6e39b46c8 100644 --- a/test/http-client-tests/pom.xml +++ b/test/http-client-tests/pom.xml @@ -48,6 +48,11 @@ http-client-spi ${awsjavasdk.version} + + software.amazon.awssdk + sdk-core + ${awsjavasdk.version} + software.amazon.awssdk metrics-spi diff --git a/test/http-client-tests/src/main/java/software/amazon/awssdk/core/http/NoopTestRequest.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/core/http/NoopTestRequest.java new file mode 100644 index 000000000000..3f2251a2626f --- /dev/null +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/core/http/NoopTestRequest.java @@ -0,0 +1,81 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.http; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import software.amazon.awssdk.core.SdkField; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkRequestOverrideConfiguration; + +public class NoopTestRequest extends SdkRequest { + + private final SdkRequestOverrideConfiguration requestOverrideConfig; + + private NoopTestRequest(Builder builder) { + this.requestOverrideConfig = builder.overrideConfiguration(); + + } + + @Override + public Optional overrideConfiguration() { + return Optional.ofNullable(requestOverrideConfig); + } + + @Override + public Builder toBuilder() { + return new BuilderImpl(); + } + + public static Builder builder() { + return new BuilderImpl(); + } + + @Override + public List> sdkFields() { + return Collections.emptyList(); + } + + public interface Builder extends SdkRequest.Builder { + @Override + NoopTestRequest build(); + + @Override + SdkRequestOverrideConfiguration overrideConfiguration(); + + Builder overrideConfiguration(SdkRequestOverrideConfiguration requestOverrideConfig); + } + + private static class BuilderImpl implements Builder { + private SdkRequestOverrideConfiguration requestOverrideConfig; + + @Override + public SdkRequestOverrideConfiguration overrideConfiguration() { + return requestOverrideConfig; + } + + public Builder overrideConfiguration(SdkRequestOverrideConfiguration requestOverrideConfig) { + this.requestOverrideConfig = requestOverrideConfig; + return this; + } + + @Override + public NoopTestRequest build() { + return new NoopTestRequest(this); + } + } +} diff --git a/test/http-client-tests/src/main/java/software/amazon/awssdk/core/internal/http/response/EmptySdkResponseHandler.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/core/internal/http/response/EmptySdkResponseHandler.java new file mode 100644 index 000000000000..9c1c0bdf12db --- /dev/null +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/core/internal/http/response/EmptySdkResponseHandler.java @@ -0,0 +1,37 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.http.response; + +import software.amazon.awssdk.core.SdkResponse; +import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.protocol.VoidSdkResponse; +import software.amazon.awssdk.http.SdkHttpFullResponse; + +public class EmptySdkResponseHandler implements HttpResponseHandler { + + @Override + public SdkResponse handle(SdkHttpFullResponse response, + ExecutionAttributes executionAttributes) + throws Exception { + return VoidSdkResponse.builder().build(); + } + + @Override + public boolean needsConnectionLeftOpen() { + return true; + } +} diff --git a/test/http-client-tests/src/main/java/software/amazon/awssdk/core/internal/http/response/NullErrorResponseHandler.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/core/internal/http/response/NullErrorResponseHandler.java new file mode 100644 index 000000000000..97916c106ed1 --- /dev/null +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/core/internal/http/response/NullErrorResponseHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.http.response; + +import software.amazon.awssdk.core.exception.SdkServiceException; +import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.http.SdkHttpFullResponse; + +public class NullErrorResponseHandler implements HttpResponseHandler { + + @Override + public SdkServiceException handle(SdkHttpFullResponse response, + ExecutionAttributes executionAttributes) throws Exception { + return SdkServiceException.builder() + .statusCode(response.statusCode()) + .build(); + } + + @Override + public boolean needsConnectionLeftOpen() { + return false; + } + +} diff --git a/test/http-client-tests/src/main/java/software/amazon/awssdk/core/internal/util/AsyncResponseHandlerTestUtils.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/core/internal/util/AsyncResponseHandlerTestUtils.java new file mode 100644 index 000000000000..8761bbfb6403 --- /dev/null +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/core/internal/util/AsyncResponseHandlerTestUtils.java @@ -0,0 +1,142 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.util; + +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.core.Response; +import software.amazon.awssdk.core.async.DrainingSubscriber; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.core.internal.http.TransformingAsyncResponseHandler; +import software.amazon.awssdk.core.internal.http.async.CombinedResponseAsyncHttpResponseHandler; +import software.amazon.awssdk.http.SdkHttpResponse; + +public class AsyncResponseHandlerTestUtils { + private AsyncResponseHandlerTestUtils() { + } + + public static TransformingAsyncResponseHandler noOpResponseHandler() { + return noOpResponseHandler(null); + } + + public static TransformingAsyncResponseHandler noOpResponseHandler(T result) { + return new NoOpResponseHandler<>(result); + } + + public static TransformingAsyncResponseHandler superSlowResponseHandler(long sleepInMillis) { + return superSlowResponseHandler(null, sleepInMillis); + } + + public static TransformingAsyncResponseHandler superSlowResponseHandler(T result, long sleepInMillis) { + return new SuperSlowResponseHandler<>(result, sleepInMillis); + } + + + public static TransformingAsyncResponseHandler> combinedAsyncResponseHandler( + TransformingAsyncResponseHandler successResponseHandler, + TransformingAsyncResponseHandler failureResponseHandler) { + + return new CombinedResponseAsyncHttpResponseHandler<>( + successResponseHandler == null ? noOpResponseHandler() : successResponseHandler, + failureResponseHandler == null ? noOpResponseHandler() : failureResponseHandler); + } + + private static class NoOpResponseHandler implements TransformingAsyncResponseHandler { + private final CompletableFuture cf = new CompletableFuture<>(); + private final T result; + + NoOpResponseHandler(T result) { + this.result = result; + } + + @Override + public CompletableFuture prepare() { + return cf; + } + + @Override + public void onHeaders(SdkHttpResponse headers) { + } + + @Override + public void onStream(Publisher stream) { + stream.subscribe(new DrainingSubscriber() { + @Override + public void onError(Throwable t) { + cf.completeExceptionally(t); + } + + @Override + public void onComplete() { + cf.complete(result); + } + }); + } + + @Override + public void onError(Throwable error) { + cf.completeExceptionally(error); + } + } + + private static class SuperSlowResponseHandler implements TransformingAsyncResponseHandler { + private final CompletableFuture cf = new CompletableFuture<>(); + private final T result; + private final long sleepMillis; + + SuperSlowResponseHandler(T result, long sleepMillis) { + this.result = result; + this.sleepMillis = sleepMillis; + } + + @Override + public CompletableFuture prepare() { + return cf.thenApply(r -> { + try { + Thread.sleep(sleepMillis); + } catch (InterruptedException ignored) { + // Ignored. + } + return r; + }); + } + + @Override + public void onHeaders(SdkHttpResponse headers) { + } + + @Override + public void onStream(Publisher stream) { + stream.subscribe(new DrainingSubscriber() { + @Override + public void onError(Throwable t) { + cf.completeExceptionally(t); + } + + @Override + public void onComplete() { + cf.complete(result); + } + }); + } + + @Override + public void onError(Throwable error) { + cf.completeExceptionally(error); + } + } +} diff --git a/test/http-client-tests/src/main/java/software/amazon/awssdk/core/internal/util/ResponseHandlerTestUtils.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/core/internal/util/ResponseHandlerTestUtils.java new file mode 100644 index 000000000000..d3e2731cba1e --- /dev/null +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/core/internal/util/ResponseHandlerTestUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.util; + +import software.amazon.awssdk.core.Response; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.core.exception.SdkServiceException; +import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.internal.http.CombinedResponseHandler; + +public final class ResponseHandlerTestUtils { + private ResponseHandlerTestUtils() { + } + + public static HttpResponseHandler noOpSyncResponseHandler() { + return (response, executionAttributes) -> null; + } + + public static HttpResponseHandler superSlowResponseHandler(long sleepInMills) { + return (response, executionAttributes) -> { + Thread.sleep(sleepInMills); + return null; + }; + } + + public static HttpResponseHandler> combinedSyncResponseHandler( + HttpResponseHandler successResponseHandler, + HttpResponseHandler failureResponseHandler) { + + return new CombinedResponseHandler<>( + successResponseHandler == null ? noOpSyncResponseHandler() : successResponseHandler, + failureResponseHandler == null ? noOpSyncResponseHandler() : failureResponseHandler); + } +} diff --git a/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkAsyncHttpClientApiCallAttemptTimeoutTestSuite.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkAsyncHttpClientApiCallAttemptTimeoutTestSuite.java new file mode 100644 index 000000000000..c3e3c5287272 --- /dev/null +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkAsyncHttpClientApiCallAttemptTimeoutTestSuite.java @@ -0,0 +1,81 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static software.amazon.awssdk.core.internal.util.AsyncResponseHandlerTestUtils.noOpResponseHandler; +import static utils.HttpTestUtils.executionContext; +import static utils.HttpTestUtils.testAsyncClientBuilder; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import java.io.ByteArrayInputStream; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import org.junit.Rule; +import org.junit.Test; +import software.amazon.awssdk.core.exception.ApiCallAttemptTimeoutException; +import software.amazon.awssdk.core.http.NoopTestRequest; +import software.amazon.awssdk.core.internal.http.AmazonAsyncHttpClient; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import utils.ValidSdkObjects; + +/** + * Reusable suite that verifies sdk-core's async API-call-attempt timeout aborts a slow real response, exercised over a real + * {@link SdkAsyncHttpClient}. Each concrete async HTTP client module subclasses this and provides its client via + * {@link #createSdkAsyncHttpClient()}. + */ +public abstract class SdkAsyncHttpClientApiCallAttemptTimeoutTestSuite { + + private static final Duration API_CALL_TIMEOUT = Duration.ofSeconds(1); + + @Rule + public WireMockRule wireMock = new WireMockRule(0); + + private AmazonAsyncHttpClient httpClient; + + protected abstract SdkAsyncHttpClient createSdkAsyncHttpClient(); + + @Test + public void slowApiAttempt_ThrowsApiCallAttemptTimeoutException() { + httpClient = testAsyncClientBuilder() + .asyncHttpClient(createSdkAsyncHttpClient()) + .apiCallTimeout(API_CALL_TIMEOUT) + .apiCallAttemptTimeout(Duration.ofMillis(1)) + .build(); + + stubFor(get(anyUrl()) + .willReturn(aResponse().withStatus(200).withBody("{}").withFixedDelay(1_000))); + CompletableFuture future = requestBuilder().execute(noOpResponseHandler()); + assertThatThrownBy(future::join).hasCauseInstanceOf(ApiCallAttemptTimeoutException.class); + } + + private AmazonAsyncHttpClient.RequestExecutionBuilder requestBuilder() { + return httpClient.requestExecutionBuilder() + .request(generateRequest()) + .originalRequest(NoopTestRequest.builder().build()) + .executionContext(executionContext(null)); + } + + private SdkHttpFullRequest generateRequest() { + return ValidSdkObjects.sdkHttpFullRequest(wireMock.port()) + .host("localhost") + .contentStreamProvider(() -> new ByteArrayInputStream("test".getBytes())).build(); + } +} diff --git a/core/sdk-core/src/it/java/software/amazon/awssdk/core/http/ConnectionPoolMaxConnectionsIntegrationTest.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientConnectionPoolTestSuite.java similarity index 59% rename from core/sdk-core/src/it/java/software/amazon/awssdk/core/http/ConnectionPoolMaxConnectionsIntegrationTest.java rename to test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientConnectionPoolTestSuite.java index 942b6c957820..ad424615ef96 100644 --- a/core/sdk-core/src/it/java/software/amazon/awssdk/core/http/ConnectionPoolMaxConnectionsIntegrationTest.java +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientConnectionPoolTestSuite.java @@ -13,55 +13,72 @@ * permissions and limitations under the License. */ -package software.amazon.awssdk.core.http; +package software.amazon.awssdk.http; -import static software.amazon.awssdk.core.internal.http.timers.ClientExecutionAndRequestTimerTestUtils.executionContext; +import static org.assertj.core.api.Assertions.assertThat; import static software.amazon.awssdk.core.internal.util.ResponseHandlerTestUtils.combinedSyncResponseHandler; +import static utils.HttpTestUtils.executionContext; import java.time.Duration; - -import org.apache.http.conn.ConnectionPoolTimeoutException; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import software.amazon.awssdk.core.exception.SdkClientException; -import software.amazon.awssdk.core.http.server.MockServer; +import software.amazon.awssdk.core.http.NoopTestRequest; import software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient; import software.amazon.awssdk.core.internal.http.response.EmptySdkResponseHandler; -import software.amazon.awssdk.http.SdkHttpFullRequest; -import software.amazon.awssdk.http.SdkHttpMethod; -import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.http.server.MockServer; import software.amazon.awssdk.retries.DefaultRetryStrategy; import utils.HttpTestUtils; -public class ConnectionPoolMaxConnectionsIntegrationTest { +/** + * Verifies that a client whose connection pool is limited to a single connection fails to lease a second connection while the + * first one is still in use. + * + *

This suite applies to sync clients that expose a bounded connection pool through a {@code maxConnections} setting, which + * today means {@code apache-client} and {@code apache5-client}. {@code url-connection-client} and {@code aws-crt-client} do not + * expose that setting, so they do not subclass this suite.

+ * + *

The exception raised on pool exhaustion is specific to the underlying HTTP library, so subclasses declare it through + * {@link #expectedPoolTimeoutCause()}.

+ */ +public abstract class SdkHttpClientConnectionPoolTestSuite { private static MockServer server; - @BeforeClass + /** + * Returns a client whose connection pool holds at most {@code maxConnections} connections, and which waits no longer than + * {@code connectionTimeout} for a connection. + */ + protected abstract SdkHttpClient createSdkHttpClient(int maxConnections, Duration connectionTimeout); + + /** + * Returns the exception the underlying HTTP library raises when the pool cannot supply a connection in time. + */ + protected abstract Class expectedPoolTimeoutCause(); + + @BeforeAll public static void setup() { server = MockServer.createMockServer(MockServer.ServerBehavior.OVERLOADED); server.startServer(); } - @AfterClass + @AfterAll public static void tearDown() { if (server != null) { server.stopServer(); } } - @Test(timeout = 60 * 1000) + @Test + @Timeout(60) public void leasing_a_new_connection_fails_with_connection_pool_timeout() { AmazonSyncHttpClient httpClient = HttpTestUtils.testClientBuilder() .retryStrategy(DefaultRetryStrategy.doNotRetry()) - .httpClient(ApacheHttpClient.builder() - .connectionTimeout(Duration.ofMillis(100)) - .maxConnections(1) - .build()) + .httpClient(createSdkHttpClient(1, Duration.ofMillis(100))) .build(); SdkHttpFullRequest request = server.configureHttpEndpoint(SdkHttpFullRequest.builder()) @@ -83,9 +100,9 @@ public void leasing_a_new_connection_fails_with_connection_pool_timeout() { .originalRequest(NoopTestRequest.builder().build()) .executionContext(executionContext(request)) .execute(combinedSyncResponseHandler(null, null)); - Assert.fail("Connection pool timeout exception is expected!"); + Assertions.fail("Connection pool timeout exception is expected!"); } catch (SdkClientException e) { - Assert.assertTrue(e.getCause() instanceof ConnectionPoolTimeoutException); + assertThat(e.getCause()).isInstanceOf(expectedPoolTimeoutCause()); } } } diff --git a/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSdkPipelineBehaviorTestSuite.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSdkPipelineBehaviorTestSuite.java new file mode 100644 index 000000000000..06ef704a0acf --- /dev/null +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSdkPipelineBehaviorTestSuite.java @@ -0,0 +1,230 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.optionsRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static software.amazon.awssdk.core.internal.util.ResponseHandlerTestUtils.combinedSyncResponseHandler; +import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; +import static utils.HttpTestUtils.executionContext; +import static utils.HttpTestUtils.testClientBuilder; + +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import software.amazon.awssdk.core.exception.SdkServiceException; +import software.amazon.awssdk.core.http.NoopTestRequest; +import software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient; +import software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyTransactionIdStage; +import software.amazon.awssdk.core.internal.http.response.NullErrorResponseHandler; +import software.amazon.awssdk.core.io.SdkFilterInputStream; +import utils.http.WireMockTestBase; + +/** + * Reusable suite that exercises sdk-core's {@link AmazonSyncHttpClient} execution pipeline (client-configuration header + * injection, request-header precedence, OPTIONS pass-through, transaction-id consistency across retries, and closing of + * every {@link ContentStreamProvider}-created stream) over a real {@link SdkHttpClient}. Each concrete HTTP client module + * subclasses this and provides its client via {@link #createSdkHttpClient()}. + */ +public abstract class SdkHttpClientSdkPipelineBehaviorTestSuite extends WireMockTestBase { + + private static final String OPERATION = "/some-operation"; + private static final String HEADER = "Some-Header"; + private static final String CONFIG_HEADER_VALUE = "client config header value"; + private static final String REQUEST_HEADER_VALUE = "request header value"; + private static final String RESOURCE_PATH = "/transaction-id/"; + + protected abstract SdkHttpClient createSdkHttpClient(); + + @Test + public void headersSpecifiedInClientConfigurationArePutOnRequest() { + stubFor(any(urlPathEqualTo(OPERATION)).willReturn(aResponse())); + SdkHttpFullRequest request = newGetRequest(OPERATION).build(); + + AmazonSyncHttpClient sut = createClient(HEADER, CONFIG_HEADER_VALUE); + sendRequest(request, sut); + + verify(getRequestedFor(urlPathEqualTo(OPERATION)).withHeader(HEADER, matching(CONFIG_HEADER_VALUE))); + } + + @Test + public void headersOnRequestsWinOverClientConfigurationHeaders() { + stubFor(any(urlPathEqualTo(OPERATION)).willReturn(aResponse())); + SdkHttpFullRequest request = newGetRequest(OPERATION) + .putHeader(HEADER, REQUEST_HEADER_VALUE) + .build(); + + AmazonSyncHttpClient sut = createClient(HEADER, CONFIG_HEADER_VALUE); + sendRequest(request, sut); + + verify(getRequestedFor(urlPathEqualTo(OPERATION)).withHeader(HEADER, matching(REQUEST_HEADER_VALUE))); + } + + @Test + public void canHandleOptionsRequest() { + stubFor(any(urlPathEqualTo(OPERATION)).willReturn(aResponse())); + SdkHttpFullRequest request = newRequest(OPERATION) + .method(SdkHttpMethod.OPTIONS) + .build(); + + AmazonSyncHttpClient sut = amazonSyncHttpClient(); + sendRequest(request, sut); + + verify(optionsRequestedFor(urlPathEqualTo(OPERATION))); + } + + @Test + public void retriedRequest_HasSameTransactionIdForAllRetries() throws Exception { + stubFor(get(urlEqualTo(RESOURCE_PATH)).willReturn(aResponse().withStatus(500))); + executeRequest(); + assertTransactionIdIsUnchangedAcrossRetries(); + } + + @Test + public void closesAllCreatedInputStreamsFromProvider() { + stubFor(any(urlPathEqualTo(OPERATION)).willReturn(aResponse().withStatus(500))); + + TestContentStreamProvider provider = new TestContentStreamProvider(); + SdkHttpFullRequest request = newRequest(OPERATION) + .contentStreamProvider(provider) + .method(SdkHttpMethod.PUT) + .build(); + + AmazonSyncHttpClient testClient = amazonSyncHttpClient(); + try { + sendRequest(request, testClient); + fail("Should have thrown SdkServiceException"); + } catch (SdkServiceException ignored) { + // Ignored or expected. + } + + // The test client uses the default retry policy so there should be 4 + // total attempts and an equal number created streams + assertThat(provider.getCreatedStreams().size()).isEqualTo(4); + for (CloseTrackingInputStream is : provider.getCreatedStreams()) { + assertThat(is.isClosed()).isTrue(); + } + } + + private void executeRequest() throws Exception { + AmazonSyncHttpClient httpClient = amazonSyncHttpClient(); + try { + SdkHttpFullRequest request = newGetRequest(RESOURCE_PATH).build(); + httpClient.requestExecutionBuilder() + .request(request) + .originalRequest(NoopTestRequest.builder().build()) + .executionContext(executionContext(request)) + .execute(combinedSyncResponseHandler(null, stubErrorHandler())); + fail("Expected exception"); + } catch (SdkServiceException expected) { + // Ignored or expected. + } + } + + private void assertTransactionIdIsUnchangedAcrossRetries() { + String previousTransactionId = null; + for (LoggedRequest request : findAll(getRequestedFor(urlEqualTo(RESOURCE_PATH)))) { + String currentTransactionId = request.getHeader(ApplyTransactionIdStage.HEADER_SDK_TRANSACTION_ID); + // Transaction ID should always be set + assertNotNull(currentTransactionId); + // Transaction ID should be the same across retries + if (previousTransactionId != null) { + assertEquals(previousTransactionId, currentTransactionId); + } + previousTransactionId = currentTransactionId; + } + } + + private void sendRequest(SdkHttpFullRequest request, AmazonSyncHttpClient sut) { + sut.requestExecutionBuilder() + .request(request) + .originalRequest(NoopTestRequest.builder().build()) + .executionContext(executionContext(request)) + .execute(combinedSyncResponseHandler(null, new NullErrorResponseHandler())); + } + + private AmazonSyncHttpClient createClient(String headerName, String headerValue) { + return testClientBuilder().httpClient(createSdkHttpClient()).additionalHeader(headerName, headerValue).build(); + } + + private AmazonSyncHttpClient amazonSyncHttpClient() { + return testClientBuilder().httpClient(createSdkHttpClient()).build(); + } + + private static class TestContentStreamProvider implements ContentStreamProvider { + private static final byte[] CONTENT_BYTES = "Hello".getBytes(StandardCharsets.UTF_8); + private List createdStreams = new ArrayList<>(); + + @Override + public InputStream newStream() { + closeCurrentStream(); + CloseTrackingInputStream s = newContentStream(); + createdStreams.add(s); + return s; + } + + List getCreatedStreams() { + return createdStreams; + } + + private CloseTrackingInputStream newContentStream() { + return new CloseTrackingInputStream(new ByteArrayInputStream(CONTENT_BYTES)); + } + + private void closeCurrentStream() { + if (createdStreams.isEmpty()) { + return; + } + invokeSafely(() -> createdStreams.get(createdStreams.size() - 1).close()); + } + } + + private static class CloseTrackingInputStream extends SdkFilterInputStream { + private boolean isClosed = false; + + CloseTrackingInputStream(InputStream in) { + super(in); + } + + @Override + public void close() throws IOException { + super.close(); + isClosed = true; + } + + boolean isClosed() { + return isClosed; + } + } +} diff --git a/core/sdk-core/src/it/java/software/amazon/awssdk/core/http/AmazonHttpClientSslHandshakeTimeoutTest.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSslHandshakeTimeoutTestSuite.java similarity index 63% rename from core/sdk-core/src/it/java/software/amazon/awssdk/core/http/AmazonHttpClientSslHandshakeTimeoutTest.java rename to test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSslHandshakeTimeoutTestSuite.java index 3b82be2be2c8..fa64b4728b06 100644 --- a/core/sdk-core/src/it/java/software/amazon/awssdk/core/http/AmazonHttpClientSslHandshakeTimeoutTest.java +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSslHandshakeTimeoutTestSuite.java @@ -13,49 +13,67 @@ * permissions and limitations under the License. */ -package software.amazon.awssdk.core.http; +package software.amazon.awssdk.http; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; -import static software.amazon.awssdk.core.internal.http.timers.ClientExecutionAndRequestTimerTestUtils.executionContext; +import static org.junit.jupiter.api.Assertions.fail; import static software.amazon.awssdk.core.internal.util.ResponseHandlerTestUtils.combinedSyncResponseHandler; +import static utils.HttpTestUtils.executionContext; import java.io.IOException; import java.time.Duration; - -import org.junit.Test; - +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.http.NoopTestRequest; import software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient; import software.amazon.awssdk.core.internal.http.response.NullErrorResponseHandler; -import software.amazon.awssdk.http.SdkHttpFullRequest; -import software.amazon.awssdk.http.SdkHttpMethod; -import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.http.server.MockServer; import software.amazon.awssdk.retries.DefaultRetryStrategy; import utils.HttpTestUtils; /** - * This test is to verify that the apache-httpclient library has fixed the bug where socket timeout configuration is - * incorrectly ignored during SSL handshake. This test is expected to hang (and fail after the junit timeout) if run - * against the problematic httpclient version (e.g. 4.3). + * Verifies that a client honors its configured socket timeout during the SSL handshake. The client is pointed at a server that + * accepts the connection and then sends nothing, so a client that ignores the socket timeout hangs until the test times out. + * + *

This suite applies to sync clients that expose a socket timeout, which today means {@code apache-client}, + * {@code apache5-client} and {@code url-connection-client}. It started as a regression test for a bug in Apache HttpClient + * 4.3, where the socket timeout was ignored during the SSL handshake.

* * @link https://issues.apache.org/jira/browse/HTTPCLIENT-1478 */ -public class AmazonHttpClientSslHandshakeTimeoutTest extends UnresponsiveMockServerTestBase { +public abstract class SdkHttpClientSslHandshakeTimeoutTestSuite { private static final Duration CLIENT_SOCKET_TO = Duration.ofSeconds(1); - @Test(timeout = 60 * 1000) + private MockServer server; + + /** + * Returns a client that waits no longer than {@code socketTimeout} for data from the server. + */ + protected abstract SdkHttpClient createSdkHttpClient(Duration socketTimeout); + + @BeforeEach + public void setupBaseFixture() { + server = MockServer.createMockServer(MockServer.ServerBehavior.UNRESPONSIVE); + server.startServer(); + } + + @AfterEach + public void tearDownBaseFixture() { + server.stopServer(); + } + + @Test + @Timeout(60) public void testSslHandshakeTimeout() { AmazonSyncHttpClient httpClient = HttpTestUtils.testClientBuilder() .retryStrategy(DefaultRetryStrategy.doNotRetry()) - .httpClient(ApacheHttpClient.builder() - .socketTimeout(CLIENT_SOCKET_TO) - .build()) + .httpClient(createSdkHttpClient(CLIENT_SOCKET_TO)) .build(); - System.out.println("Sending request to localhost..."); - try { SdkHttpFullRequest request = server.configureHttpsEndpoint(SdkHttpFullRequest.builder()) .method(SdkHttpMethod.GET) diff --git a/test/http-client-tests/src/main/java/software/amazon/awssdk/http/server/MockServer.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/server/MockServer.java index 71e6c0c3909a..f1e1986c232f 100644 --- a/test/http-client-tests/src/main/java/software/amazon/awssdk/http/server/MockServer.java +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/server/MockServer.java @@ -58,6 +58,10 @@ public static MockServer createMockServer(ServerBehavior serverBehavior) { return new MockServer(new FullCloseInBetweenServerBehavior()); case FULL_CLOSE_AT_THE_END: return new MockServer(new FullCloseAtTheEndServerBehavior()); + case UNRESPONSIVE: + return new MockServer(new UnresponsiveServerBehavior()); + case OVERLOADED: + return new MockServer(new OverloadedServerBehavior()); default: throw new IllegalArgumentException("Unsupported implementation for server issue: " + serverBehavior); } @@ -94,6 +98,18 @@ public void startServer(TlsKeyManagersProvider keyManagersProvider) { listenerThread.start(); } + public void startServer() { + try { + serverSocket = new ServerSocket(0); // auto-assign a port at localhost + logger.info(() -> "Listening on port " + serverSocket.getLocalPort()); + } catch (IOException e) { + throw new RuntimeException("Unable to start the server socket.", e); + } + listenerThread = new MockServerListenerThread(serverSocket, serverBehaviorStrategy); + listenerThread.setDaemon(true); + listenerThread.start(); + } + public void stopServer() { listenerThread.interrupt(); try { @@ -127,7 +143,9 @@ public SdkHttpFullRequest.Builder configureHttpEndpoint(SdkHttpFullRequest.Build public enum ServerBehavior { HALF_CLOSE, FULL_CLOSE_IN_BETWEEN, - FULL_CLOSE_AT_THE_END + FULL_CLOSE_AT_THE_END, + UNRESPONSIVE, + OVERLOADED } public interface ServerBehaviorStrategy { @@ -293,6 +311,73 @@ public void runServer(ServerSocket serverSocket) { } } + /** + * A server behavior which accepts a single connection and then holds it open without writing any bytes. The test client + * talking to this server is expected to timeout appropriately, instead of hanging and waiting for the response forever. + */ + public static class UnresponsiveServerBehavior implements ServerBehaviorStrategy { + + @Override + public void runServer(ServerSocket serverSocket) { + Socket socket = null; + try { + socket = serverSocket.accept(); + Socket acceptedSocket = socket; + logger.info(() -> "Socket created on port " + acceptedSocket.getLocalPort()); + while (true) { + logger.debug(() -> "Holding the connection open without responding."); + Thread.sleep(10 * 1000); + } + } catch (InterruptedException e) { + // Stop server will interrupt to stop this thread. + return; + } catch (IOException e) { + throw new RuntimeException("Error when waiting for new socket connection.", e); + } finally { + closeQuietly(socket); + } + } + } + + /** + * A server behavior which accepts a connection, writes a partial HTTP response (with a Content-Length larger than the bytes + * actually sent) and then keeps holding the connection open while periodically writing a few more bytes. The test client + * talking to this server is expected to timeout appropriately, instead of hanging and waiting for the response forever. + */ + public static class OverloadedServerBehavior implements ServerBehaviorStrategy { + + @Override + public void runServer(ServerSocket serverSocket) { + try { + while (true) { + Socket socket = null; + try { + socket = serverSocket.accept(); + try (DataOutputStream out = new DataOutputStream(socket.getOutputStream())) { + out.writeBytes("HTTP/1.1 200 OK\r\n"); + out.writeBytes("Content-Type: text/html\r\n"); + out.writeBytes("Content-Length: 500\r\n\r\n"); + out.writeBytes("

Hello."); + while (true) { + Thread.sleep(1000); + out.writeBytes("Hi."); + } + } + } catch (SocketException se) { + // Ignored or expected. + } finally { + closeQuietly(socket); + } + } + } catch (IOException e) { + throw new RuntimeException("Error when waiting for new socket connection.", e); + } catch (InterruptedException e) { + // Stop server will interrupt to stop this thread. + return; + } + } + } + // TLS1.3 enbaled by default for 8u341 onwards. // https://www.oracle.com/java/technologies/javase/8u341-relnotes.html diff --git a/test/http-client-tests/src/main/java/utils/HttpTestUtils.java b/test/http-client-tests/src/main/java/utils/HttpTestUtils.java new file mode 100644 index 000000000000..928146962343 --- /dev/null +++ b/test/http-client-tests/src/main/java/utils/HttpTestUtils.java @@ -0,0 +1,221 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 utils; + +import java.net.URI; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; +import software.amazon.awssdk.core.ClientEndpointProvider; +import software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.http.ExecutionContext; +import software.amazon.awssdk.core.http.NoopTestRequest; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptorChain; +import software.amazon.awssdk.core.interceptor.InterceptorContext; +import software.amazon.awssdk.core.internal.http.AmazonAsyncHttpClient; +import software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient; +import software.amazon.awssdk.core.internal.retry.SdkDefaultRetryStrategy; +import software.amazon.awssdk.core.signer.NoOpSigner; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.retries.api.RetryStrategy; +import software.amazon.awssdk.utils.Validate; + +/** + * Test helpers for building {@link AmazonSyncHttpClient} / {@link AmazonAsyncHttpClient} around a caller-supplied + * {@link SdkHttpClient} / {@link SdkAsyncHttpClient}. Unlike the sdk-core copy of this class, the builders here require an + * explicit transport (there is no ServiceLoader-based default), so that per-client test suites can drive the sdk-core + * execution pipeline over each real HTTP client. + */ +public class HttpTestUtils { + + private HttpTestUtils() { + } + + public static TestClientBuilder testClientBuilder() { + return new TestClientBuilder(); + } + + public static TestAsyncClientBuilder testAsyncClientBuilder() { + return new TestAsyncClientBuilder(); + } + + /** + * Builds a minimal {@link ExecutionContext} for driving {@link AmazonSyncHttpClient} / {@link AmazonAsyncHttpClient} + * directly. + */ + public static ExecutionContext executionContext(SdkHttpFullRequest request) { + InterceptorContext interceptorContext = + InterceptorContext.builder() + .request(NoopTestRequest.builder().build()) + .httpRequest(request) + .build(); + return ExecutionContext.builder() + .signer(new NoOpSigner()) + .interceptorChain(new ExecutionInterceptorChain(Collections.emptyList())) + .executionAttributes(new ExecutionAttributes()) + .interceptorContext(interceptorContext) + .metricCollector(MetricCollector.create("ApiCall")) + .build(); + } + + public static SdkClientConfiguration testClientConfiguration() { + return SdkClientConfiguration.builder() + .option(SdkClientOption.EXECUTION_INTERCEPTORS, new ArrayList<>()) + .option(SdkClientOption.CLIENT_ENDPOINT_PROVIDER, + ClientEndpointProvider.forEndpointOverride(URI.create("http://localhost:8080"))) + .option(SdkClientOption.RETRY_STRATEGY, + SdkDefaultRetryStrategy.defaultRetryStrategy()) + .option(SdkClientOption.ADDITIONAL_HTTP_HEADERS, new HashMap<>()) + .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false) + .option(SdkAdvancedClientOption.SIGNER, new NoOpSigner()) + .option(SdkAdvancedClientOption.USER_AGENT_PREFIX, "") + .option(SdkAdvancedClientOption.USER_AGENT_SUFFIX, "") + .option(SdkClientOption.SCHEDULED_EXECUTOR_SERVICE, Executors.newScheduledThreadPool(1)) + .option(SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR, Runnable::run) + .build(); + } + + public static class TestClientBuilder { + private RetryStrategy retryStrategy; + private SdkHttpClient httpClient; + private Map additionalHeaders = new HashMap<>(); + private Duration apiCallTimeout; + private Duration apiCallAttemptTimeout; + + public TestClientBuilder retryStrategy(RetryStrategy retryStrategy) { + this.retryStrategy = retryStrategy; + return this; + } + + public TestClientBuilder httpClient(SdkHttpClient sdkHttpClient) { + this.httpClient = sdkHttpClient; + return this; + } + + public TestClientBuilder additionalHeader(String key, String value) { + this.additionalHeaders.put(key, value); + return this; + } + + public TestClientBuilder apiCallTimeout(Duration duration) { + this.apiCallTimeout = duration; + return this; + } + + public TestClientBuilder apiCallAttemptTimeout(Duration timeout) { + this.apiCallAttemptTimeout = timeout; + return this; + } + + public AmazonSyncHttpClient build() { + SdkHttpClient sdkHttpClient = Validate.paramNotNull(this.httpClient, "httpClient"); + return new AmazonSyncHttpClient(testClientConfiguration().toBuilder() + .option(SdkClientOption.SYNC_HTTP_CLIENT, sdkHttpClient) + .applyMutation(this::configureRetryStrategy) + .applyMutation(this::configureAdditionalHeaders) + .option(SdkClientOption.API_CALL_TIMEOUT, apiCallTimeout) + .option(SdkClientOption.API_CALL_ATTEMPT_TIMEOUT, + apiCallAttemptTimeout) + .build()); + } + + private void configureAdditionalHeaders(SdkClientConfiguration.Builder builder) { + Map> headers = + this.additionalHeaders.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> Arrays.asList(e.getValue()))); + + builder.option(SdkClientOption.ADDITIONAL_HTTP_HEADERS, headers); + } + + private void configureRetryStrategy(SdkClientConfiguration.Builder builder) { + if (retryStrategy != null) { + builder.option(SdkClientOption.RETRY_STRATEGY, retryStrategy); + } + } + } + + public static class TestAsyncClientBuilder { + private RetryStrategy retryStrategy; + private SdkAsyncHttpClient asyncHttpClient; + private Duration apiCallTimeout; + private Duration apiCallAttemptTimeout; + private Map additionalHeaders = new HashMap<>(); + + public TestAsyncClientBuilder retryStrategy(RetryStrategy retryStrategy) { + this.retryStrategy = retryStrategy; + return this; + } + + public TestAsyncClientBuilder asyncHttpClient(SdkAsyncHttpClient asyncHttpClient) { + this.asyncHttpClient = asyncHttpClient; + return this; + } + + public TestAsyncClientBuilder additionalHeader(String key, String value) { + this.additionalHeaders.put(key, value); + return this; + } + + public TestAsyncClientBuilder apiCallTimeout(Duration duration) { + this.apiCallTimeout = duration; + return this; + } + + public TestAsyncClientBuilder apiCallAttemptTimeout(Duration timeout) { + this.apiCallAttemptTimeout = timeout; + return this; + } + + public AmazonAsyncHttpClient build() { + SdkAsyncHttpClient asyncHttpClient = Validate.paramNotNull(this.asyncHttpClient, "asyncHttpClient"); + return new AmazonAsyncHttpClient(testClientConfiguration().toBuilder() + .option(SdkClientOption.ASYNC_HTTP_CLIENT, asyncHttpClient) + .option(SdkClientOption.API_CALL_TIMEOUT, apiCallTimeout) + .option(SdkClientOption.API_CALL_ATTEMPT_TIMEOUT, + apiCallAttemptTimeout) + .applyMutation(this::configureRetryStrategy) + .applyMutation(this::configureAdditionalHeaders) + .build()); + } + + private void configureAdditionalHeaders(SdkClientConfiguration.Builder builder) { + Map> headers = + this.additionalHeaders.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> Arrays.asList(e.getValue()))); + + builder.option(SdkClientOption.ADDITIONAL_HTTP_HEADERS, headers); + } + + private void configureRetryStrategy(SdkClientConfiguration.Builder builder) { + if (retryStrategy != null) { + builder.option(SdkClientOption.RETRY_STRATEGY, retryStrategy); + } + } + } +} diff --git a/test/http-client-tests/src/main/java/utils/ValidSdkObjects.java b/test/http-client-tests/src/main/java/utils/ValidSdkObjects.java new file mode 100644 index 000000000000..221ea778c1a8 --- /dev/null +++ b/test/http-client-tests/src/main/java/utils/ValidSdkObjects.java @@ -0,0 +1,71 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 utils; + +import java.net.URI; +import java.util.List; +import java.util.Optional; +import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkField; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpFullResponse; +import software.amazon.awssdk.http.SdkHttpMethod; + +/** + * A collection of objects (or object builder) pre-populated with all required fields. This allows tests to focus on what data + * they care about, not necessarily what data is required. + */ +public final class ValidSdkObjects { + private ValidSdkObjects() { + } + + public static SdkRequest sdkRequest() { + return new SdkRequest() { + @Override + public Optional overrideConfiguration() { + return Optional.empty(); + } + + @Override + public Builder toBuilder() { + return null; + } + + @Override + public List> sdkFields() { + return null; + } + }; + } + + public static SdkHttpFullRequest.Builder sdkHttpFullRequest() { + return sdkHttpFullRequest(80); + } + + public static SdkHttpFullRequest.Builder sdkHttpFullRequest(int port) { + return SdkHttpFullRequest.builder() + .uri(URI.create("http://localhost")) + .port(port) + .putHeader("Host", "localhost") + .method(SdkHttpMethod.GET); + } + + public static SdkHttpFullResponse.Builder sdkHttpFullResponse() { + return SdkHttpFullResponse.builder() + .statusCode(200); + } +} diff --git a/test/http-client-tests/src/main/java/utils/http/WireMockTestBase.java b/test/http-client-tests/src/main/java/utils/http/WireMockTestBase.java new file mode 100644 index 000000000000..1df3d1fea019 --- /dev/null +++ b/test/http-client-tests/src/main/java/utils/http/WireMockTestBase.java @@ -0,0 +1,62 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 utils.http; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import java.net.URI; +import org.junit.Rule; +import software.amazon.awssdk.core.exception.SdkServiceException; +import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpFullResponse; +import software.amazon.awssdk.http.SdkHttpMethod; + +/** + * Base class for tests that use a WireMock server + */ +public abstract class WireMockTestBase { + + @Rule + public WireMockRule mockServer = new WireMockRule(0); + + protected SdkHttpFullRequest.Builder newGetRequest(String resourcePath) { + return newRequest(resourcePath) + .method(SdkHttpMethod.GET); + } + + protected SdkHttpFullRequest.Builder newRequest(String resourcePath) { + return SdkHttpFullRequest.builder() + .uri(URI.create("http://localhost")) + .port(mockServer.port()) + .encodedPath(resourcePath); + } + + protected HttpResponseHandler stubErrorHandler() throws Exception { + HttpResponseHandler errorHandler = mock(HttpResponseHandler.class); + when(errorHandler.handle(any(SdkHttpFullResponse.class), any(ExecutionAttributes.class))).thenReturn(mockException()); + return errorHandler; + } + + private SdkServiceException mockException() { + SdkServiceException exception = SdkServiceException.builder().message("Dummy error response").statusCode(500).build(); + return exception; + } +}