http2ConfigurationBuilderConsumer);
+
+ /**
+ * Configure whether to use a non-blocking dns resolver or not. False by default, as netty's default dns resolver is
+ * blocking; it namely calls java.net.InetAddress.getByName.
+ *
+ * When enabled, a non-blocking dns resolver will be used instead, by modifying netty's bootstrap configuration.
+ * See https://netty.io/news/2016/05/26/4-1-0-Final.html
+ */
+ Builder useNonBlockingDnsResolver(boolean useNonBlockingDnsResolver);
}
/**
@@ -492,6 +502,7 @@ private static final class DefaultBuilder implements Builder {
private Http2Configuration http2Configuration;
private SslProvider sslProvider;
private ProxyConfiguration proxyConfiguration;
+ private boolean useNonBlockingDnsResolver;
private DefaultBuilder() {
}
@@ -716,6 +727,16 @@ public void setHttp2Configuration(Http2Configuration http2Configuration) {
http2Configuration(http2Configuration);
}
+ @Override
+ public Builder useNonBlockingDnsResolver(boolean useNonBlockingDnsResolver) {
+ this.useNonBlockingDnsResolver = useNonBlockingDnsResolver;
+ return this;
+ }
+
+ public void setUseNonBlockingDnsResolver(boolean useNonBlockingDnsResolver) {
+ useNonBlockingDnsResolver(useNonBlockingDnsResolver);
+ }
+
@Override
public SdkAsyncHttpClient buildWithDefaults(AttributeMap serviceDefaults) {
if (standardOptions.get(SdkHttpConfigurationOption.TLS_NEGOTIATION_TIMEOUT) == null) {
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/SdkEventLoopGroup.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/SdkEventLoopGroup.java
index abb665f2c39a..75cbee568cda 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/SdkEventLoopGroup.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/SdkEventLoopGroup.java
@@ -19,18 +19,20 @@
import io.netty.channel.ChannelFactory;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.DatagramChannel;
+import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.util.Optional;
import java.util.concurrent.ThreadFactory;
import software.amazon.awssdk.annotations.SdkPublicApi;
-import software.amazon.awssdk.http.nio.netty.internal.utils.SocketChannelResolver;
+import software.amazon.awssdk.http.nio.netty.internal.utils.ChannelResolver;
import software.amazon.awssdk.utils.ThreadFactoryBuilder;
import software.amazon.awssdk.utils.Validate;
/**
- * Provides {@link EventLoopGroup} and {@link ChannelFactory} for {@link NettyNioAsyncHttpClient}.
+ * Provides {@link EventLoopGroup}, {@link ChannelFactory} and {@link DatagramChannel} for {@link NettyNioAsyncHttpClient}.
*
- * There are three ways to create a new instance.
+ * There are four ways to create a new instance.
*
*
* - using {@link #builder()} to provide custom configuration of {@link EventLoopGroup}.
@@ -39,11 +41,15 @@
*
*
- Using {@link #create(EventLoopGroup)} to provide a custom {@link EventLoopGroup}. {@link ChannelFactory} will
* be resolved based on the type of {@link EventLoopGroup} provided via
- * {@link SocketChannelResolver#resolveSocketChannelFactory(EventLoopGroup)}
+ * {@link ChannelResolver#resolveSocketChannelFactory(EventLoopGroup)}. {@link DatagramChannel} will be resolved based
+ * on the type of {@link EventLoopGroup} provided via {@link ChannelResolver#resolveDatagramChannel(EventLoopGroup)}
*
*
- * - Using {@link #create(EventLoopGroup, ChannelFactory)} to provide a custom {@link EventLoopGroup} and
- * {@link ChannelFactory}
+ *
- Using {@link #create(EventLoopGroup, ChannelFactory)} to provide a custom
+ * {@link EventLoopGroup} and {@link ChannelFactory}
+ *
+ * - Using {@link #create(EventLoopGroup, ChannelFactory, Class)} to provide a custom
+ * {@link EventLoopGroup}, {@link ChannelFactory} and {@link DatagramChannel}
*
*
*
@@ -63,12 +69,23 @@ public final class SdkEventLoopGroup {
private final EventLoopGroup eventLoopGroup;
private final ChannelFactory extends Channel> channelFactory;
+ private final Class extends DatagramChannel> channelType;
SdkEventLoopGroup(EventLoopGroup eventLoopGroup, ChannelFactory extends Channel> channelFactory) {
Validate.paramNotNull(eventLoopGroup, "eventLoopGroup");
Validate.paramNotNull(channelFactory, "channelFactory");
this.eventLoopGroup = eventLoopGroup;
this.channelFactory = channelFactory;
+ this.channelType = resolveChannelType();
+ }
+
+ SdkEventLoopGroup(EventLoopGroup eventLoopGroup, ChannelFactory extends Channel> channelFactory,
+ Class extends DatagramChannel> channelType) {
+ Validate.paramNotNull(eventLoopGroup, "eventLoopGroup");
+ Validate.paramNotNull(channelFactory, "channelFactory");
+ this.eventLoopGroup = eventLoopGroup;
+ this.channelFactory = channelFactory;
+ this.channelType = channelType;
}
/**
@@ -77,6 +94,7 @@ public final class SdkEventLoopGroup {
private SdkEventLoopGroup(DefaultBuilder builder) {
this.eventLoopGroup = resolveEventLoopGroup(builder);
this.channelFactory = resolveChannelFactory();
+ this.channelType = resolveChannelType();
}
/**
@@ -93,6 +111,13 @@ public ChannelFactory extends Channel> channelFactory() {
return channelFactory;
}
+ /**
+ * @return the {@link ChannelFactory} to be used with Netty Http Client.
+ */
+ public Class extends DatagramChannel> channelType() {
+ return channelType;
+ }
+
/**
* Creates a new instance of SdkEventLoopGroup with {@link EventLoopGroup} and {@link ChannelFactory}
* to be used with {@link NettyNioAsyncHttpClient}.
@@ -105,6 +130,20 @@ public static SdkEventLoopGroup create(EventLoopGroup eventLoopGroup, ChannelFac
return new SdkEventLoopGroup(eventLoopGroup, channelFactory);
}
+ /**
+ * Creates a new instance of SdkEventLoopGroup with {@link EventLoopGroup}, {@link ChannelFactory}
+ * and {@link DatagramChannel} to be used with {@link NettyNioAsyncHttpClient}.
+ *
+ * @param eventLoopGroup the EventLoopGroup to be used
+ * @param channelFactory the channel factor to be used
+ * @param channelType the channel type to be used
+ * @return a new instance of SdkEventLoopGroup
+ */
+ public static SdkEventLoopGroup create(EventLoopGroup eventLoopGroup, ChannelFactory extends Channel> channelFactory,
+ Class extends DatagramChannel> channelType) {
+ return new SdkEventLoopGroup(eventLoopGroup, channelFactory, channelType);
+ }
+
/**
* Creates a new instance of SdkEventLoopGroup with {@link EventLoopGroup}.
*
@@ -116,7 +155,8 @@ public static SdkEventLoopGroup create(EventLoopGroup eventLoopGroup, ChannelFac
* @return a new instance of SdkEventLoopGroup
*/
public static SdkEventLoopGroup create(EventLoopGroup eventLoopGroup) {
- return create(eventLoopGroup, SocketChannelResolver.resolveSocketChannelFactory(eventLoopGroup));
+ return create(eventLoopGroup, ChannelResolver.resolveSocketChannelFactory(eventLoopGroup),
+ ChannelResolver.resolveDatagramChannel(eventLoopGroup));
}
public static Builder builder() {
@@ -146,6 +186,10 @@ private ChannelFactory extends Channel> resolveChannelFactory() {
return NioSocketChannel::new;
}
+ private Class extends DatagramChannel> resolveChannelType() {
+ return NioDatagramChannel.class;
+ }
+
/**
* A builder for {@link SdkEventLoopGroup}.
*
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java
index 1d55e1841aa2..c86c46da87e7 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java
@@ -83,6 +83,7 @@ public void channelCreated(Channel ch) throws Exception {
private final ProxyConfiguration proxyConfiguration;
private final BootstrapProvider bootstrapProvider;
private final SslContextProvider sslContextProvider;
+ private final boolean useNonBlockingDnsResolver;
private AwaitCloseChannelPoolMap(Builder builder, Function createBootStrapProvider) {
this.configuration = builder.configuration;
@@ -94,6 +95,7 @@ private AwaitCloseChannelPoolMap(Builder builder, Function init(Class extends DatagramChannel> channelType) {
+ try {
+ Class> addressResolver = ClassLoaderHelper.loadClass(getAddressResolverGroup(), false, (Class) null);
+ Class> dnsNameResolverBuilder = ClassLoaderHelper.loadClass(getDnsNameResolverBuilder(), false, (Class) null);
+
+ Object dnsResolverObj = dnsNameResolverBuilder.newInstance();
+ Method method = dnsResolverObj.getClass().getMethod("channelType", Class.class);
+ method.invoke(dnsResolverObj, channelType);
+
+ Object e = addressResolver.getConstructor(dnsNameResolverBuilder).newInstance(dnsResolverObj);
+ return (AddressResolverGroup) e;
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException("Cannot find module io.netty.resolver.dns "
+ + " To use netty non blocking dns," +
+ " the 'netty-resolver-dns' module from io.netty must be on the class path. ", e);
+ } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
+ throw new IllegalStateException("Failed to create AddressResolverGroup", e);
+ }
+ }
+
+ private static String getAddressResolverGroup() {
+ return "io.netty.resolver.dns.DnsAddressResolverGroup";
+ }
+
+ private static String getDnsNameResolverBuilder() {
+ return "io.netty.resolver.dns.DnsNameResolverBuilder";
+ }
+}
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/SharedSdkEventLoopGroup.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/SharedSdkEventLoopGroup.java
index f316b77f3d8b..b6b30ec412d7 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/SharedSdkEventLoopGroup.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/SharedSdkEventLoopGroup.java
@@ -59,7 +59,7 @@ public static synchronized SdkEventLoopGroup get() {
referenceCount++;
return SdkEventLoopGroup.create(new ReferenceCountingEventLoopGroup(sharedSdkEventLoopGroup.eventLoopGroup()),
- sharedSdkEventLoopGroup.channelFactory());
+ sharedSdkEventLoopGroup.channelFactory(), sharedSdkEventLoopGroup.channelType());
}
/**
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/utils/SocketChannelResolver.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/utils/ChannelResolver.java
similarity index 50%
rename from http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/utils/SocketChannelResolver.java
rename to http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/utils/ChannelResolver.java
index 1d80dad5850f..0c8f512f2f17 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/utils/SocketChannelResolver.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/utils/ChannelResolver.java
@@ -21,9 +21,12 @@
import io.netty.channel.ChannelFactory;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ReflectiveChannelFactory;
+import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.DatagramChannel;
+import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.util.HashMap;
import java.util.Map;
@@ -31,16 +34,24 @@
import software.amazon.awssdk.http.nio.netty.internal.DelegatingEventLoopGroup;
@SdkInternalApi
-public final class SocketChannelResolver {
+public final class ChannelResolver {
- private static final Map KNOWN_EL_GROUPS = new HashMap<>();
+ private static final Map KNOWN_EL_GROUPS_CHANNELS = new HashMap<>();
+ private static final Map KNOWN_EL_GROUPS_DATAGRAMS = new HashMap<>();
static {
- KNOWN_EL_GROUPS.put("io.netty.channel.kqueue.KQueueEventLoopGroup", "io.netty.channel.kqueue.KQueueSocketChannel");
- KNOWN_EL_GROUPS.put("io.netty.channel.oio.OioEventLoopGroup", "io.netty.channel.socket.oio.OioSocketChannel");
+ KNOWN_EL_GROUPS_CHANNELS.put("io.netty.channel.kqueue.KQueueEventLoopGroup",
+ "io.netty.channel.kqueue.KQueueSocketChannel");
+ KNOWN_EL_GROUPS_CHANNELS.put("io.netty.channel.oio.OioEventLoopGroup",
+ "io.netty.channel.socket.oio.OioSocketChannel");
+
+ KNOWN_EL_GROUPS_DATAGRAMS.put("io.netty.channel.kqueue.KQueueEventLoopGroup",
+ "io.netty.channel.kqueue.KQueueDatagramChannel");
+ KNOWN_EL_GROUPS_DATAGRAMS.put("io.netty.channel.oio.OioEventLoopGroup",
+ "io.netty.channel.socket.oio.OioDatagramChannel");
}
- private SocketChannelResolver() {
+ private ChannelResolver() {
}
/**
@@ -63,11 +74,39 @@ public static ChannelFactory extends Channel> resolveSocketChannelFactory(Even
return EpollSocketChannel::new;
}
- String socketFqcn = KNOWN_EL_GROUPS.get(eventLoopGroup.getClass().getName());
+ String socketFqcn = KNOWN_EL_GROUPS_CHANNELS.get(eventLoopGroup.getClass().getName());
if (socketFqcn == null) {
throw new IllegalArgumentException("Unknown event loop group : " + eventLoopGroup.getClass());
}
return invokeSafely(() -> new ReflectiveChannelFactory(Class.forName(socketFqcn)));
}
+
+ /**
+ * Attempts to determine the {@link DatagramChannel} class that corresponds to the given
+ * event loop group.
+ *
+ * @param eventLoopGroup the event loop group to determine the {@link ChannelFactory} for
+ * @return A {@link DatagramChannel} class for the given event loop group.
+ */
+ @SuppressWarnings("unchecked")
+ public static Class extends DatagramChannel> resolveDatagramChannel(EventLoopGroup eventLoopGroup) {
+ if (eventLoopGroup instanceof DelegatingEventLoopGroup) {
+ return resolveDatagramChannel(((DelegatingEventLoopGroup) eventLoopGroup).getDelegate());
+ }
+
+ if (eventLoopGroup instanceof NioEventLoopGroup) {
+ return NioDatagramChannel.class;
+ }
+ if (eventLoopGroup instanceof EpollEventLoopGroup) {
+ return EpollDatagramChannel.class;
+ }
+
+ String datagramClass = KNOWN_EL_GROUPS_DATAGRAMS.get(eventLoopGroup.getClass().getName());
+ if (datagramClass == null) {
+ throw new IllegalArgumentException("Unknown event loop group : " + eventLoopGroup.getClass());
+ }
+
+ return invokeSafely(() -> (Class extends DatagramChannel>) Class.forName(datagramClass));
+ }
}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyClientTlsAuthTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyClientTlsAuthTest.java
index dc7c408c3c9f..f35c0914609d 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyClientTlsAuthTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyClientTlsAuthTest.java
@@ -39,6 +39,7 @@
import software.amazon.awssdk.http.EmptyPublisher;
import software.amazon.awssdk.http.FileStoreTlsKeyManagersProvider;
import software.amazon.awssdk.http.HttpTestUtils;
+import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.TlsKeyManagersProvider;
@@ -185,6 +186,24 @@ public void nonProxy_noKeyManagerGiven_shouldThrowException() {
.hasRootCauseInstanceOf(SSLException.class);
}
+ @Test
+ public void builderUsesProvidedKeyManagersProviderNonBlockingDns() {
+ TlsKeyManagersProvider mockKeyManagersProvider = mock(TlsKeyManagersProvider.class);
+ netty = NettyNioAsyncHttpClient.builder()
+ .useNonBlockingDnsResolver(true)
+ .proxyConfiguration(proxyCfg)
+ .tlsKeyManagersProvider(mockKeyManagersProvider)
+ .buildWithDefaults(AttributeMap.builder()
+ .put(TRUST_ALL_CERTIFICATES, true)
+ .build());
+
+ try {
+ sendRequest(netty, new RecordingResponseHandler());
+ } catch (Exception ignored) {
+ }
+ verify(mockKeyManagersProvider).keyManagers();
+ }
+
private void sendRequest(SdkAsyncHttpClient client, SdkAsyncHttpResponseHandler responseHandler) {
AsyncExecuteRequest req = AsyncExecuteRequest.builder()
.request(testSdkRequest())
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientNonBlockingDnsTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientNonBlockingDnsTest.java
new file mode 100644
index 000000000000..9535c41c2b0a
--- /dev/null
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientNonBlockingDnsTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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 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.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
+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.verify;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static java.util.Collections.singletonMap;
+import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang3.StringUtils.reverse;
+import static org.assertj.core.api.Assertions.assertThat;
+import static software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClientTestUtils.assertCanReceiveBasicRequest;
+import static software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClientTestUtils.createProvider;
+import static software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClientTestUtils.createRequest;
+import static software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClientTestUtils.makeSimpleRequest;
+
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import java.io.IOException;
+import java.net.URI;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.assertj.core.api.Condition;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+import software.amazon.awssdk.http.SdkHttpConfigurationOption;
+import software.amazon.awssdk.http.SdkHttpFullRequest;
+import software.amazon.awssdk.http.SdkHttpMethod;
+import software.amazon.awssdk.http.SdkHttpRequest;
+import software.amazon.awssdk.http.async.AsyncExecuteRequest;
+import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
+import software.amazon.awssdk.utils.AttributeMap;
+
+@RunWith(MockitoJUnitRunner.class)
+public class NettyNioAsyncHttpClientNonBlockingDnsTest {
+
+ private final RecordingNetworkTrafficListener wiremockTrafficListener = new RecordingNetworkTrafficListener();
+
+ private static final SdkAsyncHttpClient client = NettyNioAsyncHttpClient.builder()
+ .useNonBlockingDnsResolver(true)
+ .buildWithDefaults(
+ AttributeMap.builder()
+ .put(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES, true)
+ .build());
+ @Rule
+ public WireMockRule mockServer = new WireMockRule(wireMockConfig()
+ .dynamicPort()
+ .dynamicHttpsPort()
+ .networkTrafficListener(wiremockTrafficListener));
+
+ @Before
+ public void methodSetup() {
+ wiremockTrafficListener.reset();
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ client.close();
+ }
+
+ @Test
+ public void canSendContentAndGetThatContentBackNonBlockingDns() throws Exception {
+ String body = randomAlphabetic(50);
+ stubFor(any(urlEqualTo("/echo?reversed=true"))
+ .withRequestBody(equalTo(body))
+ .willReturn(aResponse().withBody(reverse(body))));
+ URI uri = URI.create("http://localhost:" + mockServer.port());
+
+ SdkHttpRequest request = createRequest(uri, "/echo", body, SdkHttpMethod.POST, singletonMap("reversed", "true"));
+
+ RecordingResponseHandler recorder = new RecordingResponseHandler();
+
+ client.execute(AsyncExecuteRequest.builder().request(request).requestContentPublisher(createProvider(body)).responseHandler(recorder).build());
+
+ recorder.completeFuture.get(5, TimeUnit.SECONDS);
+
+ verify(1, postRequestedFor(urlEqualTo("/echo?reversed=true")));
+
+ assertThat(recorder.fullResponseAsString()).isEqualTo(reverse(body));
+ }
+
+ @Test
+ public void defaultThreadFactoryUsesHelpfulName() throws Exception {
+ // Make a request to ensure a thread is primed
+ makeSimpleRequest(client, mockServer);
+
+ String expectedPattern = "aws-java-sdk-NettyEventLoop-\\d+-\\d+";
+ assertThat(Thread.getAllStackTraces().keySet())
+ .areAtLeast(1, new Condition<>(t -> t.getName().matches(expectedPattern),
+ "Matches default thread pattern: `%s`", expectedPattern));
+ }
+
+ @Test
+ public void canMakeBasicRequestOverHttp() throws Exception {
+ String smallBody = randomAlphabetic(10);
+ URI uri = URI.create("http://localhost:" + mockServer.port());
+
+ assertCanReceiveBasicRequest(client, uri, smallBody);
+ }
+
+ @Test
+ public void canMakeBasicRequestOverHttps() throws Exception {
+ String smallBody = randomAlphabetic(10);
+ URI uri = URI.create("https://localhost:" + mockServer.httpsPort());
+
+ assertCanReceiveBasicRequest(client, uri, smallBody);
+ }
+
+ @Test
+ public void canHandleLargerPayloadsOverHttp() throws Exception {
+ String largishBody = randomAlphabetic(25000);
+
+ URI uri = URI.create("http://localhost:" + mockServer.port());
+
+ assertCanReceiveBasicRequest(client, uri, largishBody);
+ }
+
+ @Test
+ public void canHandleLargerPayloadsOverHttps() throws Exception {
+ String largishBody = randomAlphabetic(25000);
+
+ URI uri = URI.create("https://localhost:" + mockServer.httpsPort());
+
+ assertCanReceiveBasicRequest(client, uri, largishBody);
+ }
+
+ @Test
+ public void requestContentOnlyEqualToContentLengthHeaderFromProvider() throws InterruptedException, ExecutionException, TimeoutException, IOException {
+ final String content = randomAlphabetic(32);
+ final String streamContent = content + reverse(content);
+ stubFor(any(urlEqualTo("/echo?reversed=true"))
+ .withRequestBody(equalTo(content))
+ .willReturn(aResponse().withBody(reverse(content))));
+ URI uri = URI.create("http://localhost:" + mockServer.port());
+
+ SdkHttpFullRequest request = createRequest(uri, "/echo", streamContent, SdkHttpMethod.POST, singletonMap("reversed", "true"));
+ request = request.toBuilder().putHeader("Content-Length", Integer.toString(content.length())).build();
+ RecordingResponseHandler recorder = new RecordingResponseHandler();
+
+ client.execute(AsyncExecuteRequest.builder().request(request).requestContentPublisher(createProvider(streamContent)).responseHandler(recorder).build());
+
+ recorder.completeFuture.get(5, TimeUnit.SECONDS);
+
+ // HTTP servers will stop processing the request as soon as it reads
+ // bytes equal to 'Content-Length' so we need to inspect the raw
+ // traffic to ensure that there wasn't anything after that.
+ assertThat(wiremockTrafficListener.requests().toString()).endsWith(content);
+ }
+}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientTestUtils.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientTestUtils.java
new file mode 100644
index 000000000000..04f9a906ee04
--- /dev/null
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientTestUtils.java
@@ -0,0 +1,148 @@
+/*
+ * 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 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.stubFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.verify;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Collections.emptyMap;
+import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import software.amazon.awssdk.http.SdkHttpFullRequest;
+import software.amazon.awssdk.http.SdkHttpMethod;
+import software.amazon.awssdk.http.SdkHttpRequest;
+import software.amazon.awssdk.http.async.AsyncExecuteRequest;
+import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
+import software.amazon.awssdk.http.async.SdkHttpContentPublisher;
+
+public class NettyNioAsyncHttpClientTestUtils {
+
+ /**
+ * Make a simple async request and wait for it to fiish.
+ *
+ * @param client Client to make request with.
+ */
+ public static void makeSimpleRequest(SdkAsyncHttpClient client, WireMockServer mockServer) throws Exception {
+ String body = randomAlphabetic(10);
+ URI uri = URI.create("http://localhost:" + mockServer.port());
+ stubFor(any(urlPathEqualTo("/")).willReturn(aResponse().withBody(body)));
+ SdkHttpRequest request = createRequest(uri);
+ RecordingResponseHandler recorder = new RecordingResponseHandler();
+ client.execute(AsyncExecuteRequest.builder().request(request).requestContentPublisher(createProvider("")).responseHandler(recorder).build());
+ recorder.completeFuture.get(5, TimeUnit.SECONDS);
+ }
+
+ public static SdkHttpContentPublisher createProvider(String body) {
+ Stream chunks = splitStringBySize(body).stream()
+ .map(chunk -> ByteBuffer.wrap(chunk.getBytes(UTF_8)));
+ return new SdkHttpContentPublisher() {
+
+ @Override
+ public Optional contentLength() {
+ return Optional.of(Long.valueOf(body.length()));
+ }
+
+ @Override
+ public void subscribe(Subscriber super ByteBuffer> s) {
+ s.onSubscribe(new Subscription() {
+ @Override
+ public void request(long n) {
+ chunks.forEach(s::onNext);
+ s.onComplete();
+ }
+
+ @Override
+ public void cancel() {
+
+ }
+ });
+ }
+ };
+ }
+
+ public static SdkHttpFullRequest createRequest(URI uri) {
+ return createRequest(uri, "/", null, SdkHttpMethod.GET, emptyMap());
+ }
+
+ public static SdkHttpFullRequest createRequest(URI uri,
+ String resourcePath,
+ String body,
+ SdkHttpMethod method,
+ Map params) {
+ String contentLength = body == null ? null : String.valueOf(body.getBytes(UTF_8).length);
+ return SdkHttpFullRequest.builder()
+ .uri(uri)
+ .method(method)
+ .encodedPath(resourcePath)
+ .applyMutation(b -> params.forEach(b::putRawQueryParameter))
+ .applyMutation(b -> {
+ b.putHeader("Host", uri.getHost());
+ if (contentLength != null) {
+ b.putHeader("Content-Length", contentLength);
+ }
+ }).build();
+ }
+
+ public static void assertCanReceiveBasicRequest(SdkAsyncHttpClient client, URI uri, String body) throws Exception {
+ stubFor(any(urlPathEqualTo("/")).willReturn(aResponse().withHeader("Some-Header", "With Value").withBody(body)));
+
+ SdkHttpRequest request = createRequest(uri);
+
+ RecordingResponseHandler recorder = new RecordingResponseHandler();
+ client.execute(AsyncExecuteRequest.builder().request(request).requestContentPublisher(createProvider("")).responseHandler(recorder).build());
+
+ recorder.completeFuture.get(5, TimeUnit.SECONDS);
+
+ assertThat(recorder.responses).hasOnlyOneElementSatisfying(
+ headerResponse -> {
+ assertThat(headerResponse.headers()).containsKey("Some-Header");
+ assertThat(headerResponse.statusCode()).isEqualTo(200);
+ });
+
+ assertThat(recorder.fullResponseAsString()).isEqualTo(body);
+ verify(1, getRequestedFor(urlMatching("/")));
+ }
+
+ private static Collection splitStringBySize(String str) {
+ if (isBlank(str)) {
+ return Collections.emptyList();
+ }
+ ArrayList split = new ArrayList<>();
+ for (int i = 0; i <= str.length() / 1000; i++) {
+ split.add(str.substring(i * 1000, Math.min((i + 1) * 1000, str.length())));
+ }
+ return split;
+ }
+}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientWireMockTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientWireMockTest.java
index 9a1121e201f5..79dee34357e2 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientWireMockTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClientWireMockTest.java
@@ -18,19 +18,14 @@
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.equalTo;
-import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
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.urlMatching;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
-import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.reverse;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -40,6 +35,10 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
+import static software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClientTestUtils.assertCanReceiveBasicRequest;
+import static software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClientTestUtils.createProvider;
+import static software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClientTestUtils.createRequest;
+import static software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClientTestUtils.makeSimpleRequest;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.http.Fault;
@@ -49,25 +48,22 @@
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.DatagramChannel;
+import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslProvider;
import io.netty.util.AttributeKey;
import java.io.IOException;
import java.net.URI;
-import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.stream.Stream;
import javax.net.ssl.TrustManagerFactory;
import org.assertj.core.api.Condition;
import org.junit.AfterClass;
@@ -78,8 +74,6 @@
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
-import org.reactivestreams.Subscriber;
-import org.reactivestreams.Subscription;
import software.amazon.awssdk.http.HttpMetric;
import software.amazon.awssdk.http.HttpTestUtils;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
@@ -88,7 +82,6 @@
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.async.AsyncExecuteRequest;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
-import software.amazon.awssdk.http.async.SdkHttpContentPublisher;
import software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration;
import software.amazon.awssdk.http.nio.netty.internal.SdkChannelPool;
import software.amazon.awssdk.http.nio.netty.internal.SdkChannelPoolMap;
@@ -183,7 +176,8 @@ public void invalidMaxPendingConnectionAcquireConfig_shouldPropagateException()
.maxConcurrency(1)
.maxPendingConnectionAcquires(0)
.build()) {
- assertThatThrownBy(() -> makeSimpleRequest(customClient)).hasMessageContaining("java.lang.IllegalArgumentException: maxPendingAcquires: 0 (expected: >= 1)");
+ assertThatThrownBy(() -> makeSimpleRequest(customClient, mockServer)).hasMessageContaining("java.lang"
+ + ".IllegalArgumentException: maxPendingAcquires: 0 (expected: >= 1)");
}
}
@@ -196,7 +190,7 @@ public void customFactoryIsUsed() throws Exception {
.threadFactory(threadFactory))
.build();
- makeSimpleRequest(customClient);
+ makeSimpleRequest(customClient, mockServer);
customClient.close();
Mockito.verify(threadFactory, atLeastOnce()).newThread(Mockito.any());
@@ -208,7 +202,7 @@ public void openSslBeingUsed() throws Exception {
NettyNioAsyncHttpClient.builder()
.sslProvider(SslProvider.OPENSSL)
.build()) {
- makeSimpleRequest(customClient);
+ makeSimpleRequest(customClient, mockServer);
}
}
@@ -218,7 +212,7 @@ public void defaultJdkSslProvider() throws Exception {
NettyNioAsyncHttpClient.builder()
.sslProvider(SslProvider.JDK)
.build()) {
- makeSimpleRequest(customClient);
+ makeSimpleRequest(customClient, mockServer);
customClient.close();
}
}
@@ -226,7 +220,7 @@ public void defaultJdkSslProvider() throws Exception {
@Test
public void defaultThreadFactoryUsesHelpfulName() throws Exception {
// Make a request to ensure a thread is primed
- makeSimpleRequest(client);
+ makeSimpleRequest(client, mockServer);
String expectedPattern = "aws-java-sdk-NettyEventLoop-\\d+-\\d+";
assertThat(Thread.getAllStackTraces().keySet())
@@ -247,7 +241,7 @@ public void customThreadCountIsRespected() throws Exception {
// Have to make enough requests to prime the threads
for (int i = 0; i < threadCount + 1; i++) {
- makeSimpleRequest(customClient);
+ makeSimpleRequest(customClient, mockServer);
}
customClient.close();
@@ -264,10 +258,10 @@ public void customEventLoopGroup_NotClosedWhenClientIsClosed() throws Exception
EventLoopGroup eventLoopGroup = spy(new NioEventLoopGroup(0, threadFactory));
SdkAsyncHttpClient customClient =
NettyNioAsyncHttpClient.builder()
- .eventLoopGroup(SdkEventLoopGroup.create(eventLoopGroup, NioSocketChannel::new))
+ .eventLoopGroup(SdkEventLoopGroup.create(eventLoopGroup, NioSocketChannel::new, NioDatagramChannel.class))
.build();
- makeSimpleRequest(customClient);
+ makeSimpleRequest(customClient, mockServer);
customClient.close();
Mockito.verify(threadFactory, atLeastOnce()).newThread(Mockito.any());
@@ -284,10 +278,10 @@ public void customChannelFactoryIsUsed() throws Exception {
SdkAsyncHttpClient customClient =
NettyNioAsyncHttpClient.builder()
- .eventLoopGroup(SdkEventLoopGroup.create(customEventLoopGroup, channelFactory))
+ .eventLoopGroup(SdkEventLoopGroup.create(customEventLoopGroup, channelFactory, NioDatagramChannel.class))
.build();
- makeSimpleRequest(customClient);
+ makeSimpleRequest(customClient, mockServer);
customClient.close();
Mockito.verify(channelFactory, atLeastOnce()).newChannel();
@@ -327,7 +321,7 @@ public void responseConnectionReused_shouldReleaseChannel() throws Exception {
NioSocketChannel channel = new NioSocketChannel();
when(channelFactory.newChannel()).thenAnswer((Answer) invocationOnMock -> channel);
- SdkEventLoopGroup eventLoopGroup = SdkEventLoopGroup.create(customEventLoopGroup, channelFactory);
+ SdkEventLoopGroup eventLoopGroup = SdkEventLoopGroup.create(customEventLoopGroup, channelFactory, NioDatagramChannel.class);
NettyNioAsyncHttpClient customClient =
(NettyNioAsyncHttpClient) NettyNioAsyncHttpClient.builder()
@@ -335,7 +329,7 @@ public void responseConnectionReused_shouldReleaseChannel() throws Exception {
.maxConcurrency(1)
.build();
- makeSimpleRequest(customClient);
+ makeSimpleRequest(customClient, mockServer);
verifyChannelRelease(channel);
assertThat(channel.isShutdown()).isFalse();
@@ -351,7 +345,7 @@ public void connectionInactive_shouldReleaseChannel() throws Exception {
NioSocketChannel channel = new NioSocketChannel();
when(channelFactory.newChannel()).thenAnswer((Answer) invocationOnMock -> channel);
- SdkEventLoopGroup eventLoopGroup = SdkEventLoopGroup.create(customEventLoopGroup, channelFactory);
+ SdkEventLoopGroup eventLoopGroup = SdkEventLoopGroup.create(customEventLoopGroup, channelFactory, NioDatagramChannel.class);
NettyNioAsyncHttpClient customClient =
(NettyNioAsyncHttpClient) NettyNioAsyncHttpClient.builder()
@@ -395,7 +389,7 @@ public void responseConnectionClosed_shouldCloseAndReleaseChannel() throws Excep
SdkHttpRequest request = createRequest(uri);
RecordingResponseHandler recorder = new RecordingResponseHandler();
- SdkEventLoopGroup eventLoopGroup = SdkEventLoopGroup.create(customEventLoopGroup, channelFactory);
+ SdkEventLoopGroup eventLoopGroup = SdkEventLoopGroup.create(customEventLoopGroup, channelFactory, NioDatagramChannel.class);
NettyNioAsyncHttpClient customClient =
(NettyNioAsyncHttpClient) NettyNioAsyncHttpClient.builder()
@@ -446,27 +440,12 @@ public void builderUsesProvidedTrustManagersProvider() throws Exception {
}
}
- /**
- * Make a simple async request and wait for it to fiish.
- *
- * @param client Client to make request with.
- */
- private void makeSimpleRequest(SdkAsyncHttpClient client) throws Exception {
- String body = randomAlphabetic(10);
- URI uri = URI.create("http://localhost:" + mockServer.port());
- stubFor(any(urlPathEqualTo("/")).willReturn(aResponse().withBody(body)));
- SdkHttpRequest request = createRequest(uri);
- RecordingResponseHandler recorder = new RecordingResponseHandler();
- client.execute(AsyncExecuteRequest.builder().request(request).requestContentPublisher(createProvider("")).responseHandler(recorder).build());
- recorder.completeFuture.get(5, TimeUnit.SECONDS);
- }
-
@Test
public void canMakeBasicRequestOverHttp() throws Exception {
String smallBody = randomAlphabetic(10);
URI uri = URI.create("http://localhost:" + mockServer.port());
- assertCanReceiveBasicRequest(uri, smallBody);
+ assertCanReceiveBasicRequest(client, uri, smallBody);
}
@Test
@@ -474,7 +453,7 @@ public void canMakeBasicRequestOverHttps() throws Exception {
String smallBody = randomAlphabetic(10);
URI uri = URI.create("https://localhost:" + mockServer.httpsPort());
- assertCanReceiveBasicRequest(uri, smallBody);
+ assertCanReceiveBasicRequest(client, uri, smallBody);
}
@Test
@@ -483,7 +462,7 @@ public void canHandleLargerPayloadsOverHttp() throws Exception {
URI uri = URI.create("http://localhost:" + mockServer.port());
- assertCanReceiveBasicRequest(uri, largishBody);
+ assertCanReceiveBasicRequest(client, uri, largishBody);
}
@Test
@@ -492,7 +471,7 @@ public void canHandleLargerPayloadsOverHttps() throws Exception {
URI uri = URI.create("https://localhost:" + mockServer.httpsPort());
- assertCanReceiveBasicRequest(uri, largishBody);
+ assertCanReceiveBasicRequest(client, uri, largishBody);
}
@Test
@@ -563,7 +542,7 @@ public ChannelFuture close() {
};
SdkAsyncHttpClient customClient = NettyNioAsyncHttpClient.builder()
- .eventLoopGroup(new SdkEventLoopGroup(new NioEventLoopGroup(1), channelFactory))
+ .eventLoopGroup(new SdkEventLoopGroup(new NioEventLoopGroup(1), channelFactory, NioDatagramChannel.class))
.buildWithDefaults(mapWithTrustAllCerts());
try {
@@ -579,88 +558,6 @@ public ChannelFuture close() {
assertThat(channelClosedFuture.get(5, TimeUnit.SECONDS)).isTrue();
}
- private void assertCanReceiveBasicRequest(URI uri, String body) throws Exception {
- stubFor(any(urlPathEqualTo("/")).willReturn(aResponse().withHeader("Some-Header", "With Value").withBody(body)));
-
- SdkHttpRequest request = createRequest(uri);
-
- RecordingResponseHandler recorder = new RecordingResponseHandler();
- client.execute(AsyncExecuteRequest.builder().request(request).requestContentPublisher(createProvider("")).responseHandler(recorder).build());
-
- recorder.completeFuture.get(5, TimeUnit.SECONDS);
-
- assertThat(recorder.responses).hasOnlyOneElementSatisfying(
- headerResponse -> {
- assertThat(headerResponse.headers()).containsKey("Some-Header");
- assertThat(headerResponse.statusCode()).isEqualTo(200);
- });
-
- assertThat(recorder.fullResponseAsString()).isEqualTo(body);
- verify(1, getRequestedFor(urlMatching("/")));
- }
-
- private SdkHttpContentPublisher createProvider(String body) {
- Stream chunks = splitStringBySize(body).stream()
- .map(chunk -> ByteBuffer.wrap(chunk.getBytes(UTF_8)));
- return new SdkHttpContentPublisher() {
-
- @Override
- public Optional contentLength() {
- return Optional.of(Long.valueOf(body.length()));
- }
-
- @Override
- public void subscribe(Subscriber super ByteBuffer> s) {
- s.onSubscribe(new Subscription() {
- @Override
- public void request(long n) {
- chunks.forEach(s::onNext);
- s.onComplete();
- }
-
- @Override
- public void cancel() {
-
- }
- });
- }
- };
- }
-
- private SdkHttpFullRequest createRequest(URI uri) {
- return createRequest(uri, "/", null, SdkHttpMethod.GET, emptyMap());
- }
-
- private SdkHttpFullRequest createRequest(URI uri,
- String resourcePath,
- String body,
- SdkHttpMethod method,
- Map params) {
- String contentLength = body == null ? null : String.valueOf(body.getBytes(UTF_8).length);
- return SdkHttpFullRequest.builder()
- .uri(uri)
- .method(method)
- .encodedPath(resourcePath)
- .applyMutation(b -> params.forEach(b::putRawQueryParameter))
- .applyMutation(b -> {
- b.putHeader("Host", uri.getHost());
- if (contentLength != null) {
- b.putHeader("Content-Length", contentLength);
- }
- }).build();
- }
-
- private static Collection splitStringBySize(String str) {
- if (isBlank(str)) {
- return Collections.emptyList();
- }
- ArrayList split = new ArrayList<>();
- for (int i = 0; i <= str.length() / 1000; i++) {
- split.add(str.substring(i * 1000, Math.min((i + 1) * 1000, str.length())));
- }
- return split;
- }
-
// Needs to be a non-anon class in order to spy
public static class CustomThreadFactory implements ThreadFactory {
@Override
@@ -719,7 +616,7 @@ public void createNettyClient_ReadWriteTimeoutCanBeZero() throws Exception {
.writeTimeout(Duration.ZERO)
.build();
- makeSimpleRequest(customClient);
+ makeSimpleRequest(customClient, mockServer);
customClient.close();
}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/ProxyWireMockTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/ProxyWireMockTest.java
index f797a760fdf7..438d65e1f9fc 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/ProxyWireMockTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/ProxyWireMockTest.java
@@ -126,6 +126,30 @@ public void proxyConfigured_hostInNonProxySet_doesNotConnect() {
assertThat(responseHandler.fullResponseAsString()).isEqualTo("hello");
}
+ @Test
+ public void proxyConfigured_hostInNonProxySet_nonBlockingDns_doesNotConnect() {
+ RecordingResponseHandler responseHandler = new RecordingResponseHandler();
+ AsyncExecuteRequest req = AsyncExecuteRequest.builder()
+ .request(testSdkRequest())
+ .responseHandler(responseHandler)
+ .requestContentPublisher(new EmptyPublisher())
+ .build();
+
+ ProxyConfiguration cfg = proxyCfg.toBuilder()
+ .nonProxyHosts(Stream.of("localhost").collect(Collectors.toSet()))
+ .build();
+
+ client = NettyNioAsyncHttpClient.builder()
+ .proxyConfiguration(cfg)
+ .useNonBlockingDnsResolver(true)
+ .build();
+
+ client.execute(req).join();
+
+ responseHandler.completeFuture.join();
+ assertThat(responseHandler.fullResponseAsString()).isEqualTo("hello");
+ }
+
private SdkHttpFullRequest testSdkRequest() {
return SdkHttpFullRequest.builder()
.method(SdkHttpMethod.GET)
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/SdkEventLoopGroupTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/SdkEventLoopGroupTest.java
index a3ae76469359..a47eeee007f8 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/SdkEventLoopGroupTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/SdkEventLoopGroupTest.java
@@ -19,6 +19,7 @@
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.junit.Test;
@@ -28,13 +29,23 @@ public class SdkEventLoopGroupTest {
public void creatingUsingBuilder() {
SdkEventLoopGroup sdkEventLoopGroup = SdkEventLoopGroup.builder().numberOfThreads(1).build();
assertThat(sdkEventLoopGroup.channelFactory()).isNotNull();
+ assertThat(sdkEventLoopGroup.channelType()).isNotNull();
assertThat(sdkEventLoopGroup.eventLoopGroup()).isNotNull();
}
@Test
- public void creatingUsingStaticMethod() {
+ public void creatingUsingStaticMethod_A() {
SdkEventLoopGroup sdkEventLoopGroup = SdkEventLoopGroup.create(new NioEventLoopGroup(), NioSocketChannel::new);
assertThat(sdkEventLoopGroup.channelFactory()).isNotNull();
+ assertThat(sdkEventLoopGroup.channelType()).isNotNull();
+ assertThat(sdkEventLoopGroup.eventLoopGroup()).isNotNull();
+ }
+
+ @Test
+ public void creatingUsingStaticMethod_B() {
+ SdkEventLoopGroup sdkEventLoopGroup = SdkEventLoopGroup.create(new NioEventLoopGroup(), NioSocketChannel::new, NioDatagramChannel.class);
+ assertThat(sdkEventLoopGroup.channelFactory()).isNotNull();
+ assertThat(sdkEventLoopGroup.channelType()).isNotNull();
assertThat(sdkEventLoopGroup.eventLoopGroup()).isNotNull();
}
@@ -43,6 +54,8 @@ public void notProvidingChannelFactory_channelFactoryResolved() {
SdkEventLoopGroup sdkEventLoopGroup = SdkEventLoopGroup.create(new NioEventLoopGroup());
assertThat(sdkEventLoopGroup.channelFactory()).isNotNull();
+ assertThat(sdkEventLoopGroup.channelType()).isNotNull();
+ assertThat(sdkEventLoopGroup.channelType()).isNotNull();
}
@Test(expected = IllegalArgumentException.class)
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMapTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMapTest.java
index 3b72f71be4db..6df9a60779fd 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMapTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMapTest.java
@@ -118,7 +118,7 @@ public void get_callsInjectedBootstrapProviderCorrectly() {
channelPoolMap = new AwaitCloseChannelPoolMap(builder, null, bootstrapProvider);
channelPoolMap.get(targetUri);
- verify(bootstrapProvider).createBootstrap("some-awesome-service-1234.amazonaws.com", 8080);
+ verify(bootstrapProvider).createBootstrap("some-awesome-service-1234.amazonaws.com", 8080, false);
}
@Test
@@ -151,7 +151,7 @@ public void get_usingProxy_callsInjectedBootstrapProviderCorrectly() {
channelPoolMap = new AwaitCloseChannelPoolMap(builder, shouldProxyCache, bootstrapProvider);
channelPoolMap.get(targetUri);
- verify(bootstrapProvider).createBootstrap("localhost", mockProxy.port());
+ verify(bootstrapProvider).createBootstrap("localhost", mockProxy.port(), false);
}
@Test
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/BootstrapProviderTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/BootstrapProviderTest.java
index 337cb7ba2ec2..914587b85df3 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/BootstrapProviderTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/BootstrapProviderTest.java
@@ -42,7 +42,19 @@ public class BootstrapProviderTest {
// connection attempt and not cached between connection attempts.
@Test
public void createBootstrap_usesUnresolvedInetSocketAddress() {
- Bootstrap bootstrap = bootstrapProvider.createBootstrap("some-awesome-service-1234.amazonaws.com", 443);
+ Bootstrap bootstrap = bootstrapProvider.createBootstrap("some-awesome-service-1234.amazonaws.com", 443, false);
+
+ SocketAddress socketAddress = bootstrap.config().remoteAddress();
+
+ assertThat(socketAddress).isInstanceOf(InetSocketAddress.class);
+ InetSocketAddress inetSocketAddress = (InetSocketAddress)socketAddress;
+
+ assertThat(inetSocketAddress.isUnresolved()).isTrue();
+ }
+
+ @Test
+ public void createBootstrapNonBlockingDns_usesUnresolvedInetSocketAddress() {
+ Bootstrap bootstrap = bootstrapProvider.createBootstrap("some-awesome-service-1234.amazonaws.com", 443, true);
SocketAddress socketAddress = bootstrap.config().remoteAddress();
@@ -54,7 +66,7 @@ public void createBootstrap_usesUnresolvedInetSocketAddress() {
@Test
public void createBootstrap_defaultConfiguration_tcpKeepAliveShouldBeFalse() {
- Bootstrap bootstrap = bootstrapProvider.createBootstrap("some-awesome-service-1234.amazonaws.com", 443);
+ Bootstrap bootstrap = bootstrapProvider.createBootstrap("some-awesome-service-1234.amazonaws.com", 443, false);
Boolean keepAlive = (Boolean) bootstrap.config().options().get(ChannelOption.SO_KEEPALIVE);
assertThat(keepAlive).isFalse();
@@ -70,7 +82,7 @@ public void createBootstrap_tcpKeepAliveTrue_shouldApply() {
nettyConfiguration,
new SdkChannelOptions());
- Bootstrap bootstrap = provider.createBootstrap("some-awesome-service-1234.amazonaws.com", 443);
+ Bootstrap bootstrap = provider.createBootstrap("some-awesome-service-1234.amazonaws.com", 443, false);
Boolean keepAlive = (Boolean) bootstrap.config().options().get(ChannelOption.SO_KEEPALIVE);
assertThat(keepAlive).isTrue();
}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/utils/SocketChannelResolverTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/utils/ChannelResolverTest.java
similarity index 95%
rename from http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/utils/SocketChannelResolverTest.java
rename to http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/utils/ChannelResolverTest.java
index 472c417d4485..d7d30fe01819 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/utils/SocketChannelResolverTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/utils/ChannelResolverTest.java
@@ -16,7 +16,7 @@
package software.amazon.awssdk.http.nio.netty.internal.utils;
import static org.assertj.core.api.Assertions.assertThat;
-import static software.amazon.awssdk.http.nio.netty.internal.utils.SocketChannelResolver.resolveSocketChannelFactory;
+import static software.amazon.awssdk.http.nio.netty.internal.utils.ChannelResolver.resolveSocketChannelFactory;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
@@ -29,7 +29,7 @@
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.http.nio.netty.internal.DelegatingEventLoopGroup;
-public class SocketChannelResolverTest {
+public class ChannelResolverTest {
@Test
public void canDetectFactoryForStandardNioEventLoopGroup() {
diff --git a/utils/src/main/java/software/amazon/awssdk/utils/internal/ClassLoaderHelper.java b/utils/src/main/java/software/amazon/awssdk/utils/internal/ClassLoaderHelper.java
new file mode 100644
index 000000000000..4cf8e91f93ae
--- /dev/null
+++ b/utils/src/main/java/software/amazon/awssdk/utils/internal/ClassLoaderHelper.java
@@ -0,0 +1,150 @@
+/*
+ * 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.utils.internal;
+
+
+import software.amazon.awssdk.annotations.SdkInternalApi;
+
+@SdkInternalApi
+public final class ClassLoaderHelper {
+
+ private ClassLoaderHelper() {
+ }
+
+ private static Class> loadClassViaClasses(String fqcn, Class>[] classes) {
+ if (classes == null) {
+ return null;
+ }
+
+ for (Class> clzz: classes) {
+ if (clzz == null) {
+ continue;
+ }
+ ClassLoader loader = clzz.getClassLoader();
+ if (loader != null) {
+ try {
+ return loader.loadClass(fqcn);
+ } catch (ClassNotFoundException e) {
+ // move on to try the next class loader
+ }
+ }
+ }
+ return null;
+ }
+
+ private static Class> loadClassViaContext(String fqcn) {
+ ClassLoader loader = contextClassLoader();
+ try {
+ return loader == null ? null : loader.loadClass(fqcn);
+ } catch (ClassNotFoundException e) {
+ // Ignored.
+ }
+ return null;
+ }
+
+ /**
+ * Loads the class via the optionally specified classes in the order of
+ * their specification, and if not found, via the context class loader of
+ * the current thread, and if not found, from the caller class loader as the
+ * last resort.
+ *
+ * @param fqcn
+ * fully qualified class name of the target class to be loaded
+ * @param classes
+ * class loader providers
+ * @return the class loaded; never null
+ *
+ * @throws ClassNotFoundException
+ * if failed to load the class
+ */
+ public static Class> loadClass(String fqcn, Class>... classes) throws ClassNotFoundException {
+ return loadClass(fqcn, true, classes);
+ }
+
+ /**
+ * If classesFirst is false, loads the class via the context class
+ * loader of the current thread, and if not found, via the class loaders of
+ * the optionally specified classes in the order of their specification, and
+ * if not found, from the caller class loader as the
+ * last resort.
+ *
+ * If classesFirst is true, loads the class via the optionally
+ * specified classes in the order of their specification, and if not found,
+ * via the context class loader of the current thread, and if not found,
+ * from the caller class loader as the last resort.
+ *
+ * @param fqcn
+ * fully qualified class name of the target class to be loaded
+ * @param classesFirst
+ * true if the class loaders of the optionally specified classes
+ * take precedence over the context class loader of the current
+ * thread; false if the opposite is true.
+ * @param classes
+ * class loader providers
+ * @return the class loaded; never null
+ *
+ * @throws ClassNotFoundException if failed to load the class
+ */
+ public static Class> loadClass(String fqcn, boolean classesFirst,
+ Class>... classes) throws ClassNotFoundException {
+ Class> target = null;
+ if (classesFirst) {
+ target = loadClassViaClasses(fqcn, classes);
+ if (target == null) {
+ target = loadClassViaContext(fqcn);
+ }
+ } else {
+ target = loadClassViaContext(fqcn);
+ if (target == null) {
+ target = loadClassViaClasses(fqcn, classes);
+ }
+ }
+ return target == null ? Class.forName(fqcn) : target;
+ }
+
+ /**
+ * Attempt to get the current thread's class loader and fallback to the system classloader if null
+ * @return a {@link ClassLoader} or null if none found
+ */
+ private static ClassLoader contextClassLoader() {
+ ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader();
+ if (threadClassLoader != null) {
+ return threadClassLoader;
+ }
+ return ClassLoader.getSystemClassLoader();
+ }
+
+ /**
+ * Attempt to get class loader that loads the classes and fallback to the thread context classloader if null.
+ *
+ * @param classes the classes
+ * @return a {@link ClassLoader} or null if none found
+ */
+ public static ClassLoader classLoader(Class>... classes) {
+ if (classes != null) {
+ for (Class clzz : classes) {
+ ClassLoader classLoader = clzz.getClassLoader();
+
+ if (classLoader != null) {
+ return classLoader;
+ }
+ }
+ }
+
+ return contextClassLoader();
+ }
+
+}
\ No newline at end of file