diff --git a/sdk/clientcore/http-netty4/src/main/java/io/clientcore/http/netty4/NettyHttpClientBuilder.java b/sdk/clientcore/http-netty4/src/main/java/io/clientcore/http/netty4/NettyHttpClientBuilder.java
index 68c163abd0014..ef38cddf23eeb 100644
--- a/sdk/clientcore/http-netty4/src/main/java/io/clientcore/http/netty4/NettyHttpClientBuilder.java
+++ b/sdk/clientcore/http-netty4/src/main/java/io/clientcore/http/netty4/NettyHttpClientBuilder.java
@@ -12,15 +12,22 @@
import io.clientcore.http.netty4.implementation.ChannelInitializationProxyHandler;
import io.clientcore.http.netty4.implementation.Netty4ConnectionPool;
import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
-import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.IoEventLoopGroup;
+import io.netty.channel.IoHandler;
+import io.netty.channel.IoHandlerFactory;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.util.concurrent.DefaultThreadFactory;
+import io.netty.util.internal.SystemPropertyUtil;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -36,35 +43,35 @@ public class NettyHttpClientBuilder {
private static final String EPOLL = "io.netty.channel.epoll.Epoll";
private static final String EPOLL_CHANNEL = "io.netty.channel.epoll.EpollSocketChannel";
- private static final String EPOLL_EVENT_LOOP_GROUP = "io.netty.channel.epoll.EpollEventLoopGroup";
+ private static final String EPOLL_IO_HANDLER = "io.netty.channel.epoll.EpollIoHandler";
private static final boolean IS_EPOLL_AVAILABLE;
private static final Class extends SocketChannel> EPOLL_CHANNEL_CLASS;
- private static final Class> EPOLL_EVENT_LOOP_GROUP_CLASS;
- private static final MethodHandle EPOLL_EVENT_LOOP_GROUP_CREATOR;
+ private static final Class extends IoHandler> EPOLL_IO_HANDLER_CLASS;
+ private static final MethodHandle EPOLL_IO_HANDLER_FACTORY_CREATOR;
private static final String KQUEUE = "io.netty.channel.kqueue.KQueue";
private static final String KQUEUE_CHANNEL = "io.netty.channel.kqueue.KQueueSocketChannel";
- private static final String KQUEUE_EVENT_LOOP_GROUP = "io.netty.channel.kqueue.KQueueEventLoopGroup";
+ private static final String KQUEUE_IO_HANDLER = "io.netty.channel.kqueue.KQueueIoHandler";
private static final boolean IS_KQUEUE_AVAILABLE;
private static final Class extends SocketChannel> KQUEUE_CHANNEL_CLASS;
- private static final Class> KQUEUE_EVENT_LOOP_GROUP_CLASS;
- private static final MethodHandle KQUEUE_EVENT_LOOP_GROUP_CREATOR;
+ private static final Class extends IoHandler> KQUEUE_IO_HANDLER_CLASS;
+ private static final MethodHandle KQUEUE_IO_HANDLER_FACTORY_CREATOR;
static {
// Inspect the class path to determine is native transports are available.
// If they are, this will determine runtime behaviors.
boolean isEpollAvailable;
Class extends SocketChannel> epollChannelClass;
- Class> epollEventLoopGroupClass;
- MethodHandle epollEventLoopGroupCreator;
+ Class extends IoHandler> epollIoHandlerClass;
+ MethodHandle epollIoHandlerFactoryCreator;
try {
Class> epollClass = Class.forName(EPOLL);
isEpollAvailable = (boolean) epollClass.getDeclaredMethod("isAvailable").invoke(null);
epollChannelClass = getChannelClass(EPOLL_CHANNEL);
- epollEventLoopGroupClass = Class.forName(EPOLL_EVENT_LOOP_GROUP);
- epollEventLoopGroupCreator = MethodHandles.publicLookup()
- .unreflectConstructor(epollEventLoopGroupClass.getDeclaredConstructor(ThreadFactory.class));
+ epollIoHandlerClass = Class.forName(EPOLL_IO_HANDLER).asSubclass(IoHandler.class);
+ epollIoHandlerFactoryCreator
+ = MethodHandles.publicLookup().unreflect(epollIoHandlerClass.getDeclaredMethod("newFactory"));
LOGGER.atVerbose()
.addKeyValue("epollAvailable", isEpollAvailable)
.log("Lookup for Epoll completed without error.");
@@ -77,27 +84,27 @@ public class NettyHttpClientBuilder {
LOGGER.atVerbose().log("Epoll is unavailable and won't be used.");
isEpollAvailable = false;
epollChannelClass = null;
- epollEventLoopGroupClass = null;
- epollEventLoopGroupCreator = null;
+ epollIoHandlerClass = null;
+ epollIoHandlerFactoryCreator = null;
}
IS_EPOLL_AVAILABLE = isEpollAvailable;
EPOLL_CHANNEL_CLASS = epollChannelClass;
- EPOLL_EVENT_LOOP_GROUP_CLASS = epollEventLoopGroupClass;
- EPOLL_EVENT_LOOP_GROUP_CREATOR = epollEventLoopGroupCreator;
+ EPOLL_IO_HANDLER_CLASS = epollIoHandlerClass;
+ EPOLL_IO_HANDLER_FACTORY_CREATOR = epollIoHandlerFactoryCreator;
boolean isKqueueAvailable;
Class extends SocketChannel> kqueueChannelClass;
- Class> kqueueEventLoopGroupClass;
- MethodHandle kqueueEventLoopGroupCreator;
+ Class extends IoHandler> kqueueIoHandlerClass;
+ MethodHandle kqueueIoHandlerFactoryCreator;
try {
Class> kqueueClass = Class.forName(KQUEUE);
isKqueueAvailable = (boolean) kqueueClass.getDeclaredMethod("isAvailable").invoke(null);
kqueueChannelClass = getChannelClass(KQUEUE_CHANNEL);
- kqueueEventLoopGroupClass = Class.forName(KQUEUE_EVENT_LOOP_GROUP);
- kqueueEventLoopGroupCreator = MethodHandles.publicLookup()
- .unreflectConstructor(kqueueEventLoopGroupClass.getDeclaredConstructor(ThreadFactory.class));
+ kqueueIoHandlerClass = Class.forName(KQUEUE_IO_HANDLER).asSubclass(IoHandler.class);
+ kqueueIoHandlerFactoryCreator
+ = MethodHandles.publicLookup().unreflect(kqueueIoHandlerClass.getDeclaredMethod("newFactory"));
LOGGER.atVerbose()
.addKeyValue("kqueueAvailable", isKqueueAvailable)
.log("Lookup for KQueue completed without error.");
@@ -110,14 +117,14 @@ public class NettyHttpClientBuilder {
LOGGER.atVerbose().log("KQueue is unavailable and won't be used.");
isKqueueAvailable = false;
kqueueChannelClass = null;
- kqueueEventLoopGroupClass = null;
- kqueueEventLoopGroupCreator = null;
+ kqueueIoHandlerClass = null;
+ kqueueIoHandlerFactoryCreator = null;
}
IS_KQUEUE_AVAILABLE = isKqueueAvailable;
KQUEUE_CHANNEL_CLASS = kqueueChannelClass;
- KQUEUE_EVENT_LOOP_GROUP_CLASS = kqueueEventLoopGroupClass;
- KQUEUE_EVENT_LOOP_GROUP_CREATOR = kqueueEventLoopGroupCreator;
+ KQUEUE_IO_HANDLER_CLASS = kqueueIoHandlerClass;
+ KQUEUE_IO_HANDLER_FACTORY_CREATOR = kqueueIoHandlerFactoryCreator;
}
@SuppressWarnings("unchecked")
@@ -154,10 +161,10 @@ public NettyHttpClientBuilder() {
* Sets the event loop group for the Netty client.
*
* By default, if no {@code eventLoopGroup} is configured and no native transports are available (Epoll KQueue)
- * {@link NioEventLoopGroup} will be used.
+ * a {@link MultiThreadIoEventLoopGroup} using {@link NioIoHandler} will be used.
*
- * If native transports are available, the {@link EventLoopGroup} implementation for the native transport will be
- * chosen over {@link NioEventLoopGroup}.
+ * If native transports are available, the {@link IoHandlerFactory} for the native transport will be
+ * chosen over {@link NioIoHandler}.
*
* @param eventLoopGroup The event loop group.
* @return The updated builder.
@@ -377,9 +384,9 @@ public NettyHttpClientBuilder maxPendingAcquires(int maxPendingAcquires) {
*/
public HttpClient build() {
EventLoopGroup group = getEventLoopGroupToUse(this.eventLoopGroup, this.channelClass, IS_EPOLL_AVAILABLE,
- EPOLL_EVENT_LOOP_GROUP_CREATOR, IS_KQUEUE_AVAILABLE, KQUEUE_EVENT_LOOP_GROUP_CREATOR);
+ EPOLL_IO_HANDLER_FACTORY_CREATOR, IS_KQUEUE_AVAILABLE, KQUEUE_IO_HANDLER_FACTORY_CREATOR);
Class extends Channel> channelClass
- = getChannelClass(this.channelClass, group.getClass(), IS_EPOLL_AVAILABLE, IS_KQUEUE_AVAILABLE);
+ = getChannelClass(this.channelClass, group, IS_EPOLL_AVAILABLE, IS_KQUEUE_AVAILABLE);
// Leave breadcrumbs about the NettyHttpClient configuration, in case troubleshooting is needed.
LoggingEvent loggingEvent = LOGGER.atVerbose()
@@ -401,6 +408,13 @@ public HttpClient build() {
Bootstrap bootstrap = new Bootstrap().group(group)
.channel(channelClass)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) getTimeoutMillis(connectTimeout, 10_000));
+ bootstrap.option(ChannelOption.ALLOCATOR,
+ // If io.netty.allocator.type was configured, respect the configuration. Otherwise, fallback to the default
+ // setting from Netty 4.1 which was the pooled allocator. In the future, this should be validated for if
+ // this should use the new Netty 4.2 default which is an adaptive allocator.
+ SystemPropertyUtil.contains("io.netty.allocator.type")
+ ? ByteBufAllocator.DEFAULT
+ : PooledByteBufAllocator.DEFAULT);
// Disable auto-read as we want to control when and how data is read from the channel.
bootstrap.option(ChannelOption.AUTO_READ, false);
// Enable TCP keep-alive to proactively detect and clean up stale connections in the pool. This helps evict
@@ -455,47 +469,52 @@ ProxyOptions getProxyOptions() {
static EventLoopGroup getEventLoopGroupToUse(EventLoopGroup configuredGroup,
Class extends SocketChannel> configuredChannelClass, boolean isEpollAvailable,
- MethodHandle epollEventLoopGroupCreator, boolean isKqueueAvailable, MethodHandle kqueueEventLoopGroupCreator) {
+ MethodHandle epollIoHandlerFactoryCreator, boolean isKqueueAvailable,
+ MethodHandle kqueueIoHandlerFactoryCreator) {
if (configuredGroup != null) {
return configuredGroup;
}
ThreadFactory threadFactory = new DefaultThreadFactory("clientcore-netty-client", true);
- // Use EpollEventLoopGroup if Epoll is available and 'channelClass' wasn't configured or was configured to
+ // Use Epoll if available and 'channelClass' wasn't configured or was configured to
// EpollSocketChannel.
if (isEpollAvailable && (configuredChannelClass == null || configuredChannelClass == EPOLL_CHANNEL_CLASS)) {
try {
- return (EventLoopGroup) epollEventLoopGroupCreator.invoke(threadFactory);
+ return new MultiThreadIoEventLoopGroup(threadFactory,
+ (IoHandlerFactory) epollIoHandlerFactoryCreator.invoke());
} catch (Throwable ex) {
- LOGGER.atVerbose().setThrowable(ex).log("Failed to create an EpollEventLoopGroup.");
+ LOGGER.atVerbose().setThrowable(ex).log("Failed to create an Epoll event loop group.");
}
}
- // Use KQueueEventLoopGroup if KQueue is available and 'channelClass' wasn't configured or was configured to
+ // Use KQueue if available and 'channelClass' wasn't configured or was configured to
// KQueueSocketChannel.
if (isKqueueAvailable && (configuredChannelClass == null || configuredChannelClass == KQUEUE_CHANNEL_CLASS)) {
try {
- return (EventLoopGroup) kqueueEventLoopGroupCreator.invoke(threadFactory);
+ return new MultiThreadIoEventLoopGroup(threadFactory,
+ (IoHandlerFactory) kqueueIoHandlerFactoryCreator.invoke());
} catch (Throwable ex) {
- LOGGER.atVerbose().setThrowable(ex).log("Failed to create a KQueueEventLoopGroup.");
+ LOGGER.atVerbose().setThrowable(ex).log("Failed to create a KQueue event loop group.");
}
}
- // Fallback to NioEventLoopGroup.
- return new NioEventLoopGroup(threadFactory);
+ // Fall back to NIO.
+ return new MultiThreadIoEventLoopGroup(threadFactory, NioIoHandler.newFactory());
}
static Class extends SocketChannel> getChannelClass(Class extends SocketChannel> configuredChannelClass,
- Class extends EventLoopGroup> configuredGroupClass, boolean isEpollAvailable, boolean isKqueueAvailable) {
+ EventLoopGroup configuredGroup, boolean isEpollAvailable, boolean isKqueueAvailable) {
if (configuredChannelClass != null) {
// If the Channel class was manually set, use it.
return configuredChannelClass;
- } else if (isEpollAvailable && configuredGroupClass == EPOLL_EVENT_LOOP_GROUP_CLASS) {
- // If Epoll is available and the EventLoopGroup is EpollEventLoopGroup, use EpollSocketChannel.
+ } else if (isEpollAvailable
+ && configuredGroup instanceof IoEventLoopGroup
+ && ((IoEventLoopGroup) configuredGroup).isIoType(EPOLL_IO_HANDLER_CLASS)) {
return EPOLL_CHANNEL_CLASS;
- } else if (isKqueueAvailable && configuredGroupClass == KQUEUE_EVENT_LOOP_GROUP_CLASS) {
- // If KQueue is available and the EventLoopGroup is KQueueEventLoopGroup, use KQueueSocketChannel.
+ } else if (isKqueueAvailable
+ && configuredGroup instanceof IoEventLoopGroup
+ && ((IoEventLoopGroup) configuredGroup).isIoType(KQUEUE_IO_HANDLER_CLASS)) {
return KQUEUE_CHANNEL_CLASS;
} else {
// Fallback to NioSocketChannel.
diff --git a/sdk/clientcore/http-netty4/src/main/java/io/clientcore/http/netty4/implementation/Netty4Utility.java b/sdk/clientcore/http-netty4/src/main/java/io/clientcore/http/netty4/implementation/Netty4Utility.java
index f0d7835c190f7..4bdffdc966f88 100644
--- a/sdk/clientcore/http-netty4/src/main/java/io/clientcore/http/netty4/implementation/Netty4Utility.java
+++ b/sdk/clientcore/http-netty4/src/main/java/io/clientcore/http/netty4/implementation/Netty4Utility.java
@@ -85,10 +85,12 @@ public final class Netty4Utility {
// Non-native dependencies are required while native dependencies are optional. Without the native dependencies
// the SDK will fall back to using the JDK implementations.
private static final List REQUIRED_NETTY_VERSION_ARTIFACTS
- = Arrays.asList("netty-buffer", "netty-codec", "netty-codec-http", "netty-codec-http2", "netty-common",
- "netty-handler", "netty-handler-proxy", "netty-resolver", "netty-resolver-dns", "netty-transport");
- private static final List OPTIONAL_NETTY_VERSION_ARTIFACTS = Arrays
- .asList("netty-transport-native-unix-common", "netty-transport-native-epoll", "netty-transport-native-kqueue");
+ = Arrays.asList("netty-buffer", "netty-codec-base", "netty-codec-compression", "netty-codec-dns",
+ "netty-codec-socks", "netty-codec-http", "netty-codec-http2", "netty-common", "netty-handler",
+ "netty-handler-proxy", "netty-resolver", "netty-resolver-dns", "netty-transport");
+ private static final List OPTIONAL_NETTY_VERSION_ARTIFACTS
+ = Arrays.asList("netty-transport-native-unix-common", "netty-transport-native-epoll",
+ "netty-transport-native-kqueue", "netty-transport-classes-epoll", "netty-transport-classes-kqueue");
private static final int TWO_FIFTY_SIX_KB = 256 * 1024;
diff --git a/sdk/clientcore/http-netty4/src/main/java/module-info.java b/sdk/clientcore/http-netty4/src/main/java/module-info.java
index 2ab0fc5e7223a..a9e31d53266bd 100644
--- a/sdk/clientcore/http-netty4/src/main/java/module-info.java
+++ b/sdk/clientcore/http-netty4/src/main/java/module-info.java
@@ -14,6 +14,7 @@
requires io.netty.common;
requires io.netty.handler.proxy;
requires io.netty.codec;
+ requires io.netty.codec.compression;
requires io.netty.resolver;
requires io.netty.codec.http2;
requires java.sql;
diff --git a/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/NettyHttp2HttpClientTests.java b/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/NettyHttp2HttpClientTests.java
index dba6aa10bbeae..7a454a7650fe2 100644
--- a/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/NettyHttp2HttpClientTests.java
+++ b/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/NettyHttp2HttpClientTests.java
@@ -17,12 +17,21 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.GZIPOutputStream;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
@Timeout(value = 3, unit = TimeUnit.MINUTES)
public class NettyHttp2HttpClientTests extends HttpClientTests {
@@ -93,4 +102,31 @@ public void canSendBinaryDataDebug() {
assertArrayEquals(expectedBytes, response.getValue().toBytes());
}
}
+
+ @ParameterizedTest
+ @ValueSource(strings = { "gzip", "deflate" })
+ public void decompressesResponse(String contentEncoding) throws IOException {
+ byte[] expected = "Compressed HTTP/2 response".getBytes(StandardCharsets.UTF_8);
+ ByteArrayOutputStream compressed = new ByteArrayOutputStream();
+ try (OutputStream compressor = "gzip".equals(contentEncoding)
+ ? new GZIPOutputStream(compressed)
+ : new DeflaterOutputStream(compressed)) {
+ compressor.write(expected);
+ }
+ byte[] compressedBytes = compressed.toByteArray();
+ LocalTestServer compressedServer
+ = new LocalTestServer(HttpProtocolVersion.HTTP_2, true, (request, response, requestBody) -> {
+ response.setHeader("Content-Encoding", contentEncoding);
+ response.setContentLength(compressedBytes.length);
+ response.getOutputStream().write(compressedBytes);
+ });
+ compressedServer.start();
+ try (Response response = getHttpClient()
+ .send(new HttpRequest().setMethod(HttpMethod.GET).setUri(compressedServer.getHttpsUri()))) {
+ assertEquals(200, response.getStatusCode());
+ assertArrayEquals(expected, response.getValue().toBytes());
+ } finally {
+ compressedServer.stop();
+ }
+ }
}
diff --git a/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/NettyHttpClientBuilderTests.java b/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/NettyHttpClientBuilderTests.java
index a417ef32f253a..ac0e01e2f2add 100644
--- a/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/NettyHttpClientBuilderTests.java
+++ b/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/NettyHttpClientBuilderTests.java
@@ -13,15 +13,23 @@
import io.clientcore.core.utils.configuration.Configuration;
import io.clientcore.http.netty4.implementation.NettyHttpClientLocalTestServer;
import io.netty.bootstrap.BootstrapConfig;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
+import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
-import io.netty.channel.epoll.EpollEventLoopGroup;
+import io.netty.channel.IoEventLoopGroup;
+import io.netty.channel.IoHandler;
+import io.netty.channel.IoHandlerFactory;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.epoll.EpollIoHandler;
import io.netty.channel.epoll.EpollSocketChannel;
-import io.netty.channel.kqueue.KQueueEventLoopGroup;
+import io.netty.channel.kqueue.KQueueIoHandler;
import io.netty.channel.kqueue.KQueueSocketChannel;
-import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.resolver.DefaultAddressResolverGroup;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.condition.EnabledOnOs;
@@ -38,7 +46,6 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -50,6 +57,13 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
/**
* Tests {@link NettyHttpClientBuilder}.
@@ -196,6 +210,7 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
arguments.add(
Configuration.from(new TestConfigurationSource().put(Configuration.HTTP_PROXY, "http://localhost:12345")
+ .put(Configuration.HTTPS_PROXY, "")
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true")));
/*
@@ -207,6 +222,7 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
arguments.add(
Configuration.from(new TestConfigurationSource().put(Configuration.HTTP_PROXY, "http://1:1@localhost:12345")
+ .put(Configuration.HTTPS_PROXY, "")
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true")));
/*
@@ -222,6 +238,7 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
.from(baseJavaProxyConfigurationSupplier.get().put(JAVA_NON_PROXY_HOSTS, rawJavaNonProxyHosts)));
arguments.add(
Configuration.from(new TestConfigurationSource().put(Configuration.HTTP_PROXY, "http://localhost:12345")
+ .put(Configuration.HTTPS_PROXY, "")
.put(Configuration.NO_PROXY, rawEnvNonProxyHosts)
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true")));
@@ -234,6 +251,7 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
.put(JAVA_HTTP_PROXY_PASSWORD, "1")));
arguments.add(
Configuration.from(new TestConfigurationSource().put(Configuration.HTTP_PROXY, "http://1:1@localhost:12345")
+ .put(Configuration.HTTPS_PROXY, "")
.put(Configuration.NO_PROXY, rawEnvNonProxyHosts)
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true")));
@@ -286,12 +304,16 @@ private static Stream buildWithExplicitConfigurationProxySupplier
@Test
public void buildEventLoopClient() {
String expectedThreadName = "testEventLoop";
- NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(1, (Runnable r) -> new Thread(r, expectedThreadName));
-
- NettyHttpClient nettyClient
- = (NettyHttpClient) new NettyHttpClientBuilder().eventLoopGroup(eventLoopGroup).build();
-
- assertSame(eventLoopGroup, nettyClient.getBootstrap().config().group());
+ EventLoopGroup eventLoopGroup = new MultiThreadIoEventLoopGroup(1,
+ (Runnable r) -> new Thread(r, expectedThreadName), NioIoHandler.newFactory());
+
+ try {
+ NettyHttpClient nettyClient
+ = (NettyHttpClient) new NettyHttpClientBuilder().eventLoopGroup(eventLoopGroup).build();
+ assertSame(eventLoopGroup, nettyClient.getBootstrap().config().group());
+ } finally {
+ eventLoopGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS).syncUninterruptibly();
+ }
}
@ParameterizedTest
@@ -307,13 +329,27 @@ private static Stream getTimeoutMillisSupplier() {
Arguments.of(Duration.ofNanos(1), TimeUnit.MILLISECONDS.toMillis(1)));
}
+ @Test
+ public void preservesAllocatorAndResolverDefaults() {
+ NettyHttpClient client = (NettyHttpClient) new NettyHttpClientBuilder().build();
+ try {
+ ByteBufAllocator expectedAllocator = System.getProperty("io.netty.allocator.type") == null
+ ? PooledByteBufAllocator.DEFAULT
+ : ByteBufAllocator.DEFAULT;
+ assertSame(expectedAllocator, client.getBootstrap().config().options().get(ChannelOption.ALLOCATOR));
+ assertSame(DefaultAddressResolverGroup.INSTANCE, client.getBootstrap().config().resolver());
+ } finally {
+ client.getBootstrap().config().group().shutdownGracefully(0, 5, TimeUnit.SECONDS).syncUninterruptibly();
+ }
+ }
+
@Test
@EnabledOnOs(OS.WINDOWS)
public void windowsUseNioByDefault() {
NettyHttpClient nettyHttpClient = (NettyHttpClient) new NettyHttpClientBuilder().build();
BootstrapConfig config = nettyHttpClient.getBootstrap().config();
- assertInstanceOf(NioEventLoopGroup.class, config.group());
+ assertTrue(assertInstanceOf(IoEventLoopGroup.class, config.group()).isIoType(NioIoHandler.class));
assertInstanceOf(NioSocketChannel.class, config.channelFactory().newChannel());
}
@@ -323,7 +359,7 @@ public void macUsesKQueueByDefault() {
NettyHttpClient nettyHttpClient = (NettyHttpClient) new NettyHttpClientBuilder().build();
BootstrapConfig config = nettyHttpClient.getBootstrap().config();
- assertInstanceOf(KQueueEventLoopGroup.class, config.group());
+ assertTrue(assertInstanceOf(IoEventLoopGroup.class, config.group()).isIoType(KQueueIoHandler.class));
assertInstanceOf(KQueueSocketChannel.class, config.channelFactory().newChannel());
}
@@ -332,11 +368,11 @@ public void macUsesKQueueByDefault() {
public void macUsesNioIfConfigured() {
NettyHttpClient nettyHttpClient
= (NettyHttpClient) new NettyHttpClientBuilder().channelClass(NioSocketChannel.class)
- .eventLoopGroup(new NioEventLoopGroup())
+ .eventLoopGroup(new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory()))
.build();
BootstrapConfig config = nettyHttpClient.getBootstrap().config();
- assertInstanceOf(NioEventLoopGroup.class, config.group());
+ assertTrue(assertInstanceOf(IoEventLoopGroup.class, config.group()).isIoType(NioIoHandler.class));
assertInstanceOf(NioSocketChannel.class, config.channelFactory().newChannel());
}
@@ -346,7 +382,7 @@ public void linuxUsesEpollByDefault() {
NettyHttpClient nettyHttpClient = (NettyHttpClient) new NettyHttpClientBuilder().build();
BootstrapConfig config = nettyHttpClient.getBootstrap().config();
- assertInstanceOf(EpollEventLoopGroup.class, config.group());
+ assertTrue(assertInstanceOf(IoEventLoopGroup.class, config.group()).isIoType(EpollIoHandler.class));
assertInstanceOf(EpollSocketChannel.class, config.channelFactory().newChannel());
}
@@ -355,24 +391,61 @@ public void linuxUsesEpollByDefault() {
public void linuxUsesNioIfConfigured() {
NettyHttpClient nettyHttpClient
= (NettyHttpClient) new NettyHttpClientBuilder().channelClass(NioSocketChannel.class)
- .eventLoopGroup(new NioEventLoopGroup())
+ .eventLoopGroup(new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory()))
.build();
BootstrapConfig config = nettyHttpClient.getBootstrap().config();
- assertInstanceOf(NioEventLoopGroup.class, config.group());
+ assertTrue(assertInstanceOf(IoEventLoopGroup.class, config.group()).isIoType(NioIoHandler.class));
assertInstanceOf(NioSocketChannel.class, config.channelFactory().newChannel());
}
@ParameterizedTest
@MethodSource("getEventLoopGroupToUseSupplier")
- public void getEventLoopGroupToUse(Class> expected, EventLoopGroup configuredGroup,
- Class extends SocketChannel> configuredChannelClass, boolean isEpollAvailable,
- MethodHandle epollEventLoopGroupCreator, boolean isKqueueAvailable, MethodHandle kqueueEventLoopGroupCreator) {
- EventLoopGroup eventLoopGroup
- = NettyHttpClientBuilder.getEventLoopGroupToUse(configuredGroup, configuredChannelClass, isEpollAvailable,
- epollEventLoopGroupCreator, isKqueueAvailable, kqueueEventLoopGroupCreator);
-
- assertInstanceOf(expected, eventLoopGroup);
+ public void getEventLoopGroupToUse(String expectedTransport, boolean configureGroup,
+ Class extends SocketChannel> configuredChannelClass, boolean isEpollAvailable, boolean epollFails,
+ boolean isKqueueAvailable, boolean kqueueFails) {
+ IoHandlerFactory epollFactory = mockIoHandlerFactory();
+ IoHandlerFactory kqueueFactory = mockIoHandlerFactory();
+ MethodHandle exceptionCreator
+ = MethodHandles.throwException(IoHandlerFactory.class, IllegalStateException.class)
+ .bindTo(new IllegalStateException("Native transport unavailable"));
+ MethodHandle epollCreator
+ = epollFails ? exceptionCreator : MethodHandles.constant(IoHandlerFactory.class, epollFactory);
+ MethodHandle kqueueCreator
+ = kqueueFails ? exceptionCreator : MethodHandles.constant(IoHandlerFactory.class, kqueueFactory);
+ EventLoopGroup configuredGroup
+ = configureGroup ? new MultiThreadIoEventLoopGroup(1, NioIoHandler.newFactory()) : null;
+ EventLoopGroup eventLoopGroup = NettyHttpClientBuilder.getEventLoopGroupToUse(configuredGroup,
+ configuredChannelClass, isEpollAvailable, epollCreator, isKqueueAvailable, kqueueCreator);
+
+ try {
+ assertInstanceOf(MultiThreadIoEventLoopGroup.class, eventLoopGroup);
+ if (configuredGroup != null) {
+ assertSame(configuredGroup, eventLoopGroup);
+ }
+ if ("epoll".equals(expectedTransport)) {
+ verify(epollFactory, atLeastOnce()).newHandler(any());
+ verifyNoInteractions(kqueueFactory);
+ } else if ("kqueue".equals(expectedTransport)) {
+ verify(kqueueFactory, atLeastOnce()).newHandler(any());
+ verifyNoInteractions(epollFactory);
+ } else {
+ verifyNoInteractions(epollFactory, kqueueFactory);
+ }
+ assertTrue(assertInstanceOf(IoEventLoopGroup.class, eventLoopGroup).isIoType(NioIoHandler.class));
+ if (configuredGroup == null) {
+ assertTrue(eventLoopGroup.next()
+ .submit(() -> Thread.currentThread().isDaemon())
+ .syncUninterruptibly()
+ .getNow());
+ assertTrue(eventLoopGroup.next()
+ .submit(() -> Thread.currentThread().getName().startsWith("clientcore-netty-client"))
+ .syncUninterruptibly()
+ .getNow());
+ }
+ } finally {
+ eventLoopGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS).syncUninterruptibly();
+ }
}
@Test
@@ -404,128 +477,60 @@ public void testMaximumHttpVersion() throws NoSuchFieldException, IllegalAccessE
assertEquals(HttpProtocolVersion.HTTP_2, httpVersionField.get(clientv2));
}
- private static Stream getEventLoopGroupToUseSupplier() throws ReflectiveOperationException {
- // Doesn't matter what this is calling, just needs to throw an exception.
- // This will as it doesn't accept the arguments that it will be called with.
- MethodHandle exceptionCreator
- = MethodHandles.publicLookup().unreflectConstructor(NioEventLoopGroup.class.getDeclaredConstructor());
-
- // NOTE: This test doesn't use EpollEventLoopGroup or KQueueEventLoopGroup directly, but rather uses different
- // EventLoopGroup classes as the creation of those requires native libraries to be loaded.
- // This is a workaround to avoid loading the native libraries in the test, as not all OSes can support the
- // native transports.
- MethodHandle epollCreator = MethodHandles.publicLookup()
- .unreflectConstructor(MockEpollEventLoopGroup.class.getDeclaredConstructor(ThreadFactory.class));
- MethodHandle kqueueCreator = MethodHandles.publicLookup()
- .unreflectConstructor(MockKQueueEventLoopGroup.class.getDeclaredConstructor(ThreadFactory.class));
-
- // EventLoopGroup is configured, use it.
- Arguments configuredGroup
- = Arguments.of(NioEventLoopGroup.class, new NioEventLoopGroup(), null, false, null, false, null);
-
- // Epoll is available and nothing is configured, use EpollEventLoopGroup.
- Arguments epollGroup = Arguments.of(MockEpollEventLoopGroup.class, null, null, true, epollCreator, false, null);
-
- // Epoll is available and EpollSocketChannel is configured, use EpollEventLoopGroup.
- Arguments epollChannelGroup = Arguments.of(MockEpollEventLoopGroup.class, null, EpollSocketChannel.class, true,
- epollCreator, false, null);
-
- // Epoll is available but throws an exception, use NioEventLoopGroup.
- Arguments epollExceptionGroup
- = Arguments.of(NioEventLoopGroup.class, null, null, true, exceptionCreator, false, null);
-
- // KQueue is available and nothing is configured, use KQueueEventLoopGroup.
- Arguments kqueueGroup
- = Arguments.of(MockKQueueEventLoopGroup.class, null, null, false, null, true, kqueueCreator);
-
- // KQueue is available and KQueueSocketChannel is configured, use KQueueEventLoopGroup.
- Arguments kqueueChannelGroup = Arguments.of(MockKQueueEventLoopGroup.class, null, KQueueSocketChannel.class,
- false, null, true, kqueueCreator);
-
- // KQueue is available but throws an exception, use NioEventLoopGroup.
- Arguments kqueueExceptionGroup
- = Arguments.of(NioEventLoopGroup.class, null, null, false, null, true, exceptionCreator);
-
- // Both Epoll and KQueue are available, use EpollEventLoopGroup.
- Arguments epollAndKqueueGroup
- = Arguments.of(MockEpollEventLoopGroup.class, null, null, true, epollCreator, true, kqueueCreator);
-
- // Both Epoll and KQueue are available but channel class is set to KQueueSocketChannel, use
- // KQueueEventLoopGroup.
- Arguments epollAndKqueueChannelGroup = Arguments.of(MockKQueueEventLoopGroup.class, null,
- KQueueSocketChannel.class, true, epollCreator, true, kqueueCreator);
-
- // Both Epoll and KQueue are available but throws an exception, use NioEventLoopGroup.
- Arguments epollAndKqueueExceptionGroup
- = Arguments.of(NioEventLoopGroup.class, null, null, true, exceptionCreator, true, exceptionCreator);
-
- // Both Epoll and KQueue are available but channel class is set to EpollSocketChannel, use
- // EpollEventLoopGroup.
- Arguments epollAndKqueueChannelExceptionGroup = Arguments.of(MockEpollEventLoopGroup.class, null,
- EpollSocketChannel.class, true, epollCreator, true, kqueueCreator);
-
- // Both Epoll and KQueue are available but channel class is set to NioSocketChannel, use
- // NioEventLoopGroup.
- Arguments epollAndKqueueChannelNioGroup = Arguments.of(NioEventLoopGroup.class, null, NioSocketChannel.class,
- true, epollCreator, true, kqueueCreator);
-
- return Stream.of(configuredGroup, epollGroup, epollChannelGroup, epollExceptionGroup, kqueueGroup,
- kqueueChannelGroup, kqueueExceptionGroup, epollAndKqueueGroup, epollAndKqueueChannelGroup,
- epollAndKqueueExceptionGroup, epollAndKqueueChannelExceptionGroup, epollAndKqueueChannelNioGroup);
+ private static Stream getEventLoopGroupToUseSupplier() {
+ return Stream.of(Arguments.of("nio", true, null, true, true, true, true),
+ Arguments.of("epoll", false, null, true, false, false, false),
+ Arguments.of("epoll", false, EpollSocketChannel.class, true, false, false, false),
+ Arguments.of("nio", false, null, true, true, false, false),
+ Arguments.of("kqueue", false, null, false, false, true, false),
+ Arguments.of("kqueue", false, KQueueSocketChannel.class, false, false, true, false),
+ Arguments.of("nio", false, null, false, false, true, true),
+ Arguments.of("epoll", false, null, true, false, true, false),
+ Arguments.of("kqueue", false, KQueueSocketChannel.class, true, false, true, false),
+ Arguments.of("kqueue", false, null, true, true, true, false),
+ Arguments.of("nio", false, null, true, true, true, true),
+ Arguments.of("epoll", false, EpollSocketChannel.class, true, false, true, false),
+ Arguments.of("nio", false, NioSocketChannel.class, true, true, true, true),
+ Arguments.of("nio", false, null, false, false, false, false));
}
- public static final class MockEpollEventLoopGroup extends NioEventLoopGroup {
- public MockEpollEventLoopGroup(ThreadFactory threadFactory) {
- super(threadFactory);
- }
- }
-
- public static final class MockKQueueEventLoopGroup extends NioEventLoopGroup {
- public MockKQueueEventLoopGroup(ThreadFactory threadFactory) {
- super(threadFactory);
- }
+ private static IoHandlerFactory mockIoHandlerFactory() {
+ // Delegate to NIO so factory selection can be tested without loading native libraries.
+ IoHandlerFactory delegate = NioIoHandler.newFactory();
+ IoHandlerFactory factory = mock(IoHandlerFactory.class);
+ when(factory.newHandler(any())).thenAnswer(invocation -> delegate.newHandler(invocation.getArgument(0)));
+ return factory;
}
@ParameterizedTest
@MethodSource("getChannelClassSupplier")
public void getChannelClass(Class> expected, Class extends SocketChannel> configuredChannelClass,
- Class extends EventLoopGroup> congiguredGroupClass, boolean isEpollAvailable, boolean isKqueueAvailable) {
+ EventLoopGroup configuredGroup, boolean isEpollAvailable, boolean isKqueueAvailable) {
Class extends Channel> channelClass = NettyHttpClientBuilder.getChannelClass(configuredChannelClass,
- congiguredGroupClass, isEpollAvailable, isKqueueAvailable);
+ configuredGroup, isEpollAvailable, isKqueueAvailable);
assertEquals(expected, channelClass);
}
private static Stream getChannelClassSupplier() {
- // Channel class is configured, use it.
- Arguments configuredChannel = Arguments.of(NioSocketChannel.class, NioSocketChannel.class, null, false, false);
-
- // Epoll is available and EventLoopGroup is EpollEventLoopGroup, use EpollSocketChannel.
- Arguments epollChannel = Arguments.of(EpollSocketChannel.class, null, EpollEventLoopGroup.class, true, false);
-
- // KQueue is available and EventLoopGroup is KQueueEventLoopGroup, use KQueueSocketChannel.
- Arguments kqueueChannel
- = Arguments.of(KQueueSocketChannel.class, null, KQueueEventLoopGroup.class, false, true);
-
- // Epoll is available and EventLoopGroup is NioEventLoopGroup, use NioSocketChannel.
- Arguments epollNioChannel = Arguments.of(NioSocketChannel.class, null, NioEventLoopGroup.class, true, false);
-
- // KQueue is available and EventLoopGroup is NioEventLoopGroup, use NioSocketChannel.
- Arguments kqueueNioChannel = Arguments.of(NioSocketChannel.class, null, NioEventLoopGroup.class, false, true);
-
- // Both Epoll and KQueue are available and EventLoopGroup is NioEventLoopGroup, use NioSocketChannel.
- Arguments epollAndKqueueNioChannel
- = Arguments.of(NioSocketChannel.class, null, NioEventLoopGroup.class, true, true);
-
- // Both Epoll and KQueue are available and EventLoopGroup is EpollEventLoopGroup, use EpollSocketChannel.
- Arguments epollAndKqueueEpollChannel
- = Arguments.of(EpollSocketChannel.class, null, EpollEventLoopGroup.class, true, true);
-
- // Both Epoll and KQueue are available and EventLoopGroup is KQueueEventLoopGroup, use KQueueSocketChannel.
- Arguments epollAndKqueueKqueueChannel
- = Arguments.of(KQueueSocketChannel.class, null, KQueueEventLoopGroup.class, true, true);
+ IoEventLoopGroup nioGroup = mockIoEventLoopGroup(NioIoHandler.class);
+ IoEventLoopGroup epollGroup = mockIoEventLoopGroup(EpollIoHandler.class);
+ IoEventLoopGroup kqueueGroup = mockIoEventLoopGroup(KQueueIoHandler.class);
+
+ return Stream.of(Arguments.of(NioSocketChannel.class, NioSocketChannel.class, epollGroup, true, true),
+ Arguments.of(EpollSocketChannel.class, null, epollGroup, true, false),
+ Arguments.of(KQueueSocketChannel.class, null, kqueueGroup, false, true),
+ Arguments.of(NioSocketChannel.class, null, nioGroup, true, false),
+ Arguments.of(NioSocketChannel.class, null, nioGroup, false, true),
+ Arguments.of(NioSocketChannel.class, null, nioGroup, true, true),
+ Arguments.of(EpollSocketChannel.class, null, epollGroup, true, true),
+ Arguments.of(KQueueSocketChannel.class, null, kqueueGroup, true, true),
+ Arguments.of(NioSocketChannel.class, null, mock(EventLoopGroup.class), true, true));
+ }
- return Stream.of(configuredChannel, epollChannel, kqueueChannel, epollNioChannel, kqueueNioChannel,
- epollAndKqueueNioChannel, epollAndKqueueEpollChannel, epollAndKqueueKqueueChannel);
+ private static IoEventLoopGroup mockIoEventLoopGroup(Class extends IoHandler> ioHandlerClass) {
+ IoEventLoopGroup group = mock(IoEventLoopGroup.class);
+ when(group.isIoType(ioHandlerClass)).thenReturn(true);
+ return group;
}
}
diff --git a/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4ConnectionPoolTests.java b/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4ConnectionPoolTests.java
index ba84eb3e90351..25f02cd4d45f6 100644
--- a/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4ConnectionPoolTests.java
+++ b/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4ConnectionPoolTests.java
@@ -10,7 +10,8 @@
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
-import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.Future;
import org.junit.jupiter.api.AfterAll;
@@ -63,7 +64,7 @@ public class Netty4ConnectionPoolTests {
public static void startTestServerAndEventLoopGroup() {
server = NettyHttpClientLocalTestServer.getServer();
server.start();
- eventLoopGroup = new NioEventLoopGroup(2);
+ eventLoopGroup = new MultiThreadIoEventLoopGroup(2, NioIoHandler.newFactory());
bootstrap = new Bootstrap().group(eventLoopGroup).channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.AUTO_READ, false);
SocketAddress socketAddress = new InetSocketAddress("localhost", server.getPort());
diff --git a/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4Http11ChannelBinaryDataTests.java b/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4Http11ChannelBinaryDataTests.java
index 02ccf7538c9ba..aa9a0ad8cf2dc 100644
--- a/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4Http11ChannelBinaryDataTests.java
+++ b/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4Http11ChannelBinaryDataTests.java
@@ -50,6 +50,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -260,6 +261,7 @@ public void closeAfterDrainingDisconnectsChannel() {
TestMockChannel realChannel = new TestMockChannel();
new DefaultEventLoop().register(realChannel);
Channel spiedChannel = spy(realChannel);
+ doReturn(realChannel.pipeline()).when(spiedChannel).pipeline();
Runnable cleanupTask = () -> {
spiedChannel.disconnect();
spiedChannel.close();
diff --git a/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4PipelineCleanupHandlerTests.java b/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4PipelineCleanupHandlerTests.java
index afd24f2ab29af..6df2d35cffac8 100644
--- a/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4PipelineCleanupHandlerTests.java
+++ b/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4PipelineCleanupHandlerTests.java
@@ -293,6 +293,11 @@ public int getCloseCallCount() {
}
private static class MockEventLoop extends DefaultEventLoop {
+ @Override
+ public boolean inEventLoop(Thread thread) {
+ return true;
+ }
+
@Override
public void execute(Runnable task) {
if (task == null) {
diff --git a/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4UtilityTests.java b/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4UtilityTests.java
index 468f57acffd0f..ffa52f66e01c0 100644
--- a/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4UtilityTests.java
+++ b/sdk/clientcore/http-netty4/src/test/java/io/clientcore/http/netty4/implementation/Netty4UtilityTests.java
@@ -2,15 +2,22 @@
// Licensed under the MIT License.
package io.clientcore.http.netty4.implementation;
+import io.clientcore.core.http.client.HttpProtocolVersion;
import io.clientcore.core.http.models.HttpHeader;
import io.clientcore.core.http.models.HttpHeaderName;
import io.clientcore.core.http.models.HttpHeaders;
import io.clientcore.core.utils.CoreUtils;
+import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslProvider;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import javax.net.ssl.SSLException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -22,11 +29,32 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertLinesMatch;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Timeout(value = 3, unit = TimeUnit.MINUTES)
public class Netty4UtilityTests {
+ @ParameterizedTest
+ @EnumSource(HttpProtocolVersion.class)
+ public void sslContextEnablesHostnameVerification(HttpProtocolVersion protocolVersion) throws SSLException {
+ SslContext context
+ = Netty4Utility.buildSslContext(protocolVersion, builder -> builder.sslProvider(SslProvider.JDK));
+ assertEquals("HTTPS",
+ context.newEngine(UnpooledByteBufAllocator.DEFAULT, "localhost", 443)
+ .getSSLParameters()
+ .getEndpointIdentificationAlgorithm());
+ }
+
+ @Test
+ public void sslContextHonorsHostnameVerificationModifier() throws SSLException {
+ SslContext context = Netty4Utility.buildSslContext(HttpProtocolVersion.HTTP_1_1,
+ builder -> builder.sslProvider(SslProvider.JDK).endpointIdentificationAlgorithm(null));
+ assertNull(context.newEngine(UnpooledByteBufAllocator.DEFAULT, "localhost", 443)
+ .getSSLParameters()
+ .getEndpointIdentificationAlgorithm());
+ }
+
@Test
public void validateNettyVersionsWithWhatThePomSpecifies() {
Map pomVersions = CoreUtils.getProperties(PROPERTIES_FILE_NAME);
diff --git a/sdk/clientcore/http-okhttp3/src/test/java/io/clientcore/http/okhttp3/OkHttpHttpClientBuilderTests.java b/sdk/clientcore/http-okhttp3/src/test/java/io/clientcore/http/okhttp3/OkHttpHttpClientBuilderTests.java
index a68b162b07d6f..9498ba6526916 100644
--- a/sdk/clientcore/http-okhttp3/src/test/java/io/clientcore/http/okhttp3/OkHttpHttpClientBuilderTests.java
+++ b/sdk/clientcore/http-okhttp3/src/test/java/io/clientcore/http/okhttp3/OkHttpHttpClientBuilderTests.java
@@ -505,6 +505,7 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
Configuration simpleEnvProxy
= Configuration.from(new TestConfigurationSource().put(Configuration.HTTP_PROXY, "http://localhost:12345")
+ .put(Configuration.HTTPS_PROXY, "")
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true"));
arguments.add(Arguments.of(true, simpleEnvProxy, defaultUri));
@@ -520,6 +521,7 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
Configuration envProxyWithAuthentication = Configuration.from(EMPTY_SOURCE, EMPTY_SOURCE,
new TestConfigurationSource().put(Configuration.HTTP_PROXY, "http://1:1@localhost:12345")
+ .put(Configuration.HTTPS_PROXY, "")
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true"));
arguments.add(Arguments.of(true, envProxyWithAuthentication, defaultUri));
@@ -544,6 +546,7 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
= () -> baseJavaProxyConfigurationSupplier.get().put(JAVA_NON_PROXY_HOSTS, rawJavaNonProxyHosts);
Supplier envNonProxyHostsSupplier
= () -> new TestConfigurationSource().put(Configuration.HTTP_PROXY, "http://localhost:12345")
+ .put(Configuration.HTTPS_PROXY, "")
.put(Configuration.NO_PROXY, rawEnvNonProxyHosts)
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true");
List> nonProxyHostsSuppliers
@@ -568,6 +571,7 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
= () -> javaNonProxyHostsSupplier.get().put(JAVA_HTTP_PROXY_USER, "1").put(JAVA_HTTP_PROXY_PASSWORD, "1");
Supplier authenticatedEnvNonProxyHostsSupplier
= () -> new TestConfigurationSource().put(Configuration.HTTP_PROXY, "http://1:1@localhost:12345")
+ .put(Configuration.HTTPS_PROXY, "")
.put(Configuration.NO_PROXY, rawEnvNonProxyHosts)
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true");
List> authenticatedNonProxyHostsSuppliers
diff --git a/sdk/communication/azure-communication-common/pom.xml b/sdk/communication/azure-communication-common/pom.xml
index da9da743a7f2d..f52555fe1afdd 100644
--- a/sdk/communication/azure-communication-common/pom.xml
+++ b/sdk/communication/azure-communication-common/pom.xml
@@ -80,12 +80,6 @@
5.13.4
test
-
- io.projectreactor
- reactor-test
- 3.7.19
- test
-
com.azure
azure-core-test
diff --git a/sdk/communication/azure-communication-jobrouter/pom.xml b/sdk/communication/azure-communication-jobrouter/pom.xml
index 835fd5291fe23..810722db9d86c 100644
--- a/sdk/communication/azure-communication-jobrouter/pom.xml
+++ b/sdk/communication/azure-communication-jobrouter/pom.xml
@@ -100,20 +100,6 @@
-
- org.apache.maven.plugins
- maven-enforcer-plugin
- 3.6.3
-
-
-
-
- com.azure:*
-
-
-
-
-
diff --git a/sdk/confidentialledger/azure-security-confidentialledger/pom.xml b/sdk/confidentialledger/azure-security-confidentialledger/pom.xml
index 108c068c76af0..55004dc857428 100644
--- a/sdk/confidentialledger/azure-security-confidentialledger/pom.xml
+++ b/sdk/confidentialledger/azure-security-confidentialledger/pom.xml
@@ -39,6 +39,9 @@
--add-exports com.azure.http.netty/com.azure.core.http.netty=com.azure.security.confidentialledger
--add-reads com.azure.security.confidentialledger=com.azure.http.netty
+ --add-reads com.azure.security.confidentialledger=io.netty.handler
+ --add-reads com.azure.security.confidentialledger=reactor.netty.core
+ --add-reads com.azure.security.confidentialledger=reactor.netty.http
false
diff --git a/sdk/core/azure-core-experimental/pom.xml b/sdk/core/azure-core-experimental/pom.xml
index a47a0a085b764..667da00c56592 100644
--- a/sdk/core/azure-core-experimental/pom.xml
+++ b/sdk/core/azure-core-experimental/pom.xml
@@ -100,7 +100,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/core/azure-core-http-jdk-httpclient/pom.xml b/sdk/core/azure-core-http-jdk-httpclient/pom.xml
index 1315ec0a5e854..5dcd3b7ae31d8 100644
--- a/sdk/core/azure-core-http-jdk-httpclient/pom.xml
+++ b/sdk/core/azure-core-http-jdk-httpclient/pom.xml
@@ -89,7 +89,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/core/azure-core-http-jdk-httpclient/src/test/java/com/azure/core/http/jdk/httpclient/JdkHttpClientBuilderTests.java b/sdk/core/azure-core-http-jdk-httpclient/src/test/java/com/azure/core/http/jdk/httpclient/JdkHttpClientBuilderTests.java
index a13e7b4e45e6e..93a85fea1b911 100644
--- a/sdk/core/azure-core-http-jdk-httpclient/src/test/java/com/azure/core/http/jdk/httpclient/JdkHttpClientBuilderTests.java
+++ b/sdk/core/azure-core-http-jdk-httpclient/src/test/java/com/azure/core/http/jdk/httpclient/JdkHttpClientBuilderTests.java
@@ -147,11 +147,10 @@ public void buildWithHttpProxy() {
@Test
public void buildWithHttpProxyFromEnvConfiguration() {
- Configuration configuration = new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE,
- new TestConfigurationSource()
- .put(Configuration.PROPERTY_HTTP_PROXY,
- "http://" + PROXY_USER_INFO + "localhost:" + PROXY_SERVER_HTTP_PORT)
- .put("java.net.useSystemProxies", "true")).build();
+ Configuration configuration = new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE, new TestConfigurationSource()
+ .put(Configuration.PROPERTY_HTTP_PROXY, "http://" + PROXY_USER_INFO + "localhost:" + PROXY_SERVER_HTTP_PORT)
+ .put(Configuration.PROPERTY_HTTPS_PROXY, "")
+ .put("java.net.useSystemProxies", "true")).build();
configurationProxyTest(configuration);
}
@@ -193,6 +192,7 @@ private static Stream buildWithExplicitConfigurationProxySupplier() {
final Configuration envConfiguration = new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE,
new TestConfigurationSource().put(Configuration.PROPERTY_HTTP_PROXY, "http://localhost:8888")
+ .put(Configuration.PROPERTY_HTTPS_PROXY, "")
.put(Configuration.PROPERTY_NO_PROXY, "localhost")).build();
arguments.add(Arguments.of(envConfiguration));
diff --git a/sdk/core/azure-core-http-netty/CHANGELOG.md b/sdk/core/azure-core-http-netty/CHANGELOG.md
index 147e24463451f..db11cd9233df7 100644
--- a/sdk/core/azure-core-http-netty/CHANGELOG.md
+++ b/sdk/core/azure-core-http-netty/CHANGELOG.md
@@ -8,8 +8,16 @@
### Bugs Fixed
+- Preserved asynchronous file downloads when Netty uses scoped direct buffers on newer JDKs.
+- Declared the Netty transport dependency directly to prevent older transitive transports from being selected.
+
### Other Changes
+#### Dependency Updates
+
+- Migrated Netty dependencies from `4.1.137.Final` to `4.2.19.Final`.
+- Upgraded Reactor Netty from `1.2.18` to `1.3.7` and Reactor from `3.7.19` to `3.8.7`.
+
## 1.16.8 (2026-09-29)
### Other Changes
diff --git a/sdk/core/azure-core-http-netty/README.md b/sdk/core/azure-core-http-netty/README.md
index bb7dcfcce92ec..0489ce5573be8 100644
--- a/sdk/core/azure-core-http-netty/README.md
+++ b/sdk/core/azure-core-http-netty/README.md
@@ -55,6 +55,47 @@ add the direct dependency to your project as follows.
## Key concepts
+### Netty 4.2 compatibility
+
+Starting with `1.17.0-beta.1`, this library uses Netty `4.2.19.Final`, Reactor Netty `1.3.7`, and Reactor `3.8.7`.
+Netty 4.1 and 4.2 must not be mixed on the classpath. If your application or framework manages these dependencies,
+align the entire Netty and Reactor stack when upgrading. Java 8 remains supported.
+
+Netty 4.2 enables TLS hostname verification by default and changes its default buffer allocator to the adaptive allocator.
+This library retains pooled allocation unless the application explicitly configures an allocator.
+See the [Netty 4.2 migration guide](https://netty.io/wiki/netty-4.2-migration-guide.html#recommended-upgrade-process)
+for application-level migration guidance.
+
+### Native transports on the module path
+
+On JDK 11 or later, Netty 4.2 packages the epoll/kqueue classes and their platform-specific JNI libraries
+as **separate explicit Java modules**. Having the JARs on the module path does not resolve either module
+automatically. A named-module application that wants native transport must include the matching
+`netty-transport-native-epoll` or `netty-transport-native-kqueue` classifier JAR (which brings in its
+`netty-transport-classes-*` JAR) and add **both** modules as launch roots:
+
+| Platform and classifier | Launch option |
+| --- | --- |
+| Linux x86_64 (`netty-transport-native-epoll:linux-x86_64`) | `--add-modules=io.netty.transport.classes.epoll,io.netty.transport.epoll.linux.x86_64` |
+| macOS x86_64 (`netty-transport-native-kqueue:osx-x86_64`) | `--add-modules=io.netty.transport.classes.kqueue,io.netty.transport.kqueue.osx.x86_64` |
+
+For example, with the Linux classifier JAR and its dependencies on the module path:
+
+```text
+java --module-path "path-to-application-and-dependency-JARs" --add-modules=io.netty.transport.classes.epoll,io.netty.transport.epoll.linux.x86_64 -m my.app/com.example.Main
+```
+
+This library declares the x86_64 classifier dependencies, but applications using another architecture
+must supply the corresponding native classifier and resolve its module instead. Check that JAR's
+module name with `jar --describe-module --file --release 11`; see
+[Netty's native transport guide](https://netty.io/wiki/native-transports.html#using-the-native-transports)
+for classifier details. Resolving only the classes
+leaves the bundled JNI library invisible to Netty; `requires static` in a library descriptor does not
+resolve either optional module at run time. If native transport is unavailable, Reactor Netty uses
+JDK NIO. These launch options do not apply to Java 8 or to applications using the classpath rather
+than the module path, and resolving the modules alone does not guarantee native transport is usable
+on the current OS.
+
## Examples
The following sections provide several code snippets covering some of the most common client configuration scenarios.
@@ -220,5 +261,3 @@ For details on contributing to this repository, see the [contributing guide](htt
[logging]: https://learn.microsoft.com/azure/developer/java/sdk/logging-overview
[jdk_link]: https://learn.microsoft.com/java/azure/jdk/?view=azure-java-stable
[java8_client_compatibility]: https://learn.microsoft.com/azure/security/fundamentals/azure-ca-details?tabs=root-and-subordinate-cas-list#client-compatibility-for-public-pkis
-
-
diff --git a/sdk/core/azure-core-http-netty/cgmanifest.json b/sdk/core/azure-core-http-netty/cgmanifest.json
index dd6d6dd04339b..0f3d3948a2dd0 100644
--- a/sdk/core/azure-core-http-netty/cgmanifest.json
+++ b/sdk/core/azure-core-http-netty/cgmanifest.json
@@ -9,7 +9,7 @@
"maven": {
"groupId": "io.netty",
"artifactId": "netty-handler-proxy",
- "version": "4.1.110.Final"
+ "version": "4.2.19.Final"
}
}
}
diff --git a/sdk/core/azure-core-http-netty/pom.xml b/sdk/core/azure-core-http-netty/pom.xml
index 5e71c171f2f08..1b4a40118e828 100644
--- a/sdk/core/azure-core-http-netty/pom.xml
+++ b/sdk/core/azure-core-http-netty/pom.xml
@@ -64,7 +64,7 @@
false
false
- 4.1.138.Final
+ 4.2.19.Final
2.0.84.Final
@@ -85,49 +85,70 @@
io.netty
netty-handler
- 4.1.138.Final
+ 4.2.19.Final
io.netty
netty-handler-proxy
- 4.1.138.Final
+ 4.2.19.Final
io.netty
netty-buffer
- 4.1.138.Final
+ 4.2.19.Final
io.netty
- netty-codec
- 4.1.138.Final
+ netty-transport
+ 4.2.19.Final
+
+
+ io.netty
+ netty-codec-base
+ 4.2.19.Final
io.netty
netty-codec-http
- 4.1.138.Final
+ 4.2.19.Final
io.netty
netty-codec-http2
- 4.1.138.Final
+ 4.2.19.Final
+
+
+ io.netty
+ netty-codec-http3
+ 4.2.19.Final
+
+
+ io.netty
+ netty-resolver-dns
+ 4.2.19.Final
+
+
+ io.netty
+ netty-resolver-dns-native-macos
+ 4.2.19.Final
+ osx-x86_64
io.netty
netty-transport-native-unix-common
- 4.1.138.Final
+ 4.2.19.Final
io.netty
netty-transport-native-epoll
- 4.1.138.Final
+ 4.2.19.Final
linux-x86_64
io.netty
netty-transport-native-kqueue
- 4.1.138.Final
+ 4.2.19.Final
osx-x86_64
@@ -141,13 +162,13 @@
io.projectreactor.netty
reactor-netty-http
- 1.2.18
+ 1.3.7
io.netty
netty-common
- 4.1.138.Final
+ 4.2.19.Final
@@ -161,7 +182,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
@@ -209,17 +230,21 @@
io.netty:netty-tcnative-boringssl-static:[2.0.84.Final]
- io.projectreactor.netty:reactor-netty-http:[1.2.18]
- io.netty:netty-buffer:[4.1.138.Final]
- io.netty:netty-common:[4.1.138.Final]
- io.netty:netty-codec:[4.1.138.Final]
- io.netty:netty-codec-http:[4.1.138.Final]
- io.netty:netty-codec-http2:[4.1.138.Final]
- io.netty:netty-handler:[4.1.138.Final]
- io.netty:netty-handler-proxy:[4.1.138.Final]
- io.netty:netty-transport-native-unix-common:[4.1.138.Final]
- io.netty:netty-transport-native-epoll:[4.1.138.Final]
- io.netty:netty-transport-native-kqueue:[4.1.138.Final]
+ io.projectreactor.netty:reactor-netty-http:[1.3.7]
+ io.netty:netty-buffer:[4.2.19.Final]
+ io.netty:netty-transport:[4.2.19.Final]
+ io.netty:netty-common:[4.2.19.Final]
+ io.netty:netty-codec-base:[4.2.19.Final]
+ io.netty:netty-codec-http:[4.2.19.Final]
+ io.netty:netty-codec-http2:[4.2.19.Final]
+ io.netty:netty-codec-http3:[4.2.19.Final]
+ io.netty:netty-handler:[4.2.19.Final]
+ io.netty:netty-handler-proxy:[4.2.19.Final]
+ io.netty:netty-resolver-dns:[4.2.19.Final]
+ io.netty:netty-resolver-dns-native-macos:[4.2.19.Final]
+ io.netty:netty-transport-native-unix-common:[4.2.19.Final]
+ io.netty:netty-transport-native-epoll:[4.2.19.Final]
+ io.netty:netty-transport-native-kqueue:[4.2.19.Final]
diff --git a/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/NettyAsyncHttpClientBuilder.java b/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/NettyAsyncHttpClientBuilder.java
index 1ee286d12e3ac..2909fb0cd2926 100644
--- a/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/NettyAsyncHttpClientBuilder.java
+++ b/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/NettyAsyncHttpClientBuilder.java
@@ -16,12 +16,13 @@
import com.azure.core.util.Context;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.logging.ClientLogger;
+import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
-import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.logging.LoggingHandler;
import io.netty.resolver.DefaultAddressResolverGroup;
import io.netty.resolver.NoopAddressResolverGroup;
+import io.netty.util.internal.SystemPropertyUtil;
import reactor.netty.Connection;
import reactor.netty.NettyPipeline;
import reactor.netty.http.HttpDecoderSpec;
@@ -151,7 +152,7 @@ public NettyAsyncHttpClientBuilder() {
* .wiretap(TcpClient.class.getName(), LogLevel.INFO);
* // Create an HttpClient based on above reactor-netty client and configure EventLoop count.
* HttpClient client = new NettyAsyncHttpClientBuilder(baseHttpClient)
- * .eventLoopGroup(new NioEventLoopGroup(5))
+ * .eventLoopGroup(new MultiThreadIoEventLoopGroup(5, NioIoHandler.newFactory()))
* .build();
*
*
@@ -202,6 +203,16 @@ public com.azure.core.http.HttpClient build() {
nettyHttpClient = HttpClient.create();
}
+ // Keep the pooled default while respecting allocator choices made by the application.
+ if (!nettyHttpClient.configuration().options().containsKey(ChannelOption.ALLOCATOR)
+ && !SystemPropertyUtil.contains("io.netty.allocator.type")) {
+ // If ChannelOptions and io.netty.allocator.type wasn't configured with an allocator fallback to the default
+ // setting from Netty 4.1 which was the pooled allocator. Otherwise, respect the configuration, be it
+ // ChannelOptions or io.netty.allocator.type. In the future, this should be validated for if this should use
+ // the new Netty 4.2 default which is an adaptive allocator.
+ nettyHttpClient = nettyHttpClient.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
+ }
+
// If a resolver hasn't been set, set the default one.
if (nettyHttpClient.configuration().resolver() == null) {
nettyHttpClient = nettyHttpClient.resolver(DefaultAddressResolverGroup.INSTANCE);
@@ -385,12 +396,12 @@ public NettyAsyncHttpClientBuilder port(int port) {
/**
* Sets the NIO event loop group that will be used to run IO loops.
*
- * @param nioEventLoopGroup The {@link NioEventLoopGroup} that will run IO loops.
+ * @param nioEventLoopGroup The {@link io.netty.channel.nio.NioEventLoopGroup} that will run IO loops.
* @return the updated NettyAsyncHttpClientBuilder object.
* @deprecated deprecated in favor of {@link #eventLoopGroup(EventLoopGroup)}.
*/
@Deprecated
- public NettyAsyncHttpClientBuilder nioEventLoopGroup(NioEventLoopGroup nioEventLoopGroup) {
+ public NettyAsyncHttpClientBuilder nioEventLoopGroup(io.netty.channel.nio.NioEventLoopGroup nioEventLoopGroup) {
this.eventLoopGroup = nioEventLoopGroup;
return this;
}
@@ -404,7 +415,7 @@ public NettyAsyncHttpClientBuilder nioEventLoopGroup(NioEventLoopGroup nioEventL
*
* int threadCount = 5;
* HttpClient client = new NettyAsyncHttpClientBuilder()
- * .eventLoopGroup(new NioEventLoopGroup(threadCount))
+ * .eventLoopGroup(new MultiThreadIoEventLoopGroup(threadCount, NioIoHandler.newFactory()))
* .build();
*
*
diff --git a/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/ByteBufWriteSubscriber.java b/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/ByteBufWriteSubscriber.java
index a827169f0e2b6..4b4fb0b6c7ef6 100644
--- a/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/ByteBufWriteSubscriber.java
+++ b/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/ByteBufWriteSubscriber.java
@@ -23,6 +23,7 @@ public class ByteBufWriteSubscriber implements Subscriber {
private final MonoSink emitter;
private final int bufferSize;
private final ByteBuf buffer;
+ private final boolean useHeapBuffer;
// This subscriber is effectively synchronous so there is no need for these fields to be volatile.
private Subscription subscription;
@@ -36,12 +37,28 @@ public class ByteBufWriteSubscriber implements Subscriber {
* @param bodySize The size of the request body, if known.
*/
public ByteBufWriteSubscriber(ExceptionThrowingConsumer writer, MonoSink emitter, Long bodySize) {
+ this(writer, emitter, bodySize, false);
+ }
+
+ /**
+ * Creates a new {@link ByteBufWriteSubscriber} with control over direct buffer writes.
+ *
+ * @param writer Where to write the {@link ByteBuf ByteBufs}.
+ * @param emitter {@link MonoSink} to emit completion or error signals.
+ * @param bodySize The size of the request body, if known.
+ * @param useHeapBuffer Whether to copy all writes through a bounded heap buffer.
+ */
+ public ByteBufWriteSubscriber(ExceptionThrowingConsumer writer, MonoSink emitter, Long bodySize,
+ boolean useHeapBuffer) {
this.writer = writer;
this.emitter = emitter;
+ this.useHeapBuffer = useHeapBuffer;
// Create a writing buffer that has a minimum bound of 8KB and a maximum bound of 64KB.
// This is safe as the writer performs writes synchronously.
this.bufferSize = (bodySize == null) ? 65536 : (int) Math.max(8192, Math.min(bodySize, 65536));
- this.buffer = PooledByteBufAllocator.DEFAULT.buffer(bufferSize);
+ this.buffer = useHeapBuffer
+ ? PooledByteBufAllocator.DEFAULT.heapBuffer(bufferSize)
+ : PooledByteBufAllocator.DEFAULT.buffer(bufferSize);
}
@Override
@@ -69,11 +86,25 @@ public void onNext(ByteBuf bytes) {
return;
}
- if (bytes.readableBytes() > bufferSize) {
+ if (useHeapBuffer) {
+ while (bytes.isReadable()) {
+ buffer.writeBytes(bytes, Math.min(bytes.readableBytes(), buffer.writableBytes()));
+ if (!buffer.isWritable()) {
+ write(buffer);
+ if (done) {
+ return;
+ }
+ buffer.clear();
+ }
+ }
+ } else if (bytes.readableBytes() > bufferSize) {
// If the next ByteBuf is larger than the buffer write the buffer, if there is any data to write, then
// write the passed ByteBuf without buffering.
if (buffer.readableBytes() > 0) {
write(buffer);
+ if (done) {
+ return;
+ }
buffer.clear();
}
@@ -84,6 +115,9 @@ public void onNext(ByteBuf bytes) {
} else {
// If the next ByteBuf can't fit in the buffer write the buffer then buffer the passed ByteBuf.
write(buffer);
+ if (done) {
+ return;
+ }
buffer.clear();
buffer.writeBytes(bytes);
}
diff --git a/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/NettyAsyncHttpResponse.java b/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/NettyAsyncHttpResponse.java
index 0634e15241261..080aec218ac09 100644
--- a/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/NettyAsyncHttpResponse.java
+++ b/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/NettyAsyncHttpResponse.java
@@ -80,9 +80,9 @@ public Mono getBodyAsInputStream() {
@Override
public Mono writeBodyToAsync(AsynchronousByteChannel channel) {
Long length = getContentLength();
- return Mono.using(() -> this,
- response -> Mono.create(sink -> response.bodyIntern()
- .subscribe(new ByteBufWriteSubscriber(byteBuffer -> channel.write(byteBuffer).get(), sink, length))),
+ // Asynchronous file channels cannot accept scoped direct buffers allocated by Netty on newer JDKs.
+ return Mono.using(() -> this, response -> Mono.create(sink -> response.bodyIntern()
+ .subscribe(new ByteBufWriteSubscriber(byteBuffer -> channel.write(byteBuffer).get(), sink, length, true))),
NettyAsyncHttpResponse::close);
}
diff --git a/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/NettyUtility.java b/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/NettyUtility.java
index a0eba784b12f7..233bca94b296e 100644
--- a/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/NettyUtility.java
+++ b/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/NettyUtility.java
@@ -36,8 +36,10 @@ public final class NettyUtility {
// List of Netty artifacts that should match the 'netty.version' property in the pom.xml file.
// Non-native dependencies are required while native dependencies are optional. Without the native dependencies
// the SDK will fall back to using the JDK implementations.
- private static final List REQUIRED_NETTY_VERSION_ARTIFACTS = Arrays.asList("netty-common", "netty-handler",
- "netty-handler-proxy", "netty-buffer", "netty-codec", "netty-codec-http", "netty-codec-http2");
+ private static final List REQUIRED_NETTY_VERSION_ARTIFACTS
+ = Arrays.asList("netty-common", "netty-handler", "netty-handler-proxy", "netty-buffer", "netty-codec-base",
+ "netty-codec-compression", "netty-codec-dns", "netty-codec-http", "netty-codec-http2", "netty-codec-socks",
+ "netty-resolver", "netty-resolver-dns", "netty-transport");
private static final List OPTIONAL_NETTY_VERSION_ARTIFACTS = Arrays
.asList("netty-transport-native-unix-common", "netty-transport-native-epoll", "netty-transport-native-kqueue");
diff --git a/sdk/core/azure-core-http-netty/src/main/java/module-info.java b/sdk/core/azure-core-http-netty/src/main/java/module-info.java
index 30d30814abe41..4ac1c23b8ae0f 100644
--- a/sdk/core/azure-core-http-netty/src/main/java/module-info.java
+++ b/sdk/core/azure-core-http-netty/src/main/java/module-info.java
@@ -5,11 +5,14 @@
requires transitive com.azure.core;
requires io.netty.buffer;
requires io.netty.codec;
+ requires io.netty.codec.compression;
requires io.netty.codec.http;
+ requires io.netty.codec.http2;
requires io.netty.common;
requires io.netty.handler;
requires io.netty.handler.proxy;
requires io.netty.resolver;
+ requires io.netty.resolver.dns;
requires io.netty.transport;
requires reactor.netty.core;
requires reactor.netty.http;
diff --git a/sdk/core/azure-core-http-netty/src/main/resources/META-INF/native-image/com.azure/azure-core-http-netty/native-image.properties b/sdk/core/azure-core-http-netty/src/main/resources/META-INF/native-image/com.azure/azure-core-http-netty/native-image.properties
index 58d7ea86ca259..86c33a53652fa 100644
--- a/sdk/core/azure-core-http-netty/src/main/resources/META-INF/native-image/com.azure/azure-core-http-netty/native-image.properties
+++ b/sdk/core/azure-core-http-netty/src/main/resources/META-INF/native-image/com.azure/azure-core-http-netty/native-image.properties
@@ -12,7 +12,6 @@ Args=\
io.netty.channel.epoll.Native,\
io.netty.channel.kqueue.KQueue,\
io.netty.channel.kqueue.KQueueEventArray,\
- io.netty.channel.kqueue.KQueueEventLoop,\
io.netty.channel.kqueue.Native,\
io.netty.channel.unix.Errors,\
io.netty.channel.unix.IovArray,\
@@ -24,10 +23,6 @@ Args=\
io.netty.handler.codec.http.websocketx.WebSocket00FrameEncoder,\
io.netty.handler.ssl.BouncyCastleAlpnSslUtils,\
io.netty.handler.ssl.ConscryptAlpnSslEngine,\
- io.netty.handler.ssl.JdkNpnApplicationProtocolNegotiator,\
- io.netty.handler.ssl.JettyAlpnSslEngine$ClientEngine,\
- io.netty.handler.ssl.JettyAlpnSslEngine$ServerEngine,\
- io.netty.handler.ssl.JettyNpnSslEngine,\
io.netty.handler.ssl.OpenSsl,\
io.netty.handler.ssl.OpenSslAsyncPrivateKeyMethod,\
io.netty.handler.ssl.OpenSslPrivateKeyMethod,\
@@ -44,4 +39,3 @@ Args=\
io.netty.internal.tcnative.SSLPrivateKeyMethod,\
io.netty.util.AbstractReferenceCounted,\
io.netty.util.internal.logging.Log4JLogger
-
diff --git a/sdk/core/azure-core-http-netty/src/samples/java/com/azure/core/http/netty/NettyAsyncHttpClientBuilderJavaDocCodeSnippets.java b/sdk/core/azure-core-http-netty/src/samples/java/com/azure/core/http/netty/NettyAsyncHttpClientBuilderJavaDocCodeSnippets.java
index 348e8b1b20f89..0d60c8e6b6f22 100644
--- a/sdk/core/azure-core-http-netty/src/samples/java/com/azure/core/http/netty/NettyAsyncHttpClientBuilderJavaDocCodeSnippets.java
+++ b/sdk/core/azure-core-http-netty/src/samples/java/com/azure/core/http/netty/NettyAsyncHttpClientBuilderJavaDocCodeSnippets.java
@@ -7,7 +7,8 @@
import com.azure.core.http.HttpMethod;
import com.azure.core.http.HttpRequest;
import com.azure.core.http.ProxyOptions;
-import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.nio.NioIoHandler;
import io.netty.handler.logging.LogLevel;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.tcp.TcpClient;
@@ -39,7 +40,7 @@ public void fixedThreadPoolSample() {
// BEGIN: com.azure.core.http.netty.NettyAsyncHttpClientBuilder#eventLoopGroup
int threadCount = 5;
HttpClient client = new NettyAsyncHttpClientBuilder()
- .eventLoopGroup(new NioEventLoopGroup(threadCount))
+ .eventLoopGroup(new MultiThreadIoEventLoopGroup(threadCount, NioIoHandler.newFactory()))
.build();
// END: com.azure.core.http.netty.NettyAsyncHttpClientBuilder#eventLoopGroup
}
@@ -87,7 +88,7 @@ public void fromExistingReactorNettyClient() {
.wiretap(TcpClient.class.getName(), LogLevel.INFO);
// Create an HttpClient based on above reactor-netty client and configure EventLoop count.
HttpClient client = new NettyAsyncHttpClientBuilder(baseHttpClient)
- .eventLoopGroup(new NioEventLoopGroup(5))
+ .eventLoopGroup(new MultiThreadIoEventLoopGroup(5, NioIoHandler.newFactory()))
.build();
// END: com.azure.core.http.netty.from-existing-http-client
}
diff --git a/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/NettyAsyncHttpClientBuilderTests.java b/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/NettyAsyncHttpClientBuilderTests.java
index c6a4f68e1b0e8..e93d31a8978ce 100644
--- a/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/NettyAsyncHttpClientBuilderTests.java
+++ b/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/NettyAsyncHttpClientBuilderTests.java
@@ -11,17 +11,23 @@
import com.azure.core.util.Configuration;
import com.azure.core.util.ConfigurationBuilder;
import com.azure.core.util.ConfigurationSource;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.PooledByteBufAllocator;
+import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPromise;
-import io.netty.channel.nio.NioEventLoop;
-import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.SingleThreadIoEventLoop;
+import io.netty.channel.nio.NioIoHandler;
import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.proxy.HttpProxyHandler;
import io.netty.handler.proxy.ProxyHandler;
import io.netty.handler.proxy.Socks4ProxyHandler;
import io.netty.handler.proxy.Socks5ProxyHandler;
+import io.netty.util.internal.SystemPropertyUtil;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
@@ -52,6 +58,7 @@
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -104,6 +111,24 @@ public void startingWithNullClientThrows() {
assertThrows(NullPointerException.class, () -> new NettyAsyncHttpClientBuilder(null));
}
+ @Test
+ public void defaultAllocatorRespectsSystemProperty() {
+ NettyAsyncHttpClient client = (NettyAsyncHttpClient) new NettyAsyncHttpClientBuilder().build();
+ ByteBufAllocator expectedAllocator = SystemPropertyUtil.contains("io.netty.allocator.type")
+ ? ByteBufAllocator.DEFAULT
+ : PooledByteBufAllocator.DEFAULT;
+ Object configuredAllocator = client.nettyClient.configuration().options().get(ChannelOption.ALLOCATOR);
+ assertSame(expectedAllocator, configuredAllocator == null ? ByteBufAllocator.DEFAULT : configuredAllocator);
+ }
+
+ @Test
+ public void preconfiguredAllocatorIsMaintained() {
+ HttpClient baseClient = HttpClient.create().option(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT);
+ NettyAsyncHttpClient client = (NettyAsyncHttpClient) new NettyAsyncHttpClientBuilder(baseClient).build();
+ assertSame(UnpooledByteBufAllocator.DEFAULT,
+ client.nettyClient.configuration().options().get(ChannelOption.ALLOCATOR));
+ }
+
/**
* Tests that creating a client with a {@link ConnectionProvider} will use it to create connections to a server.
*/
@@ -265,6 +290,8 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
Configuration simpleEnvProxy = new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE,
new TestConfigurationSource().put(Configuration.PROPERTY_HTTP_PROXY, "http://localhost:12345")
+ .put(Configuration.PROPERTY_HTTPS_PROXY, "")
+ .put(Configuration.PROPERTY_NO_PROXY, "")
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true")).build();
arguments.add(Arguments.of(true, false, simpleEnvProxy, DEFAULT_URL));
@@ -279,6 +306,8 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
Configuration envProxyWithAuthentication = new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE,
new TestConfigurationSource().put(Configuration.PROPERTY_HTTP_PROXY, "http://1:1@localhost:12345")
+ .put(Configuration.PROPERTY_HTTPS_PROXY, "")
+ .put(Configuration.PROPERTY_NO_PROXY, "")
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true")).build();
arguments.add(Arguments.of(true, true, envProxyWithAuthentication, DEFAULT_URL));
@@ -304,6 +333,7 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
= () -> baseJavaProxyConfigurationSupplier.get().put(JAVA_NON_PROXY_HOSTS, rawJavaNonProxyHosts);
Supplier envNonProxyHostsSupplier
= () -> new TestConfigurationSource().put(Configuration.PROPERTY_HTTP_PROXY, "http://localhost:12345")
+ .put(Configuration.PROPERTY_HTTPS_PROXY, "")
.put(Configuration.PROPERTY_NO_PROXY, rawEnvNonProxyHosts)
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true");
@@ -331,6 +361,7 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
= () -> javaNonProxyHostsSupplier.get().put(JAVA_HTTP_PROXY_USER, "1").put(JAVA_HTTP_PROXY_PASSWORD, "1");
Supplier authenticatedEnvNonProxyHostsSupplier
= () -> new TestConfigurationSource().put(Configuration.PROPERTY_HTTP_PROXY, "http://1:1@localhost:12345")
+ .put(Configuration.PROPERTY_HTTPS_PROXY, "")
.put(Configuration.PROPERTY_NO_PROXY, rawEnvNonProxyHosts)
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true");
@@ -524,22 +555,26 @@ public void buildPortClient() {
public void buildEventLoopClient() {
String expectedThreadName = "testEventLoop";
HttpClient validatorClient = HttpClient.create().doAfterResponseSuccess((response, connection) -> {
- // Validate that the EventLoop being used is a NioEventLoop.
- NioEventLoop eventLoop = (NioEventLoop) connection.channel().eventLoop();
+ SingleThreadIoEventLoop eventLoop = (SingleThreadIoEventLoop) connection.channel().eventLoop();
assertNotNull(eventLoop);
assertEquals(expectedThreadName, eventLoop.threadProperties().name());
});
- NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(1, (Runnable r) -> new Thread(r, expectedThreadName));
+ MultiThreadIoEventLoopGroup eventLoopGroup = new MultiThreadIoEventLoopGroup(1,
+ (Runnable r) -> new Thread(r, expectedThreadName), NioIoHandler.newFactory());
- NettyAsyncHttpClient nettyClient
- = (NettyAsyncHttpClient) new NettyAsyncHttpClientBuilder(validatorClient).eventLoopGroup(eventLoopGroup)
- .build();
+ try {
+ NettyAsyncHttpClient nettyClient
+ = (NettyAsyncHttpClient) new NettyAsyncHttpClientBuilder(validatorClient).eventLoopGroup(eventLoopGroup)
+ .build();
- StepVerifier.create(nettyClient.send(new HttpRequest(HttpMethod.GET, DEFAULT_URL)))
- .assertNext(response -> assertEquals(200, response.getStatusCode()))
- .verifyComplete();
+ StepVerifier.create(nettyClient.send(new HttpRequest(HttpMethod.GET, DEFAULT_URL)))
+ .assertNext(response -> assertEquals(200, response.getStatusCode()))
+ .verifyComplete();
+ } finally {
+ eventLoopGroup.shutdownGracefully().syncUninterruptibly();
+ }
}
@ParameterizedTest
diff --git a/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/NettyNativeTransportModulesTests.java b/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/NettyNativeTransportModulesTests.java
new file mode 100644
index 0000000000000..198b9d5bfc8ed
--- /dev/null
+++ b/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/NettyNativeTransportModulesTests.java
@@ -0,0 +1,126 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.core.http.netty;
+
+import io.netty.util.Version;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
+import org.junit.jupiter.api.condition.JRE;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Verifies native module resolution without loading platform-specific JNI libraries.
+ */
+@EnabledForJreRange(min = JRE.JAVA_11)
+public class NettyNativeTransportModulesTests {
+ private static final String EPOLL_CLASSES = "io.netty.transport.classes.epoll";
+ private static final String EPOLL_NATIVE = "io.netty.transport.epoll.linux.x86_64";
+ private static final String KQUEUE_CLASSES = "io.netty.transport.classes.kqueue";
+ private static final String KQUEUE_NATIVE = "io.netty.transport.kqueue.osx.x86_64";
+
+ @ParameterizedTest
+ @MethodSource("nativeTransports")
+ @Timeout(value = 30, unit = TimeUnit.SECONDS)
+ public void nativeTransportRequiresBothModuleRoots(String classesModule, String nativeModule) throws Exception {
+ String withoutRoots = resolveModules("io.netty.transport");
+ assertFalse(isResolved(withoutRoots, classesModule), withoutRoots);
+ assertFalse(isResolved(withoutRoots, nativeModule), withoutRoots);
+
+ String withClasses = resolveModules("io.netty.transport", classesModule);
+ assertTrue(isResolved(withClasses, classesModule), withClasses);
+ assertFalse(isResolved(withClasses, nativeModule), withClasses);
+
+ String withBoth = resolveModules("io.netty.transport", classesModule, nativeModule);
+ assertTrue(isResolved(withBoth, classesModule), withBoth);
+ assertTrue(isResolved(withBoth, nativeModule), withBoth);
+ }
+
+ private static Stream nativeTransports() {
+ return Stream.of(Arguments.of(EPOLL_CLASSES, EPOLL_NATIVE), Arguments.of(KQUEUE_CLASSES, KQUEUE_NATIVE));
+ }
+
+ private static boolean isResolved(String output, String name) {
+ return output.contains("root " + name + " ")
+ || output.contains(" requires " + name + " ")
+ || output.contains(" binds " + name + " ");
+ }
+
+ private static String resolveModules(String... roots) throws Exception {
+ String modulePath = modulePath();
+ String executable
+ = System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win") ? "java.exe" : "java";
+ String java = Paths.get(System.getProperty("java.home"), "bin", executable).toString();
+ List command = Arrays.asList(java, "--module-path", modulePath, "--add-modules",
+ String.join(",", roots), "--show-module-resolution", "-version");
+
+ Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ try (InputStream input = process.getInputStream()) {
+ byte[] buffer = new byte[4096];
+ int count;
+ while ((count = input.read(buffer)) != -1) {
+ output.write(buffer, 0, count);
+ }
+ }
+
+ String text = new String(output.toByteArray(), StandardCharsets.UTF_8);
+ assertEquals(0, process.waitFor(), text);
+ return text;
+ }
+
+ private static String modulePath() {
+ Version nettyCommon
+ = Version.identify(NettyNativeTransportModulesTests.class.getClassLoader()).get("netty-common");
+ assertNotNull(nettyCommon, "Missing Netty common version");
+ String version = nettyCommon.artifactVersion();
+ // Surefire separates resolved modules from the test classpath containing the optional native JARs.
+ List entries = Stream.of("surefire.test.class.path", "jdk.module.path", "java.class.path")
+ .map(System::getProperty)
+ .filter(value -> value != null && !value.isEmpty())
+ .flatMap(value -> Arrays.stream(value.split(Pattern.quote(File.pathSeparator))))
+ .map(Paths::get)
+ .distinct()
+ .collect(Collectors.toList());
+ assertFalse(entries.isEmpty(), "Missing test classpath and module path");
+ List jars = Arrays.asList(jarFromClasspath(entries, "netty-common-" + version + ".jar"),
+ jarFromClasspath(entries, "netty-buffer-" + version + ".jar"),
+ jarFromClasspath(entries, "netty-transport-" + version + ".jar"),
+ jarFromClasspath(entries, "netty-resolver-" + version + ".jar"),
+ jarFromClasspath(entries, "netty-transport-native-unix-common-" + version + ".jar"),
+ jarFromClasspath(entries, "netty-transport-classes-epoll-" + version + ".jar"),
+ jarFromClasspath(entries, "netty-transport-classes-kqueue-" + version + ".jar"),
+ jarFromClasspath(entries, "netty-transport-native-epoll-" + version + "-linux-x86_64.jar"),
+ jarFromClasspath(entries, "netty-transport-native-kqueue-" + version + "-osx-x86_64.jar"));
+ return jars.stream().distinct().map(Path::toString).collect(Collectors.joining(File.pathSeparator));
+ }
+
+ private static Path jarFromClasspath(List entries, String fileName) {
+ List matches = entries.stream()
+ .filter(path -> path.getFileName().toString().equals(fileName))
+ .collect(Collectors.toList());
+ assertEquals(1, matches.size(), "Expected one Netty test dependency for " + fileName);
+ return matches.get(0);
+ }
+}
diff --git a/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/implementation/ByteBufWriterSubscriberTests.java b/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/implementation/ByteBufWriterSubscriberTests.java
index 752bc322d0a4d..988ba5467941a 100644
--- a/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/implementation/ByteBufWriterSubscriberTests.java
+++ b/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/implementation/ByteBufWriterSubscriberTests.java
@@ -6,11 +6,13 @@
import com.azure.core.http.netty.mocking.MockMonoSink;
import com.azure.core.http.netty.mocking.WriteCountTrackingChannel;
import io.netty.buffer.ByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
import org.reactivestreams.Subscription;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -30,14 +32,55 @@
import static com.azure.core.validation.http.HttpValidatonUtils.assertArraysEqual;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Tests {@link ByteBufWriteSubscriber}.
*/
@SuppressWarnings("resource")
public class ByteBufWriterSubscriberTests {
+ @ParameterizedTest
+ @ValueSource(ints = { 4096, 65536, 131136 })
+ public void heapBufferWritesDirectBuffersInBoundedChunks(int size) {
+ byte[] expected = new byte[size];
+ ThreadLocalRandom.current().nextBytes(expected);
+ ByteBuf data = PooledByteBufAllocator.DEFAULT.directBuffer(size).writeBytes(expected);
+ WriteCountTrackingChannel channel = new WriteCountTrackingChannel();
+ try {
+ StepVerifier
+ .create(Mono.create(sink -> Flux.just(data).subscribe(new ByteBufWriteSubscriber(buffer -> {
+ assertFalse(buffer.isDirect());
+ assertTrue(buffer.remaining() <= 65536);
+ channel.write(buffer);
+ }, sink, null, true))))
+ .verifyComplete();
+
+ assertArraysEqual(expected, channel.getDataWritten());
+ } finally {
+ data.release();
+ }
+ }
+
+ @Test
+ public void heapBufferWriteFailureStopsBeforeWritingRemainingData() {
+ ByteBuf data = PooledByteBufAllocator.DEFAULT.directBuffer(131136).writeZero(131136);
+ AtomicInteger writes = new AtomicInteger();
+ try {
+ StepVerifier
+ .create(Mono.create(sink -> Flux.just(data).subscribe(new ByteBufWriteSubscriber(buffer -> {
+ writes.incrementAndGet();
+ throw new IOException("write failed");
+ }, sink, null, true))))
+ .verifyError(IOException.class);
+ assertEquals(1, writes.get());
+ } finally {
+ data.release();
+ }
+ }
+
/**
* Tests that when multiple subscriptions are made to the subscriber all subscriptions after the first are ignored
* and cancelled.
diff --git a/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/implementation/NettyUtilityTests.java b/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/implementation/NettyUtilityTests.java
index 5a71bba892f80..0b6013caa7846 100644
--- a/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/implementation/NettyUtilityTests.java
+++ b/sdk/core/azure-core-http-netty/src/test/java/com/azure/core/http/netty/implementation/NettyUtilityTests.java
@@ -23,6 +23,10 @@ public void validateNettyVersionsWithWhatThePomSpecifies() {
// Should never have version mismatches when running tests, that would mean either the version properties are
// wrong or there is a dependency diamond within azure-core-http-netty. Either way, it should be fixed.
assertFalse(logInformation.shouldLog());
+ assertTrue(logInformation.classpathNettyVersions.containsKey("io.netty:netty-codec-base"));
+ assertTrue(logInformation.classpathNettyVersions.containsKey("io.netty:netty-codec-compression"));
+ assertTrue(logInformation.classpathNettyVersions.containsKey("io.netty:netty-codec-socks"));
+ assertFalse(logInformation.classpathNettyVersions.containsKey("io.netty:netty-codec"));
for (String artifactFullName : logInformation.classpathNettyVersions.keySet()) {
assertTrue(artifactFullName.startsWith("io.netty:netty-"),
"All artifact information should start with 'io.netty:netty-'");
diff --git a/sdk/core/azure-core-http-okhttp/pom.xml b/sdk/core/azure-core-http-okhttp/pom.xml
index 7ce74e03bff27..9c20a28e20b14 100644
--- a/sdk/core/azure-core-http-okhttp/pom.xml
+++ b/sdk/core/azure-core-http-okhttp/pom.xml
@@ -108,7 +108,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/core/azure-core-http-okhttp/src/test/java/com/azure/core/http/okhttp/OkHttpAsyncHttpClientBuilderTests.java b/sdk/core/azure-core-http-okhttp/src/test/java/com/azure/core/http/okhttp/OkHttpAsyncHttpClientBuilderTests.java
index 066cfd98590cf..79b98573d01f2 100644
--- a/sdk/core/azure-core-http-okhttp/src/test/java/com/azure/core/http/okhttp/OkHttpAsyncHttpClientBuilderTests.java
+++ b/sdk/core/azure-core-http-okhttp/src/test/java/com/azure/core/http/okhttp/OkHttpAsyncHttpClientBuilderTests.java
@@ -455,6 +455,8 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
Configuration simpleEnvProxy = new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE,
new TestConfigurationSource().put(Configuration.PROPERTY_HTTP_PROXY, "http://localhost:12345")
+ .put(Configuration.PROPERTY_HTTPS_PROXY, "")
+ .put(Configuration.PROPERTY_NO_PROXY, "")
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true")).build();
arguments.add(Arguments.of(true, simpleEnvProxy, SERVER_HTTP_URI + DEFAULT_PATH));
@@ -468,6 +470,8 @@ private static Stream buildWithEnvConfigurationProxySupplier() {
Configuration envProxyWithAuthentication = new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE,
new TestConfigurationSource().put(Configuration.PROPERTY_HTTP_PROXY, "http://1:1@localhost:12345")
+ .put(Configuration.PROPERTY_HTTPS_PROXY, "")
+ .put(Configuration.PROPERTY_NO_PROXY, "")
.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true")).build();
arguments.add(Arguments.of(true, envProxyWithAuthentication, SERVER_HTTP_URI + DEFAULT_PATH));
diff --git a/sdk/core/azure-core-http-vertx/README.md b/sdk/core/azure-core-http-vertx/README.md
index 791822d6f457d..b41666349386e 100644
--- a/sdk/core/azure-core-http-vertx/README.md
+++ b/sdk/core/azure-core-http-vertx/README.md
@@ -10,6 +10,10 @@ Azure Core Vert.x HTTP client is a plugin for the `azure-core` HTTP client API.
- Here are details about [Java 8 client compatibility with Azure Certificate Authority][java8_client_compatibility].
### Include the package
+
+This transport remains on Vert.x 4.5 and Netty 4.1 to support Java 8. Do not combine it with the Netty 4.2-based
+`azure-core-http-netty` library on the same classpath.
+
#### Include the BOM file
Please include the azure-sdk-bom to your project to take dependency on the General Availability (GA) version of the library. In the following snippet, replace the {bom_version_to_target} placeholder with the version number.
@@ -137,4 +141,3 @@ For details on contributing to this repository, see the [contributing guide](htt
[jdk_link]: https://learn.microsoft.com/java/azure/jdk/?view=azure-java-stable
[java8_client_compatibility]: https://learn.microsoft.com/azure/security/fundamentals/azure-ca-details?tabs=root-and-subordinate-cas-list#client-compatibility-for-public-pkis
-
diff --git a/sdk/core/azure-core-http-vertx/pom.xml b/sdk/core/azure-core-http-vertx/pom.xml
index ff9ad81b5b828..8e0d2b3bfdc16 100644
--- a/sdk/core/azure-core-http-vertx/pom.xml
+++ b/sdk/core/azure-core-http-vertx/pom.xml
@@ -104,7 +104,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/core/azure-core-http-vertx/src/test/java/com/azure/core/http/vertx/VertxHttpClientBuilderTests.java b/sdk/core/azure-core-http-vertx/src/test/java/com/azure/core/http/vertx/VertxHttpClientBuilderTests.java
index ecc6f3ce4c0cb..5505ad215efc1 100644
--- a/sdk/core/azure-core-http-vertx/src/test/java/com/azure/core/http/vertx/VertxHttpClientBuilderTests.java
+++ b/sdk/core/azure-core-http-vertx/src/test/java/com/azure/core/http/vertx/VertxHttpClientBuilderTests.java
@@ -131,11 +131,10 @@ public void buildWithHttpProxy() {
@Test
public void buildWithHttpProxyFromEnvConfiguration() {
- Configuration configuration = new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE,
- new TestConfigurationSource()
- .put(Configuration.PROPERTY_HTTP_PROXY,
- "http://" + PROXY_USER_INFO + "localhost:" + PROXY_SERVER_HTTP_PORT)
- .put("java.net.useSystemProxies", "true")).build();
+ Configuration configuration = new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE, new TestConfigurationSource()
+ .put(Configuration.PROPERTY_HTTP_PROXY, "http://" + PROXY_USER_INFO + "localhost:" + PROXY_SERVER_HTTP_PORT)
+ .put(Configuration.PROPERTY_HTTPS_PROXY, "")
+ .put("java.net.useSystemProxies", "true")).build();
HttpClient httpClient = new VertxHttpClientBuilder().configuration(configuration).build();
diff --git a/sdk/core/azure-core-management/pom.xml b/sdk/core/azure-core-management/pom.xml
index 33db44dfc2e0e..545c0e33af1e6 100644
--- a/sdk/core/azure-core-management/pom.xml
+++ b/sdk/core/azure-core-management/pom.xml
@@ -103,7 +103,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/core/azure-core-perf/pom.xml b/sdk/core/azure-core-perf/pom.xml
index 89055c6cf7044..69db6ff1dd694 100644
--- a/sdk/core/azure-core-perf/pom.xml
+++ b/sdk/core/azure-core-perf/pom.xml
@@ -63,7 +63,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/core/azure-core-serializer-avro-apache/pom.xml b/sdk/core/azure-core-serializer-avro-apache/pom.xml
index 155ecd39c844b..523385f0938f1 100644
--- a/sdk/core/azure-core-serializer-avro-apache/pom.xml
+++ b/sdk/core/azure-core-serializer-avro-apache/pom.xml
@@ -102,7 +102,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/core/azure-core-serializer-json-gson/pom.xml b/sdk/core/azure-core-serializer-json-gson/pom.xml
index c231056934ef4..7eee2e35024bb 100644
--- a/sdk/core/azure-core-serializer-json-gson/pom.xml
+++ b/sdk/core/azure-core-serializer-json-gson/pom.xml
@@ -118,7 +118,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/core/azure-core-serializer-json-jackson/pom.xml b/sdk/core/azure-core-serializer-json-jackson/pom.xml
index 13449a593c00b..8bd0b67033105 100644
--- a/sdk/core/azure-core-serializer-json-jackson/pom.xml
+++ b/sdk/core/azure-core-serializer-json-jackson/pom.xml
@@ -148,7 +148,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/core/azure-core-test/CHANGELOG.md b/sdk/core/azure-core-test/CHANGELOG.md
index 22e0eb716ca7d..55fab4183b2ef 100644
--- a/sdk/core/azure-core-test/CHANGELOG.md
+++ b/sdk/core/azure-core-test/CHANGELOG.md
@@ -10,6 +10,10 @@
### Other Changes
+#### Dependency Updates
+
+- Upgraded Reactor to `3.8.7` and the shared Netty transport to Netty `4.2.17.Final` / Reactor Netty `1.3.7`.
+
## 1.27.0-beta.19 (2026-09-29)
### Other Changes
diff --git a/sdk/core/azure-core-test/README.md b/sdk/core/azure-core-test/README.md
index 34b976af00036..ec70fbbe47911 100644
--- a/sdk/core/azure-core-test/README.md
+++ b/sdk/core/azure-core-test/README.md
@@ -353,4 +353,3 @@ This project has adopted the [Microsoft Open Source Code of Conduct][coc]. For m
[coc]: https://opensource.microsoft.com/codeofconduct/
[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/
-
diff --git a/sdk/core/azure-core-test/pom.xml b/sdk/core/azure-core-test/pom.xml
index 5448fb6a82c03..af2ef000ae484 100644
--- a/sdk/core/azure-core-test/pom.xml
+++ b/sdk/core/azure-core-test/pom.xml
@@ -99,7 +99,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
@@ -142,7 +142,7 @@
org.junit.jupiter:junit-jupiter-api:[5.13.4]
org.junit.jupiter:junit-jupiter-engine:[5.13.4]
org.junit.jupiter:junit-jupiter-params:[5.13.4]
- io.projectreactor:reactor-test:[3.7.19]
+ io.projectreactor:reactor-test:[3.8.7]
org.apache.ant:ant:[1.10.18]
diff --git a/sdk/core/azure-core-tracing-opentelemetry/pom.xml b/sdk/core/azure-core-tracing-opentelemetry/pom.xml
index d0da878d38261..f47d65bdda496 100644
--- a/sdk/core/azure-core-tracing-opentelemetry/pom.xml
+++ b/sdk/core/azure-core-tracing-opentelemetry/pom.xml
@@ -100,7 +100,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/core/azure-core-version-tests/pom.xml b/sdk/core/azure-core-version-tests/pom.xml
index 4e61ea014b666..424e3335598d0 100644
--- a/sdk/core/azure-core-version-tests/pom.xml
+++ b/sdk/core/azure-core-version-tests/pom.xml
@@ -95,7 +95,7 @@
io.projectreactor
reactor-core
- 3.7.19
+ 3.8.7
+ io.projectreactor:reactor-core:[3.8.7]
com.fasterxml.jackson.core:jackson-annotations:[2.18.11]
com.fasterxml.jackson.core:jackson-core:[2.18.11]
com.fasterxml.jackson.core:jackson-databind:[2.18.11]
diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md
index db524afd49068..1def8c71d7090 100644
--- a/sdk/core/azure-core/CHANGELOG.md
+++ b/sdk/core/azure-core/CHANGELOG.md
@@ -10,6 +10,10 @@
### Other Changes
+#### Dependency Updates
+
+- Upgraded Reactor from `3.7.19` to `3.8.7`.
+
## 1.60.0 (2026-09-29)
### Features Added
@@ -27,6 +31,8 @@
### Other Changes
+#### Dependency Updates
+
- Upgraded Jackson from `2.18.9` to `2.18.11`.
## 1.59.1 (2026-08-27)
diff --git a/sdk/core/azure-core/pom.xml b/sdk/core/azure-core/pom.xml
index 4b61b3ed11d9f..8f6487e84de51 100644
--- a/sdk/core/azure-core/pom.xml
+++ b/sdk/core/azure-core/pom.xml
@@ -146,7 +146,7 @@
io.projectreactor
reactor-core
- 3.7.19
+ 3.8.7
@@ -166,7 +166,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
@@ -223,7 +223,7 @@
- io.projectreactor:reactor-core:[3.7.19]
+ io.projectreactor:reactor-core:[3.8.7]
com.fasterxml.jackson.core:jackson-annotations:[2.18.11]
com.fasterxml.jackson.core:jackson-core:[2.18.11]
com.fasterxml.jackson.core:jackson-databind:[2.18.11]
diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/http/ProxyOptionsTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/http/ProxyOptionsTests.java
index 2e0a5e6a6d096..cc5813a589d45 100644
--- a/sdk/core/azure-core/src/test/java/com/azure/core/http/ProxyOptionsTests.java
+++ b/sdk/core/azure-core/src/test/java/com/azure/core/http/ProxyOptionsTests.java
@@ -238,53 +238,35 @@ public void invalidHttpPortEnvironmentConfigDefault(String port) {
private static Stream loadFromEnvironmentSupplier() {
return Stream.of(
// Basic Azure HTTPS proxy.
- Arguments.of(
- setJavaSystemProxyPrerequisiteToTrue(
- new TestConfigurationSource().put(Configuration.PROPERTY_HTTPS_PROXY, AZURE_HTTPS_PROXY_HOST_ONLY)),
- PROXY_HOST, 443, null, null, null),
+ Arguments.of(setJavaSystemProxyPrerequisiteToTrue(AZURE_HTTPS_PROXY_HOST_ONLY, null, true), PROXY_HOST, 443,
+ null, null, null),
// Username only Azure HTTPS proxy.
- Arguments.of(
- setJavaSystemProxyPrerequisiteToTrue(new TestConfigurationSource()
- .put(Configuration.PROPERTY_HTTPS_PROXY, AZURE_HTTPS_PROXY_WITH_USERNAME)),
- PROXY_HOST, 443, null, null, null),
+ Arguments.of(setJavaSystemProxyPrerequisiteToTrue(AZURE_HTTPS_PROXY_WITH_USERNAME, null, true), PROXY_HOST,
+ 443, null, null, null),
// Complete Azure HTTPS proxy.
- Arguments.of(
- setJavaSystemProxyPrerequisiteToTrue(new TestConfigurationSource()
- .put(Configuration.PROPERTY_HTTPS_PROXY, AZURE_HTTPS_PROXY_WITH_USER_AND_PASS)),
+ Arguments.of(setJavaSystemProxyPrerequisiteToTrue(AZURE_HTTPS_PROXY_WITH_USER_AND_PASS, null, true),
PROXY_HOST, 443, FAKE_PROXY_USER_PLACEHOLDER, FAKE_PROXY_PASSWORD_PLACEHOLDER, null),
// Azure HTTPS proxy with non-proxying hosts.
- Arguments.of(
- setJavaSystemProxyPrerequisiteToTrue(
- new TestConfigurationSource().put(Configuration.PROPERTY_HTTPS_PROXY, AZURE_HTTPS_PROXY_HOST_ONLY)
- .put(Configuration.PROPERTY_NO_PROXY, NON_PROXY_HOSTS)),
+ Arguments.of(setJavaSystemProxyPrerequisiteToTrue(AZURE_HTTPS_PROXY_HOST_ONLY, NON_PROXY_HOSTS, true),
PROXY_HOST, 443, null, null, "(" + NON_PROXY_HOSTS + ")"),
// Basic Azure HTTP proxy.
- Arguments.of(
- setJavaSystemProxyPrerequisiteToTrue(
- new TestConfigurationSource().put(Configuration.PROPERTY_HTTP_PROXY, AZURE_HTTP_PROXY_HOST_ONLY)),
- PROXY_HOST, 80, null, null, null),
+ Arguments.of(setJavaSystemProxyPrerequisiteToTrue(AZURE_HTTP_PROXY_HOST_ONLY, null, false), PROXY_HOST, 80,
+ null, null, null),
// Username only Azure HTTP proxy.
- Arguments.of(
- setJavaSystemProxyPrerequisiteToTrue(new TestConfigurationSource()
- .put(Configuration.PROPERTY_HTTP_PROXY, AZURE_HTTP_PROXY_WITH_USERNAME)),
- PROXY_HOST, 80, null, null, null),
+ Arguments.of(setJavaSystemProxyPrerequisiteToTrue(AZURE_HTTP_PROXY_WITH_USERNAME, null, false), PROXY_HOST,
+ 80, null, null, null),
// Complete Azure HTTP proxy.
- Arguments.of(
- setJavaSystemProxyPrerequisiteToTrue(new TestConfigurationSource()
- .put(Configuration.PROPERTY_HTTP_PROXY, AZURE_HTTP_PROXY_WITH_USER_AND_PASS)),
+ Arguments.of(setJavaSystemProxyPrerequisiteToTrue(AZURE_HTTP_PROXY_WITH_USER_AND_PASS, null, false),
PROXY_HOST, 80, FAKE_PROXY_USER_PLACEHOLDER, FAKE_PROXY_PASSWORD_PLACEHOLDER, null),
// Azure HTTP proxy with non-proxying hosts.
- Arguments.of(
- setJavaSystemProxyPrerequisiteToTrue(
- new TestConfigurationSource().put(Configuration.PROPERTY_HTTP_PROXY, AZURE_HTTP_PROXY_HOST_ONLY)
- .put(Configuration.PROPERTY_NO_PROXY, NON_PROXY_HOSTS)),
+ Arguments.of(setJavaSystemProxyPrerequisiteToTrue(AZURE_HTTP_PROXY_HOST_ONLY, NON_PROXY_HOSTS, false),
PROXY_HOST, 80, null, null, "(" + NON_PROXY_HOSTS + ")"),
/*
@@ -328,7 +310,7 @@ private static Stream loadFromEnvironmentSupplier() {
private static Stream loadFromExplicitConfigurationSupplier() {
return Stream.of(
/*
- * Setting up tests for loading the Java exokicit proxy configurations takes additional work as each
+ * Setting up tests for loading the Java explicit proxy configurations takes additional work as each
* piece of the proxy configuration is a separate environment value. The non-proxy hosts will be checked
* against the global environment value when it is not being set by the configuration passed by the test
* as this value may be setup by the JVM.
@@ -447,10 +429,8 @@ private static Stream nonProxyHostsSupplier() {
/*
* This emulates loading an environment formatted proxy.
*/
- TestConfigurationSource envSource
- = new TestConfigurationSource().put(Configuration.PROPERTY_HTTPS_PROXY, "http://localhost:7777")
- .put(Configuration.PROPERTY_NO_PROXY, noProxyNonProxyHosts);
- Configuration environmentProxyConfiguration = setJavaSystemProxyPrerequisiteToTrue(envSource);
+ Configuration environmentProxyConfiguration
+ = setJavaSystemProxyPrerequisiteToTrue("http://localhost:7777", noProxyNonProxyHosts, true);
Pattern environmentProxyConfigurationPattern
= compile(fromConfiguration(environmentProxyConfiguration).getNonProxyHosts(), Pattern.CASE_INSENSITIVE);
@@ -510,8 +490,22 @@ public void sanitizeJavaHttpNonProxyHostsDoesNotSplitEscapedPipes() {
assertEquals("(" + nonProxyHosts + ")", ProxyOptions.sanitizeNoProxy(nonProxyHosts));
}
- private static Configuration setJavaSystemProxyPrerequisiteToTrue(TestConfigurationSource source) {
- return new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE, source.put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true"))
- .build();
+ private static Configuration setJavaSystemProxyPrerequisiteToTrue(String proxy, String nonProxyHosts,
+ boolean isHttps) {
+ TestConfigurationSource testSource = new TestConfigurationSource().put(JAVA_SYSTEM_PROXY_PREREQUISITE, "true")
+ // Use empty string when non-proxy hosts is null or empty to ensure that the environment configuration
+ // doesn't attempt to fall back to loading from System.properties or System.env.
+ .put(Configuration.PROPERTY_NO_PROXY, CoreUtils.isNullOrEmpty(nonProxyHosts) ? "" : nonProxyHosts);
+
+ if (isHttps) {
+ testSource.put(Configuration.PROPERTY_HTTPS_PROXY, proxy);
+ } else {
+ testSource.put(Configuration.PROPERTY_HTTP_PROXY, proxy)
+ // Same as with non-proxy hosts, set HTTPS_PROXY to empty when testing HTTP_PROXY as HTTPS is checked
+ // first and could fall back to loading from System.properties or System.env.
+ .put(Configuration.PROPERTY_HTTPS_PROXY, "");
+ }
+
+ return new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE, testSource).build();
}
}
diff --git a/sdk/cosmos/azure-cosmos-encryption/pom.xml b/sdk/cosmos/azure-cosmos-encryption/pom.xml
index bf5b3d0178031..e0c497f053672 100644
--- a/sdk/cosmos/azure-cosmos-encryption/pom.xml
+++ b/sdk/cosmos/azure-cosmos-encryption/pom.xml
@@ -51,6 +51,9 @@ Licensed under the MIT License.
--add-opens com.azure.cosmos/com.azure.cosmos.implementation=ALL-UNNAMED
--add-modules java.management
--add-reads com.azure.cosmos.encryption=java.management
+ --add-reads com.azure.cosmos.encryption=io.netty.common
+ --add-reads com.azure.cosmos.encryption=io.netty.buffer
+ --add-reads com.azure.cosmos.encryption=io.netty.codec.http
-
true
@@ -178,7 +181,7 @@ Licensed under the MIT License.
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/cosmos/azure-cosmos-spark-account-data-resolver-sample/pom.xml b/sdk/cosmos/azure-cosmos-spark-account-data-resolver-sample/pom.xml
index 93239be5fdb5b..8662d66cd84c7 100644
--- a/sdk/cosmos/azure-cosmos-spark-account-data-resolver-sample/pom.xml
+++ b/sdk/cosmos/azure-cosmos-spark-account-data-resolver-sample/pom.xml
@@ -159,7 +159,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/cosmos/azure-cosmos-spark_3-4_2-12/pom.xml b/sdk/cosmos/azure-cosmos-spark_3-4_2-12/pom.xml
index 7e0bf98d65c7c..bfe17f9cfdfdc 100644
--- a/sdk/cosmos/azure-cosmos-spark_3-4_2-12/pom.xml
+++ b/sdk/cosmos/azure-cosmos-spark_3-4_2-12/pom.xml
@@ -183,6 +183,14 @@
io.netty
netty-all
+
+ io.netty
+ netty-transport-native-epoll
+
+
+ io.netty
+ netty-transport-native-kqueue
+
org.slf4j
*
@@ -199,6 +207,14 @@
io.netty
netty-all
+
+ io.netty
+ netty-transport-native-epoll
+
+
+ io.netty
+ netty-transport-native-kqueue
+
org.slf4j
*
diff --git a/sdk/cosmos/azure-cosmos-spark_3-5/pom.xml b/sdk/cosmos/azure-cosmos-spark_3-5/pom.xml
index e9918828cbec9..be5c1955b729d 100644
--- a/sdk/cosmos/azure-cosmos-spark_3-5/pom.xml
+++ b/sdk/cosmos/azure-cosmos-spark_3-5/pom.xml
@@ -104,6 +104,14 @@
io.netty
netty-all
+
+ io.netty
+ netty-transport-native-epoll
+
+
+ io.netty
+ netty-transport-native-kqueue
+
org.slf4j
*
@@ -120,6 +128,14 @@
io.netty
netty-all
+
+ io.netty
+ netty-transport-native-epoll
+
+
+ io.netty
+ netty-transport-native-kqueue
+
org.slf4j
*
diff --git a/sdk/cosmos/azure-cosmos-spark_3/pom.xml b/sdk/cosmos/azure-cosmos-spark_3/pom.xml
index fdc6b8258a842..7f50977a332ae 100644
--- a/sdk/cosmos/azure-cosmos-spark_3/pom.xml
+++ b/sdk/cosmos/azure-cosmos-spark_3/pom.xml
@@ -89,6 +89,11 @@
scala-java8-compat_${scala.binary.version}
${scala-java8-compat.version}
+
+ io.netty
+ netty-transport-classes-epoll
+ 4.2.19.Final
+
io.projectreactor
reactor-scala-extensions_${scala.binary.version}
@@ -128,7 +133,7 @@
com.azure
azure-core-http-netty
- 1.16.8
+ 1.17.0-beta.1
com.azure
@@ -198,7 +203,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
@@ -317,6 +322,7 @@
org.apache.commons:commons-lang3:[3.18.0]
+ io.netty:netty-transport-classes-epoll:[4.2.19.Final]
org.slf4j:slf4j-api:[1.7.36]
org.apache.spark:spark-sql_2.12:[${spark33.version}]
org.apache.spark:spark-sql_2.12:[${spark34.version}]
diff --git a/sdk/cosmos/azure-cosmos-spark_3/src/test/scala/com/azure/cosmos/spark/NettyCompatibilitySpec.scala b/sdk/cosmos/azure-cosmos-spark_3/src/test/scala/com/azure/cosmos/spark/NettyCompatibilitySpec.scala
new file mode 100644
index 0000000000000..0857b4c3c4027
--- /dev/null
+++ b/sdk/cosmos/azure-cosmos-spark_3/src/test/scala/com/azure/cosmos/spark/NettyCompatibilitySpec.scala
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.cosmos.spark
+
+import com.azure.cosmos.implementation.directconnectivity.rntbd.RntbdLoopNIO
+import io.netty.channel.MultiThreadIoEventLoopGroup
+import io.netty.util.Version
+import io.netty.util.concurrent.DefaultThreadFactory
+
+import java.util.concurrent.{Callable, TimeUnit}
+import scala.collection.JavaConverters._
+
+class NettyCompatibilitySpec extends UnitSpec {
+ private val timeoutInSeconds = 10
+
+ it should "resolve a consistent Netty 4.2 dependency stack" in {
+ val versions = Version.identify().values().asScala
+ .filterNot(_.artifactId().startsWith("netty-tcnative"))
+ .map(_.artifactVersion()).toSeq
+ versions should not be empty
+ all(versions) should startWith("4.2.")
+ }
+
+ it should "create NIO event loops for the Cosmos transport" in {
+ val threadName = "cosmos-spark-netty-test"
+ val eventLoops = new RntbdLoopNIO().newEventLoopGroup(1, new DefaultThreadFactory(threadName))
+ try {
+ eventLoops shouldBe a[MultiThreadIoEventLoopGroup]
+ eventLoops.next().submit(new Callable[String] {
+ override def call(): String = Thread.currentThread().getName
+ }).get(timeoutInSeconds, TimeUnit.SECONDS) should startWith(threadName)
+ } finally {
+ eventLoops.shutdownGracefully(0, timeoutInSeconds, TimeUnit.SECONDS).sync()
+ }
+ }
+}
diff --git a/sdk/cosmos/azure-cosmos-spark_4-0_2-13/pom.xml b/sdk/cosmos/azure-cosmos-spark_4-0_2-13/pom.xml
index dac2a640c5531..b2673044ade6f 100644
--- a/sdk/cosmos/azure-cosmos-spark_4-0_2-13/pom.xml
+++ b/sdk/cosmos/azure-cosmos-spark_4-0_2-13/pom.xml
@@ -188,10 +188,86 @@
spark-sql_2.13
4.0.0
+
+ io.netty
+ netty-tcnative-boringssl-static
+
+
+ io.netty
+ netty-tcnative-classes
+
io.netty
netty-all
+
+ io.netty
+ netty-buffer
+
+
+ io.netty
+ netty-codec
+
+
+ io.netty
+ netty-codec-dns
+
+
+ io.netty
+ netty-codec-http
+
+
+ io.netty
+ netty-codec-http2
+
+
+ io.netty
+ netty-codec-socks
+
+
+ io.netty
+ netty-common
+
+
+ io.netty
+ netty-handler
+
+
+ io.netty
+ netty-transport-native-unix-common
+
+
+ io.netty
+ netty-handler-proxy
+
+
+ io.netty
+ netty-resolver
+
+
+ io.netty
+ netty-resolver-dns
+
+
+ io.netty
+ netty-transport
+
+
+ io.netty
+ netty-transport-classes-epoll
+
+
+ io.netty
+ netty-transport-classes-kqueue
+
+
+ io.netty
+ netty-transport-native-epoll
+
+
+ io.netty
+ netty-transport-native-kqueue
+
org.slf4j
*
diff --git a/sdk/cosmos/azure-cosmos-spark_4-1_2-13/pom.xml b/sdk/cosmos/azure-cosmos-spark_4-1_2-13/pom.xml
index 1fd6422db2c97..dab39608248dd 100644
--- a/sdk/cosmos/azure-cosmos-spark_4-1_2-13/pom.xml
+++ b/sdk/cosmos/azure-cosmos-spark_4-1_2-13/pom.xml
@@ -235,10 +235,114 @@
spark-sql_2.13
4.1.0
+
+ io.netty
+ netty-tcnative-boringssl-static
+
+
+ io.netty
+ netty-tcnative-classes
+
io.netty
netty-all
+
+ io.netty
+ netty-buffer
+
+
+ io.netty
+ netty-codec-base
+
+
+ io.netty
+ netty-codec
+
+
+ io.netty
+ netty-codec-dns
+
+
+ io.netty
+ netty-codec-compression
+
+
+ io.netty
+ netty-codec-http
+
+
+ io.netty
+ netty-codec-http2
+
+
+ io.netty
+ netty-codec-http3
+
+
+ io.netty
+ netty-codec-socks
+
+
+ io.netty
+ netty-codec-protobuf
+
+
+ io.netty
+ netty-codec-marshalling
+
+
+ io.netty
+ netty-common
+
+
+ io.netty
+ netty-handler
+
+
+ io.netty
+ netty-transport-native-unix-common
+
+
+ io.netty
+ netty-handler-proxy
+
+
+ io.netty
+ netty-resolver
+
+
+ io.netty
+ netty-resolver-dns
+
+
+ io.netty
+ netty-transport
+
+
+ io.netty
+ netty-transport-classes-epoll
+
+
+ io.netty
+ netty-transport-classes-kqueue
+
+
+ io.netty
+ netty-transport-classes-io_uring
+
+
+ io.netty
+ netty-transport-classes-quic
+
+
+ io.netty
+ netty-transport-native-epoll
+
+
+ io.netty
+ netty-transport-native-kqueue
+
org.slf4j
*
diff --git a/sdk/cosmos/azure-cosmos-tests/pom.xml b/sdk/cosmos/azure-cosmos-tests/pom.xml
index 1153bceb09ace..1e6b49b48bc33 100644
--- a/sdk/cosmos/azure-cosmos-tests/pom.xml
+++ b/sdk/cosmos/azure-cosmos-tests/pom.xml
@@ -181,7 +181,7 @@ Licensed under the MIT License.
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/ConfigsTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/ConfigsTests.java
index f8d0a5c0292bc..ea7ba842d37fe 100644
--- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/ConfigsTests.java
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/ConfigsTests.java
@@ -6,8 +6,14 @@
import com.azure.cosmos.implementation.clienttelemetry.MetricCategory;
import com.azure.cosmos.implementation.clienttelemetry.TagName;
import com.azure.cosmos.implementation.directconnectivity.Protocol;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.handler.ssl.SslContext;
+import io.netty.util.ReferenceCountUtil;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
+import javax.net.ssl.SSLEngine;
import java.net.URI;
import java.time.Duration;
import java.util.EnumSet;
@@ -16,6 +22,39 @@
public class ConfigsTests {
+ @DataProvider(name = "sslValidationSettings")
+ public Object[][] sslValidationSettings() {
+ return new Object[][] {
+ { false, false, false }, { false, false, true },
+ { false, true, false }, { false, true, true },
+ { true, false, false }, { true, false, true },
+ { true, true, false }, { true, true, true }
+ };
+ }
+
+ @Test(groups = { "unit" }, dataProvider = "sslValidationSettings")
+ public void sslContextPreservesHostnameValidationSettings(
+ boolean serverCertValidationDisabled, boolean hostnameValidationDisabled, boolean http2Enabled)
+ throws IllegalAccessException {
+
+ Object original = FieldUtils.readStaticField(Configs.class, "cachedIsHostnameValidationDisabled", true);
+ SslContext sslContext = null;
+ SSLEngine engine = null;
+ try {
+ FieldUtils.writeStaticField(Configs.class, "cachedIsHostnameValidationDisabled",
+ hostnameValidationDisabled, true);
+ sslContext = new Configs().getSslContext(serverCertValidationDisabled, http2Enabled);
+ engine = sslContext.newEngine(ByteBufAllocator.DEFAULT, "localhost", 443);
+
+ assertThat(engine.getSSLParameters().getEndpointIdentificationAlgorithm())
+ .isEqualTo(serverCertValidationDisabled || hostnameValidationDisabled ? null : "HTTPS");
+ } finally {
+ ReferenceCountUtil.release(engine);
+ ReferenceCountUtil.release(sslContext);
+ FieldUtils.writeStaticField(Configs.class, "cachedIsHostnameValidationDisabled", original, true);
+ }
+ }
+
@Test(groups = { "unit" })
public void maxHttpHeaderSize() {
Configs config = new Configs();
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/TcpServerMock/TcpServer.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/TcpServerMock/TcpServer.java
index 3a5ff7605c8db..84deac4399f48 100644
--- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/TcpServerMock/TcpServer.java
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/TcpServerMock/TcpServer.java
@@ -36,7 +36,8 @@
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
-import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
@@ -60,8 +61,8 @@ public class TcpServer {
public TcpServer(int port) {
this.port = port;
- this.parent = new NioEventLoopGroup();
- this.child = new NioEventLoopGroup();
+ this.parent = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
+ this.child = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
requestManager = new ServerRntbdRequestManager();
}
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdLoopTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdLoopTests.java
new file mode 100644
index 0000000000000..ecac817f63f71
--- /dev/null
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdLoopTests.java
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.cosmos.implementation.directconnectivity.rntbd;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.SingleThreadIoEventLoop;
+import io.netty.channel.epoll.Epoll;
+import io.netty.util.concurrent.DefaultThreadFactory;
+import org.testng.SkipException;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RntbdLoopTests {
+
+ @Test(groups = { "unit" })
+ public void nioChannelRegistration() throws Exception {
+ RntbdLoop loop = RntbdLoopNativeDetector.getRntbdLoop(false);
+ assertThat(loop.getName()).isEqualTo("nio");
+ verifyChannelRegistration(loop);
+ }
+
+ @Test(groups = { "unit" })
+ public void preferredTransportChannelRegistration() throws Exception {
+ RntbdLoop loop = RntbdLoopNativeDetector.getRntbdLoop(true);
+ assertThat(loop.getName()).isEqualTo(Epoll.isAvailable() ? "epoll" : "nio");
+ verifyChannelRegistration(loop);
+ }
+
+ @Test(groups = { "unit" })
+ public void epollChannelRegistration() throws Exception {
+ if (!Epoll.isAvailable()) {
+ throw new SkipException("Epoll is not available on this platform", Epoll.unavailabilityCause());
+ }
+ verifyChannelRegistration(new RntbdLoopEpoll());
+ }
+
+ private static void verifyChannelRegistration(RntbdLoop loop) throws Exception {
+ String threadName = "rntbd-loop-test-" + loop.getName();
+ EventLoopGroup group = loop.newEventLoopGroup(1, new DefaultThreadFactory(threadName));
+ Channel channel = null;
+ try {
+ assertThat(group).isInstanceOf(MultiThreadIoEventLoopGroup.class);
+ assertThat(((MultiThreadIoEventLoopGroup) group).executorCount()).isEqualTo(1);
+ channel = new Bootstrap()
+ .group(group)
+ .channel(loop.getChannelClass())
+ .handler(new ChannelInboundHandlerAdapter())
+ .register().sync().channel();
+
+ assertThat(channel.isRegistered()).isTrue();
+ assertThat(channel).isInstanceOf(loop.getChannelClass());
+ assertThat(channel.eventLoop()).isInstanceOf(SingleThreadIoEventLoop.class);
+ assertThat(RntbdUtils.tryGetExecutorTaskQueueSize(channel.eventLoop())).isGreaterThanOrEqualTo(0);
+ assertThat(channel.eventLoop().submit(() -> Thread.currentThread().getName()).get(10, TimeUnit.SECONDS))
+ .startsWith(threadName);
+ assertThat(channel.eventLoop().schedule(() -> true, 1, TimeUnit.MILLISECONDS).get(10, TimeUnit.SECONDS))
+ .isTrue();
+ } finally {
+ if (channel != null) {
+ channel.close().sync();
+ }
+ group.shutdownGracefully(0, 5, TimeUnit.SECONDS).sync();
+ }
+ }
+}
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/http/ReactorNettyHttpClientTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/http/ReactorNettyHttpClientTest.java
index 88b85abbdf683..b867c9fed47c8 100644
--- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/http/ReactorNettyHttpClientTest.java
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/http/ReactorNettyHttpClientTest.java
@@ -9,6 +9,8 @@
import com.azure.cosmos.implementation.Configs;
import com.azure.cosmos.implementation.LifeCycleUtils;
import com.azure.cosmos.implementation.directconnectivity.ReflectionUtils;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,6 +48,26 @@ public void after_ReactorNettyHttpClientTest() throws Exception {
}
}
+ @Test(groups = "unit")
+ public void httpClientAllocatorRespectsConfiguration() {
+ ByteBufAllocator expectedAllocator = System.getProperty("io.netty.allocator.type") == null
+ ? PooledByteBufAllocator.DEFAULT : ByteBufAllocator.DEFAULT;
+ reactor.netty.http.client.HttpClient httpClient =
+ ReflectionUtils.get(reactor.netty.http.client.HttpClient.class, this.reactorNettyHttpClient, "httpClient");
+ assertThat(httpClient.configuration().options().get(ChannelOption.ALLOCATOR))
+ .isSameAs(expectedAllocator);
+
+ HttpClient unpooledConnectionClient = HttpClient.create(new HttpClientConfig(new Configs()));
+ try {
+ httpClient =
+ ReflectionUtils.get(reactor.netty.http.client.HttpClient.class, unpooledConnectionClient, "httpClient");
+ assertThat(httpClient.configuration().options().get(ChannelOption.ALLOCATOR))
+ .isSameAs(expectedAllocator);
+ } finally {
+ LifeCycleUtils.closeQuietly(unpooledConnectionClient);
+ }
+ }
+
@Test(groups = "unit")
public void httpClientWithMaxHeaderSize() {
reactor.netty.http.client.HttpClient httpClient =
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/proxy/HttpProxyServer.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/proxy/HttpProxyServer.java
index b518db313ee20..6a731f27381b8 100644
--- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/proxy/HttpProxyServer.java
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/proxy/HttpProxyServer.java
@@ -4,7 +4,8 @@
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.EventLoopGroup;
-import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,8 +21,8 @@ public class HttpProxyServer {
EventLoopGroup bossGroup;
EventLoopGroup workerGroup;
public HttpProxyServer() {
- bossGroup = new NioEventLoopGroup(1);
- workerGroup = new NioEventLoopGroup();
+ bossGroup = new MultiThreadIoEventLoopGroup(1, NioIoHandler.newFactory());
+ workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
}
public void start() {
diff --git a/sdk/cosmos/azure-cosmos/CHANGELOG.md b/sdk/cosmos/azure-cosmos/CHANGELOG.md
index 9bbaf9c7c67c3..2cab777b4aeef 100644
--- a/sdk/cosmos/azure-cosmos/CHANGELOG.md
+++ b/sdk/cosmos/azure-cosmos/CHANGELOG.md
@@ -10,8 +10,9 @@
* Fixed the Direct mode (RNTBD) encoding of the `x-ms-cosmos-workload-id` header, which was sent with the wrong token ID (`0x00DC`) and type (`Byte`) instead of `0x00E7` and `UShort`. - See [PR 50673](https://github.com/Azure/azure-sdk-for-java/pull/50673)
#### Other Changes
+
+* Updated Netty to `4.2.19.Final`, Reactor Netty to `1.3.7`, and Reactor Core to `3.8.7`. Migrated direct TCP event loops to the Netty 4.2 I/O handler API while preserving pooled buffers and explicit TLS validation settings.
* Added a compact `ppaf` bookmark to each data-plane attempt in `CosmosDiagnostics`, containing the current per-partition write region, failed regions, and the time it was designated, or an empty object when no override is active.
-* Upgraded Jackson from `2.18.9` to `2.18.11`.
### 4.83.0 (2026-09-22)
diff --git a/sdk/cosmos/azure-cosmos/README.md b/sdk/cosmos/azure-cosmos/README.md
index a8bb7e5868bd3..c11a4037373ac 100644
--- a/sdk/cosmos/azure-cosmos/README.md
+++ b/sdk/cosmos/azure-cosmos/README.md
@@ -95,6 +95,24 @@ A Cosmos DB account contains zero or more databases, a database (DB) contains ze
You may read more about databases, containers and items [here](https://learn.microsoft.com/azure/cosmos-db/databases-containers-items).
A few important properties defined at the level of the container, among them are provisioned throughput and partition key.
+### Direct-mode epoll on the Java module path
+
+When a named-module application uses Cosmos DB direct mode on JDK 11 or later, the
+`com.azure.cosmos` module already requires Netty's `io.netty.transport.classes.epoll` module.
+Netty 4.2 places the Linux JNI library in a **separate** explicit module, so including its JAR
+on the module path is not enough to make the bundled library visible. On Linux x86_64,
+retain or add the matching `io.netty:netty-transport-native-epoll` dependency with the
+`linux-x86_64` classifier and add `--add-modules=io.netty.transport.epoll.linux.x86_64`
+when launching the application. For another architecture, use its matching classifier
+and module name (check with `jar --describe-module --file --release 11`).
+
+If the native module is not resolved, the bundled JNI library is unavailable to Netty and
+Cosmos DB direct mode falls back to JDK NIO unless a native library is supplied another way.
+These JPMS launch options do not apply to Java 8 or to classpath applications. Resolving
+the module does not by itself prove epoll is usable on the current OS. The
+[Netty HTTP plugin's module-path guidance](https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/core/azure-core-http-netty/README.md#native-transports-on-the-module-path)
+also covers gateway HTTP and macOS kqueue.
+
### Global Distribution
- Azure Cosmos DB is a globally distributed database service that's designed to provide low latency, elastic scalability of throughput, well-defined semantics for data consistency, and high availability.
In short, if your application needs guaranteed fast response time anywhere in the world, if it's required to be always online, and needs unlimited and elastic scalability of throughput and storage, you should build your application on Azure Cosmos DB.
diff --git a/sdk/cosmos/azure-cosmos/pom.xml b/sdk/cosmos/azure-cosmos/pom.xml
index bd757939a79d4..983b97ce1e146 100644
--- a/sdk/cosmos/azure-cosmos/pom.xml
+++ b/sdk/cosmos/azure-cosmos/pom.xml
@@ -67,18 +67,12 @@ Licensed under the MIT License.
com.azure
azure-core
- 1.60.0
+ 1.61.0-beta.1
com.azure
azure-core-http-netty
- 1.16.8
-
-
- com.azure
- azure-core
-
-
+ 1.17.0-beta.1
com.fasterxml.jackson.module
diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Configs.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Configs.java
index bdb83171ee6e1..b152a8f6e3b02 100644
--- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Configs.java
+++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Configs.java
@@ -500,9 +500,10 @@ private SslContext sslContextInit(boolean serverCertVerificationDisabled, boolea
if (serverCertVerificationDisabled) {
sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE); // disable cert verification
- } else if (!isHostnameValidationDisabled()) {
- sslContextBuilder.endpointIdentificationAlgorithm("HTTPS");
}
+ // Netty 4.2 enables hostname validation by default; preserve explicit Cosmos opt-outs.
+ sslContextBuilder.endpointIdentificationAlgorithm(
+ serverCertVerificationDisabled || isHostnameValidationDisabled() ? null : "HTTPS");
if (http2Enabled) {
sslContextBuilder
diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdLoopEpoll.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdLoopEpoll.java
index 78f2e2e19bc17..711cfa9471c60 100644
--- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdLoopEpoll.java
+++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdLoopEpoll.java
@@ -4,7 +4,8 @@
package com.azure.cosmos.implementation.directconnectivity.rntbd;
import io.netty.channel.EventLoopGroup;
-import io.netty.channel.epoll.EpollEventLoopGroup;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.epoll.EpollIoHandler;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.socket.SocketChannel;
@@ -19,7 +20,7 @@ public String getName() {
@Override
public EventLoopGroup newEventLoopGroup(int threads, ThreadFactory threadFactory) {
- return new EpollEventLoopGroup(threads, threadFactory);
+ return new MultiThreadIoEventLoopGroup(threads, threadFactory, EpollIoHandler.newFactory());
}
@Override
diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdLoopNIO.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdLoopNIO.java
index 197231a06ea1d..72c24016c44e1 100644
--- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdLoopNIO.java
+++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdLoopNIO.java
@@ -4,7 +4,8 @@
package com.azure.cosmos.implementation.directconnectivity.rntbd;
import io.netty.channel.EventLoopGroup;
-import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
@@ -19,7 +20,7 @@ public String getName() {
@Override
public EventLoopGroup newEventLoopGroup(int threads, ThreadFactory threadFactory) {
- return new NioEventLoopGroup(threads, threadFactory);
+ return new MultiThreadIoEventLoopGroup(threads, threadFactory, NioIoHandler.newFactory());
}
@Override
diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdServiceEndpoint.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdServiceEndpoint.java
index 04db3536834c1..da83532855293 100644
--- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdServiceEndpoint.java
+++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdServiceEndpoint.java
@@ -202,7 +202,7 @@ private Bootstrap getBootStrap(EventLoopGroup eventLoopGroup, Config config) {
.option(ChannelOption.ALLOCATOR, config.allocator())
.option(ChannelOption.AUTO_READ, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.connectTimeoutInMillis())
- .option(ChannelOption.RCVBUF_ALLOCATOR, receiveBufferAllocator)
+ .option(ChannelOption.RECVBUF_ALLOCATOR, receiveBufferAllocator)
.option(ChannelOption.SO_KEEPALIVE, true)
.remoteAddress(this.serverKey.getHost(), this.serverKey.getPort());
diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/guava25/hash/Striped64.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/guava25/hash/Striped64.java
index df3dff010fb87..972b20ca5a37a 100644
--- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/guava25/hash/Striped64.java
+++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/guava25/hash/Striped64.java
@@ -24,8 +24,6 @@
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
-import static io.netty.util.internal.shaded.org.jctools.util.UnsafeAccess.UNSAFE;
-
/**
* A package-local class holding common representation and mechanics for classes supporting dynamic
* striping on 64bit values. The class extends Number so that concrete subclasses must publicly do
diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/ReactorNettyClient.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/ReactorNettyClient.java
index f62082edaacc8..674af8a174a92 100644
--- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/ReactorNettyClient.java
+++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/ReactorNettyClient.java
@@ -6,6 +6,8 @@
import com.azure.cosmos.implementation.Configs;
import com.azure.cosmos.implementation.ImplementationBridgeHelpers;
import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelOption;
@@ -16,6 +18,7 @@
import io.netty.resolver.DefaultAddressResolverGroup;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ResourceLeakDetector;
+import io.netty.util.internal.SystemPropertyUtil;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
@@ -51,6 +54,11 @@ private static ImplementationBridgeHelpers.Http2ConnectionConfigHelper.Http2Conn
private static final boolean leakDetectionDebuggingEnabled = ResourceLeakDetector.getLevel().ordinal() >=
ResourceLeakDetector.Level.ADVANCED.ordinal();
private static final String REACTOR_NETTY_REQUEST_RECORD_KEY = "reactorNettyRequestRecordKey";
+ // If io.netty.allocator.type was configured, respect the configuration. Otherwise, fallback to the default setting
+ // from Netty 4.1 which was the pooled allocator. In the future, this should be validated for if this should use the
+ // new Netty 4.2 default which is an adaptive allocator.
+ private static final ByteBufAllocator DEFAULT_ALLOCATOR = SystemPropertyUtil.contains("io.netty.allocator.type")
+ ? ByteBufAllocator.DEFAULT : PooledByteBufAllocator.DEFAULT;
private static final Logger logger = LoggerFactory.getLogger(ReactorNettyClient.class.getSimpleName());
@@ -134,6 +142,7 @@ private void configureChannelPipelineHandlers() {
configs.getSslContext(
httpClientConfig.isServerCertValidationDisabled(),
false)))
+ .option(ChannelOption.ALLOCATOR, DEFAULT_ALLOCATOR)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) this.httpClientConfig.getConnectionAcquireTimeout().toMillis())
.httpResponseDecoder(httpResponseDecoderSpec ->
httpResponseDecoderSpec.maxInitialLineLength(this.httpClientConfig.getMaxInitialLineLength())
diff --git a/sdk/cosmos/azure-cosmos/src/main/java/module-info.java b/sdk/cosmos/azure-cosmos/src/main/java/module-info.java
index f3faeda09014b..1b1ac64ec04f3 100644
--- a/sdk/cosmos/azure-cosmos/src/main/java/module-info.java
+++ b/sdk/cosmos/azure-cosmos/src/main/java/module-info.java
@@ -12,6 +12,7 @@
requires io.netty.common;
requires io.netty.buffer;
requires io.netty.codec;
+ requires io.netty.codec.compression;
requires io.netty.resolver;
requires io.netty.codec.http;
requires io.netty.codec.http2;
diff --git a/sdk/cosmos/cspell.yml b/sdk/cosmos/cspell.yml
index 31e8b2522d0bf..a0a1da433f555 100644
--- a/sdk/cosmos/cspell.yml
+++ b/sdk/cosmos/cspell.yml
@@ -42,6 +42,7 @@ words:
- "jgss"
- "jnilib"
- "jodd"
+ - "jpms"
- "kihn"
- "ksql"
- "latencyutils"
diff --git a/sdk/documentintelligence/azure-ai-documentintelligence/src/test/java/com/azure/ai/documentintelligence/DocumentModelAdministrationAsyncClientTest.java b/sdk/documentintelligence/azure-ai-documentintelligence/src/test/java/com/azure/ai/documentintelligence/DocumentModelAdministrationAsyncClientTest.java
index 20ad773830406..c8ab94462887c 100644
--- a/sdk/documentintelligence/azure-ai-documentintelligence/src/test/java/com/azure/ai/documentintelligence/DocumentModelAdministrationAsyncClientTest.java
+++ b/sdk/documentintelligence/azure-ai-documentintelligence/src/test/java/com/azure/ai/documentintelligence/DocumentModelAdministrationAsyncClientTest.java
@@ -29,7 +29,6 @@
import com.azure.core.util.polling.LongRunningOperationStatus;
import com.azure.core.util.polling.PollerFlux;
import com.azure.core.util.polling.SyncPoller;
-import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -105,7 +104,7 @@ public void deleteModelValidModelIdWithResponse(HttpClient httpClient,
syncPoller1.waitForCompletion();
StepVerifier.create(client.deleteModelWithResponse(modelId, null))
- .assertNext(response -> assertEquals(response.getStatusCode(), HttpResponseStatus.NO_CONTENT.code()))
+ .assertNext(response -> assertEquals(204, response.getStatusCode()))
.expectComplete()
.verify(DEFAULT_TIMEOUT);
}
diff --git a/sdk/documentintelligence/azure-ai-documentintelligence/src/test/java/com/azure/ai/documentintelligence/DocumentModelAdministrationClientTest.java b/sdk/documentintelligence/azure-ai-documentintelligence/src/test/java/com/azure/ai/documentintelligence/DocumentModelAdministrationClientTest.java
index 28cd4ccdffcaf..a5b1465dcb9e0 100644
--- a/sdk/documentintelligence/azure-ai-documentintelligence/src/test/java/com/azure/ai/documentintelligence/DocumentModelAdministrationClientTest.java
+++ b/sdk/documentintelligence/azure-ai-documentintelligence/src/test/java/com/azure/ai/documentintelligence/DocumentModelAdministrationClientTest.java
@@ -25,7 +25,6 @@
import com.azure.core.util.BinaryData;
import com.azure.core.util.Context;
import com.azure.core.util.polling.SyncPoller;
-import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
@@ -77,7 +76,7 @@ public void getModelWithResponse(HttpClient httpClient, DocumentIntelligenceServ
Response documentModelResponse
= client.getModelWithResponse(documentModelDetails.getModelId(), null);
- assertEquals(documentModelResponse.getStatusCode(), HttpResponseStatus.OK.code());
+ assertEquals(200, documentModelResponse.getStatusCode());
}
/**
@@ -99,7 +98,7 @@ public void validGetResourceDetailsWithResponse(HttpClient httpClient,
DocumentIntelligenceServiceVersion serviceVersion) {
client = getModelAdministrationClient(httpClient, serviceVersion);
Response resourceDetailsResponse = client.getResourceDetailsWithResponse(null);
- assertEquals(resourceDetailsResponse.getStatusCode(), HttpResponseStatus.OK.code());
+ assertEquals(200, resourceDetailsResponse.getStatusCode());
}
@ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS)
@@ -119,7 +118,7 @@ public void deleteModelValidModelIDWithResponse(HttpClient httpClient,
final Response deleteModelWithResponse = client.deleteModelWithResponse(createdModel.getModelId(), null);
- assertEquals(deleteModelWithResponse.getStatusCode(), HttpResponseStatus.NO_CONTENT.code());
+ assertEquals(204, deleteModelWithResponse.getStatusCode());
}
/**
diff --git a/sdk/eventhubs/azure-messaging-eventhubs-checkpointstore-jedis/pom.xml b/sdk/eventhubs/azure-messaging-eventhubs-checkpointstore-jedis/pom.xml
index 16e5bb6af13ad..e6e4e6c3acef8 100644
--- a/sdk/eventhubs/azure-messaging-eventhubs-checkpointstore-jedis/pom.xml
+++ b/sdk/eventhubs/azure-messaging-eventhubs-checkpointstore-jedis/pom.xml
@@ -81,7 +81,7 @@
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/eventhubs/azure-messaging-eventhubs/docs/pom.xml b/sdk/eventhubs/azure-messaging-eventhubs/docs/pom.xml
index ee3fc592a1525..a858aa97c1292 100644
--- a/sdk/eventhubs/azure-messaging-eventhubs/docs/pom.xml
+++ b/sdk/eventhubs/azure-messaging-eventhubs/docs/pom.xml
@@ -25,7 +25,7 @@
io.projectreactor
reactor-core
- 3.7.19
+ 3.8.7
diff --git a/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/FormTrainingAsyncClientTest.java b/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/FormTrainingAsyncClientTest.java
index 9aef13b3fe99f..47f66c61b78f0 100644
--- a/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/FormTrainingAsyncClientTest.java
+++ b/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/FormTrainingAsyncClientTest.java
@@ -21,7 +21,6 @@
import com.azure.core.http.HttpClient;
import com.azure.core.util.polling.PollerFlux;
import com.azure.core.util.polling.SyncPoller;
-import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -35,7 +34,6 @@
import static com.azure.ai.formrecognizer.FormRecognizerClientTestBase.MODEL_ID_NOT_FOUND_ERROR_CODE;
import static com.azure.ai.formrecognizer.TestUtils.DISPLAY_NAME_WITH_ARGUMENTS;
import static com.azure.ai.formrecognizer.implementation.Utility.toFluxByteBuffer;
-import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -90,7 +88,7 @@ public void getCustomModelWithResponse(HttpClient httpClient, FormRecognizerServ
StepVerifier.create(client.getCustomModelWithResponse(trainedModel.getModelId()))
.assertNext(customFormModelResponse -> {
- assertEquals(customFormModelResponse.getStatusCode(), HttpResponseStatus.OK.code());
+ assertEquals(200, customFormModelResponse.getStatusCode());
validateCustomModelData(syncPoller.getFinalResult(), false, false);
});
});
@@ -182,7 +180,7 @@ public void deleteModelValidModelIdWithResponse(HttpClient httpClient,
CustomFormModel createdModel = syncPoller.getFinalResult();
StepVerifier.create(client.deleteModelWithResponse(createdModel.getModelId()))
- .assertNext(response -> assertEquals(response.getStatusCode(), HttpResponseStatus.NO_CONTENT.code()))
+ .assertNext(response -> assertEquals(204, response.getStatusCode()))
.expectComplete()
.verify(DEFAULT_TIMEOUT);
@@ -212,7 +210,7 @@ public void deleteModelValidModelIdWithResponseWithoutTrainingLabels(HttpClient
CustomFormModel createdModel = syncPoller.getFinalResult();
StepVerifier.create(client.deleteModelWithResponse(createdModel.getModelId()))
- .assertNext(response -> assertEquals(response.getStatusCode(), HttpResponseStatus.NO_CONTENT.code()))
+ .assertNext(response -> assertEquals(204, response.getStatusCode()))
.expectComplete()
.verify(DEFAULT_TIMEOUT);
@@ -671,7 +669,7 @@ public void beginCreateComposedUnlabeledModel(HttpClient httpClient, FormRecogni
StepVerifier.create(client.beginCreateComposedModel(modelIdList, new CreateComposedModelOptions())
.setPollInterval(durationTestMode)).thenAwait().expectErrorSatisfies(throwable -> {
assertEquals(HttpResponseException.class, throwable.getClass());
- assertEquals(BAD_REQUEST.code(), ((HttpResponseException) throwable).getResponse().getStatusCode());
+ assertEquals(400, ((HttpResponseException) throwable).getResponse().getStatusCode());
}).verify(DEFAULT_TIMEOUT);
client.deleteModel(model1.getModelId()).block();
@@ -701,7 +699,7 @@ public void beginCreateComposedDuplicateModels(HttpClient httpClient, FormRecogn
.setPollInterval(durationTestMode)
.getSyncPoller()
.waitForCompletion());
- assertEquals(BAD_REQUEST.code(), httpResponseException.getResponse().getStatusCode());
+ assertEquals(400, httpResponseException.getResponse().getStatusCode());
client.deleteModel(model1.getModelId()).block();
});
diff --git a/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/FormTrainingClientTest.java b/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/FormTrainingClientTest.java
index 36f92011b7faf..854c0cc850ae9 100644
--- a/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/FormTrainingClientTest.java
+++ b/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/FormTrainingClientTest.java
@@ -23,7 +23,6 @@
import com.azure.core.http.rest.Response;
import com.azure.core.util.Context;
import com.azure.core.util.polling.SyncPoller;
-import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -33,7 +32,6 @@
import static com.azure.ai.formrecognizer.FormRecognizerClientTestBase.MODEL_ID_NOT_FOUND_ERROR_CODE;
import static com.azure.ai.formrecognizer.TestUtils.DISPLAY_NAME_WITH_ARGUMENTS;
-import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -81,7 +79,7 @@ public void getCustomModelWithResponse(HttpClient httpClient, FormRecognizerServ
.getFinalResult();
Response customModelWithResponse
= client.getCustomModelWithResponse(trainedUnlabeledModel.getModelId(), Context.NONE);
- assertEquals(customModelWithResponse.getStatusCode(), HttpResponseStatus.OK.code());
+ assertEquals(200, customModelWithResponse.getStatusCode());
validateCustomModelData(customModelWithResponse.getValue(), false, false);
});
}
@@ -143,7 +141,7 @@ public void validGetAccountPropertiesWithResponse(HttpClient httpClient,
// TODO (service bug): APIM error
client = getFormTrainingClient(httpClient, serviceVersion);
Response accountPropertiesResponse = client.getAccountPropertiesWithResponse(Context.NONE);
- assertEquals(accountPropertiesResponse.getStatusCode(), HttpResponseStatus.OK.code());
+ assertEquals(200, accountPropertiesResponse.getStatusCode());
validateAccountProperties(accountPropertiesResponse.getValue());
}
@@ -161,7 +159,7 @@ public void deleteModelValidModelIdWithResponse(HttpClient httpClient,
final Response deleteModelWithResponse
= client.deleteModelWithResponse(createdModel.getModelId(), Context.NONE);
- assertEquals(deleteModelWithResponse.getStatusCode(), HttpResponseStatus.NO_CONTENT.code());
+ assertEquals(204, deleteModelWithResponse.getStatusCode());
final HttpResponseException exception = assertThrows(HttpResponseException.class,
() -> client.getCustomModelWithResponse(createdModel.getModelId(), Context.NONE));
final FormRecognizerErrorInformation errorInformation
@@ -184,7 +182,7 @@ public void deleteModelValidModelIdWithResponseWithoutTrainingLabels(HttpClient
final Response deleteModelWithResponse
= client.deleteModelWithResponse(createdModel.getModelId(), Context.NONE);
- assertEquals(deleteModelWithResponse.getStatusCode(), HttpResponseStatus.NO_CONTENT.code());
+ assertEquals(204, deleteModelWithResponse.getStatusCode());
final HttpResponseException exception = assertThrows(HttpResponseException.class,
() -> client.getCustomModelWithResponse(createdModel.getModelId(), Context.NONE));
final FormRecognizerErrorInformation errorInformation
@@ -573,7 +571,7 @@ public void beginCreateComposedUnlabeledModel(HttpClient httpClient, FormRecogni
final HttpResponseException httpResponseException = assertThrows(HttpResponseException.class,
() -> client.beginCreateComposedModel(modelIdList, new CreateComposedModelOptions(), Context.NONE)
.setPollInterval(durationTestMode));
- assertEquals(BAD_REQUEST.code(), httpResponseException.getResponse().getStatusCode());
+ assertEquals(400, httpResponseException.getResponse().getStatusCode());
client.deleteModel(model1.getModelId());
client.deleteModel(model2.getModelId());
@@ -599,7 +597,7 @@ public void beginCreateComposedDuplicateModels(HttpClient httpClient, FormRecogn
() -> client.beginCreateComposedModel(modelIdList, new CreateComposedModelOptions(), Context.NONE)
.setPollInterval(durationTestMode)
.getFinalResult());
- assertEquals(BAD_REQUEST.code(), httpResponseException.getResponse().getStatusCode());
+ assertEquals(400, httpResponseException.getResponse().getStatusCode());
client.deleteModel(model1.getModelId());
});
diff --git a/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/documentanalysis/administration/DocumentModelAdminClientTest.java b/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/documentanalysis/administration/DocumentModelAdminClientTest.java
index 01c8fe2412403..54176159d33d2 100644
--- a/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/documentanalysis/administration/DocumentModelAdminClientTest.java
+++ b/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/documentanalysis/administration/DocumentModelAdminClientTest.java
@@ -29,7 +29,6 @@
import com.azure.core.util.BinaryData;
import com.azure.core.util.Context;
import com.azure.core.util.polling.SyncPoller;
-import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -110,7 +109,7 @@ public void getModelWithResponse(HttpClient httpClient, DocumentAnalysisServiceV
= client.getDocumentModelWithResponse(documentModelDetails.getModelId(), Context.NONE);
client.deleteDocumentModel(documentModelDetails.getModelId());
- assertEquals(documentModelResponse.getStatusCode(), HttpResponseStatus.OK.code());
+ assertEquals(200, documentModelResponse.getStatusCode());
validateDocumentModelData(documentModelResponse.getValue());
});
}
@@ -134,7 +133,7 @@ public void validGetResourceDetailsWithResponse(HttpClient httpClient,
DocumentAnalysisServiceVersion serviceVersion) {
client = getDocumentModelAdministrationClient(httpClient, serviceVersion);
Response resourceDetailsResponse = client.getResourceDetailsWithResponse(Context.NONE);
- assertEquals(resourceDetailsResponse.getStatusCode(), HttpResponseStatus.OK.code());
+ assertEquals(200, resourceDetailsResponse.getStatusCode());
validateResourceInfo(resourceDetailsResponse.getValue());
}
@@ -166,7 +165,7 @@ public void deleteModelValidModelIDWithResponse(HttpClient httpClient,
final Response deleteModelWithResponse
= client.deleteDocumentModelWithResponse(createdModel.getModelId(), Context.NONE);
- assertEquals(deleteModelWithResponse.getStatusCode(), HttpResponseStatus.NO_CONTENT.code());
+ assertEquals(204, deleteModelWithResponse.getStatusCode());
final HttpResponseException exception = assertThrows(HttpResponseException.class,
() -> client.getDocumentModelWithResponse(createdModel.getModelId(), Context.NONE));
final ResponseError responseError = (ResponseError) exception.getValue();
diff --git a/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/documentanalysis/administration/DocumentModelAdministrationAsyncClientTest.java b/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/documentanalysis/administration/DocumentModelAdministrationAsyncClientTest.java
index 7c1c164b6c0ed..59961b7503bd7 100644
--- a/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/documentanalysis/administration/DocumentModelAdministrationAsyncClientTest.java
+++ b/sdk/formrecognizer/azure-ai-formrecognizer/src/test/java/com/azure/ai/formrecognizer/documentanalysis/administration/DocumentModelAdministrationAsyncClientTest.java
@@ -33,7 +33,6 @@
import com.azure.core.util.polling.PollerFlux;
import com.azure.core.util.polling.SyncPoller;
import com.azure.identity.AzureAuthorityHosts;
-import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
@@ -134,7 +133,7 @@ public void deleteModelValidModelIdWithResponse(HttpClient httpClient,
DocumentModelDetails createdModel = syncPoller1.getFinalResult();
StepVerifier.create(client.deleteDocumentModelWithResponse(createdModel.getModelId()))
- .assertNext(response -> assertEquals(response.getStatusCode(), HttpResponseStatus.NO_CONTENT.code()))
+ .assertNext(response -> assertEquals(204, response.getStatusCode()))
.expectComplete()
.verify(DEFAULT_TIMEOUT);
@@ -482,7 +481,7 @@ public void getModelWithResponse(HttpClient httpClient, DocumentAnalysisServiceV
StepVerifier.create(client.getDocumentModelWithResponse(createdModel.getModelId()))
.assertNext(documentModelResponse -> {
- assertEquals(documentModelResponse.getStatusCode(), HttpResponseStatus.OK.code());
+ assertEquals(200, documentModelResponse.getStatusCode());
validateDocumentModelData(documentModelResponse.getValue());
});
// TODO (alzimmer): This test needs to be recorded again as it was never verifying, therefore never
diff --git a/sdk/identity/azure-identity/pom.xml b/sdk/identity/azure-identity/pom.xml
index 6a23070d50ff4..4b7c7e2a5b716 100644
--- a/sdk/identity/azure-identity/pom.xml
+++ b/sdk/identity/azure-identity/pom.xml
@@ -18,6 +18,7 @@
--add-opens java.xml/jdk.xml.internal=ALL-UNNAMED
--add-exports com.azure.core/com.azure.core.implementation.util=ALL-UNNAMED
+ --add-reads com.azure.identity=io.netty.transport,io.netty.buffer,io.netty.handler,io.netty.codec.http,io.netty.common
false
@@ -35,13 +36,13 @@
com.azure
azure-core
- 1.60.0
+ 1.61.0-beta.1
com.azure
azure-core-http-netty
- 1.16.8
+ 1.17.0-beta.1
@@ -62,7 +63,7 @@
com.azure
azure-core-test
- 1.27.0-beta.19
+ 1.27.0-beta.20
test
diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialIdentityBindingTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialIdentityBindingTest.java
index ab8a2ec756ff1..ba206acdf5d30 100644
--- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialIdentityBindingTest.java
+++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialIdentityBindingTest.java
@@ -15,8 +15,9 @@
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
-import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
@@ -76,8 +77,8 @@ public class WorkloadIdentityCredentialIdentityBindingTest {
@TempDir
Path tempDir;
- private NioEventLoopGroup bossGroup;
- private NioEventLoopGroup workerGroup;
+ private MultiThreadIoEventLoopGroup bossGroup;
+ private MultiThreadIoEventLoopGroup workerGroup;
private Channel serverChannel;
private String serverBaseUrl;
private Path tokenFilePath;
@@ -543,8 +544,8 @@ private void startNettyHttpsServer(PrivateKey privateKey, X509Certificate certif
SslContext sslContext
= SslContextBuilder.forServer(privateKey, certificate).sslProvider(SslProvider.OPENSSL).build();
- bossGroup = new NioEventLoopGroup(1);
- workerGroup = new NioEventLoopGroup();
+ bossGroup = new MultiThreadIoEventLoopGroup(1, NioIoHandler.newFactory());
+ workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/test/java/com/azure/security/keyvault/secrets/SecretClientTest.java b/sdk/keyvault/azure-security-keyvault-secrets/src/test/java/com/azure/security/keyvault/secrets/SecretClientTest.java
index 111e05ecb8bfd..374df5aa02b31 100644
--- a/sdk/keyvault/azure-security-keyvault-secrets/src/test/java/com/azure/security/keyvault/secrets/SecretClientTest.java
+++ b/sdk/keyvault/azure-security-keyvault-secrets/src/test/java/com/azure/security/keyvault/secrets/SecretClientTest.java
@@ -15,7 +15,6 @@
import com.azure.security.keyvault.secrets.models.DeletedSecret;
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
import com.azure.security.keyvault.secrets.models.SecretProperties;
-import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -230,8 +229,7 @@ public void deleteSecret(HttpClient httpClient, SecretServiceVersion serviceVers
public void deleteSecretNotFound(HttpClient httpClient, SecretServiceVersion serviceVersion) {
createClient(httpClient, serviceVersion);
- assertRestException(() -> secretClient.beginDeleteSecret("non-existing"), ResourceNotFoundException.class,
- HttpResponseStatus.NOT_FOUND.code());
+ assertRestException(() -> secretClient.beginDeleteSecret("non-existing"), ResourceNotFoundException.class, 404);
}
/**
@@ -309,7 +307,7 @@ public void recoverDeletedSecretNotFound(HttpClient httpClient, SecretServiceVer
createClient(httpClient, serviceVersion);
assertRestException(() -> secretClient.beginRecoverDeletedSecret("non-existing"),
- ResourceNotFoundException.class, HttpResponseStatus.NOT_FOUND.code());
+ ResourceNotFoundException.class, 404);
}
/**
diff --git a/sdk/metricsadvisor/azure-ai-metricsadvisor/src/test/java/com/azure/ai/metricsadvisor/DataFeedAsyncClientTest.java b/sdk/metricsadvisor/azure-ai-metricsadvisor/src/test/java/com/azure/ai/metricsadvisor/DataFeedAsyncClientTest.java
index e809843003ff0..dcc8660bd6c6e 100644
--- a/sdk/metricsadvisor/azure-ai-metricsadvisor/src/test/java/com/azure/ai/metricsadvisor/DataFeedAsyncClientTest.java
+++ b/sdk/metricsadvisor/azure-ai-metricsadvisor/src/test/java/com/azure/ai/metricsadvisor/DataFeedAsyncClientTest.java
@@ -16,7 +16,6 @@
import com.azure.core.test.annotation.DoNotRecord;
import com.azure.core.test.http.MockHttpResponse;
import com.azure.core.util.CoreUtils;
-import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -346,7 +345,7 @@ public void getDataFeedValidId(HttpClient httpClient, MetricsAdvisorServiceVersi
// Act & Assert
StepVerifier.create(client.getDataFeedWithResponse(createdDataFeed.getId()))
.assertNext(dataFeedResponse -> {
- assertEquals(dataFeedResponse.getStatusCode(), HttpResponseStatus.OK.code());
+ assertEquals(200, dataFeedResponse.getStatusCode());
validateDataFeedResult(createdDataFeed, dataFeedResponse.getValue(), SQL_SERVER_DB);
});
// TODO (alzimmer): This test needs to be recorded again as it was never verifying, therefore never
@@ -725,7 +724,7 @@ public void deleteDataFeedIdWithResponse(HttpClient httpClient, MetricsAdvisorSe
assertNotNull(createdDataFeed);
StepVerifier.create(client.deleteDataFeedWithResponse(createdDataFeed.getId()))
- .assertNext(response -> assertEquals(response.getStatusCode(), HttpResponseStatus.NO_CONTENT.code()))
+ .assertNext(response -> assertEquals(204, response.getStatusCode()))
.expectComplete()
.verify(DEFAULT_TIMEOUT);
diff --git a/sdk/metricsadvisor/azure-ai-metricsadvisor/src/test/java/com/azure/ai/metricsadvisor/DataFeedClientTest.java b/sdk/metricsadvisor/azure-ai-metricsadvisor/src/test/java/com/azure/ai/metricsadvisor/DataFeedClientTest.java
index c6f1ec9b5b355..a81dcc4edf2ed 100644
--- a/sdk/metricsadvisor/azure-ai-metricsadvisor/src/test/java/com/azure/ai/metricsadvisor/DataFeedClientTest.java
+++ b/sdk/metricsadvisor/azure-ai-metricsadvisor/src/test/java/com/azure/ai/metricsadvisor/DataFeedClientTest.java
@@ -23,7 +23,6 @@
import com.azure.core.test.http.MockHttpResponse;
import com.azure.core.util.Context;
import com.azure.core.util.CoreUtils;
-import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -316,7 +315,7 @@ public void getDataFeedValidId(HttpClient httpClient, MetricsAdvisorServiceVersi
// Act & Assert
final Response dataFeedResponse
= client.getDataFeedWithResponse(createdDataFeed.getId(), Context.NONE);
- assertEquals(dataFeedResponse.getStatusCode(), HttpResponseStatus.OK.code());
+ assertEquals(200, dataFeedResponse.getStatusCode());
validateDataFeedResult(createdDataFeed, dataFeedResponse.getValue(), SQL_SERVER_DB);
}, SQL_SERVER_DB);
} finally {
@@ -706,8 +705,7 @@ public void deleteDataFeedIdWithResponse(HttpClient httpClient, MetricsAdvisorSe
creatDataFeedRunner(dataFeed -> {
final DataFeed createdDataFeed = client.createDataFeed(dataFeed);
- assertEquals(HttpResponseStatus.NO_CONTENT.code(),
- client.deleteDataFeedWithResponse(createdDataFeed.getId(), Context.NONE).getStatusCode());
+ assertEquals(204, client.deleteDataFeedWithResponse(createdDataFeed.getId(), Context.NONE).getStatusCode());
// Act & Assert
MetricsAdvisorResponseException exception = assertThrows(MetricsAdvisorResponseException.class,
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/pom.xml b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/pom.xml
index 0d1c4bc1e9605..21acaf9209663 100644
--- a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/pom.xml
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/pom.xml
@@ -38,6 +38,7 @@
--add-opens com.azure.monitor.opentelemetry.autoconfigure/com.azure.monitor.opentelemetry.autoconfigure=ALL-UNNAMED
--add-exports com.azure.core/com.azure.core.implementation.util=ALL-UNNAMED
--add-reads com.azure.monitor.opentelemetry.autoconfigure=com.azure.core.tracing.opentelemetry
+ --add-reads com.azure.monitor.opentelemetry.autoconfigure=io.netty.transport
false
diff --git a/sdk/openai/azure-ai-openai-realtime/CHANGELOG.md b/sdk/openai/azure-ai-openai-realtime/CHANGELOG.md
index b64e6ac33abd5..29ba79c4bccd4 100644
--- a/sdk/openai/azure-ai-openai-realtime/CHANGELOG.md
+++ b/sdk/openai/azure-ai-openai-realtime/CHANGELOG.md
@@ -2,6 +2,8 @@
#### Dependency Updates
+- Migrated `netty-codec-http` to `4.2.19.Final` and updated WebSocket event loops for Netty 4.2.
+
## 1.0.0-beta.1 (TBD)
-- Azure OpenAI Realtime client library for Java.
\ No newline at end of file
+- Azure OpenAI Realtime client library for Java.
diff --git a/sdk/openai/azure-ai-openai-realtime/pom.xml b/sdk/openai/azure-ai-openai-realtime/pom.xml
index a666b0f016daf..77fbbc6d14ea9 100644
--- a/sdk/openai/azure-ai-openai-realtime/pom.xml
+++ b/sdk/openai/azure-ai-openai-realtime/pom.xml
@@ -59,14 +59,14 @@
com.azure
azure-core
- 1.60.0
+ 1.61.0-beta.1
io.netty
netty-codec-http
- 4.1.138.Final
+ 4.2.19.Final
@@ -81,7 +81,13 @@
com.azure
azure-core-test
- 1.27.0-beta.19
+ 1.27.0-beta.20
+ test
+
+
+ com.azure
+ azure-core-http-netty
+ 1.17.0-beta.1
test
@@ -102,7 +108,7 @@
- io.netty:netty-codec-http:[4.1.138.Final]
+ io.netty:netty-codec-http:[4.2.19.Final]
diff --git a/sdk/openai/azure-ai-openai-realtime/src/main/java/com/azure/ai/openai/realtime/implementation/websocket/WebSocketSessionNettyImpl.java b/sdk/openai/azure-ai-openai-realtime/src/main/java/com/azure/ai/openai/realtime/implementation/websocket/WebSocketSessionNettyImpl.java
index a033e6c6cac74..9bb3205ef776f 100644
--- a/sdk/openai/azure-ai-openai-realtime/src/main/java/com/azure/ai/openai/realtime/implementation/websocket/WebSocketSessionNettyImpl.java
+++ b/sdk/openai/azure-ai-openai-realtime/src/main/java/com/azure/ai/openai/realtime/implementation/websocket/WebSocketSessionNettyImpl.java
@@ -14,7 +14,8 @@
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
-import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
@@ -34,7 +35,6 @@
import javax.net.ssl.SSLException;
import java.net.URI;
-import java.net.URISyntaxException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -103,7 +103,7 @@ protected void initChannel(SocketChannel ch) {
this.closeHandler = closeHandler;
}
- void connect() throws URISyntaxException, SSLException, InterruptedException, ExecutionException {
+ void connect() throws SSLException, InterruptedException, ExecutionException {
String scheme = uri.getScheme() == null ? "ws" : uri.getScheme();
final String host = uri.getHost() == null ? "127.0.0.1" : uri.getHost();
final int port;
@@ -131,7 +131,7 @@ void connect() throws URISyntaxException, SSLException, InterruptedException, Ex
sslCtx = null;
}
- group = new NioEventLoopGroup();
+ group = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
handshaker = WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, this.subProtocol, true,
this.headers);
diff --git a/sdk/openai/azure-ai-openai-realtime/src/main/java/module-info.java b/sdk/openai/azure-ai-openai-realtime/src/main/java/module-info.java
index 6ea952fb0ced4..e53704ff8e5e8 100644
--- a/sdk/openai/azure-ai-openai-realtime/src/main/java/module-info.java
+++ b/sdk/openai/azure-ai-openai-realtime/src/main/java/module-info.java
@@ -10,6 +10,7 @@
requires io.netty.transport;
requires io.netty.handler;
requires io.netty.codec;
+ requires io.netty.codec.compression;
requires io.netty.codec.http;
exports com.azure.ai.openai.realtime;
diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/administration/ServiceBusAdministrationAsyncClientTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/administration/ServiceBusAdministrationAsyncClientTest.java
index 8e607a15a11af..206cc29a186cd 100644
--- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/administration/ServiceBusAdministrationAsyncClientTest.java
+++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/administration/ServiceBusAdministrationAsyncClientTest.java
@@ -38,7 +38,6 @@
import com.azure.messaging.servicebus.administration.models.QueueRuntimeProperties;
import com.azure.xml.XmlSerializable;
import com.azure.xml.XmlWriter;
-import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -528,7 +527,7 @@ void deleteRuleWithResponse() {
// Act & Assert
StepVerifier.create(client.deleteRuleWithResponse(topicName, subscriptionName, ruleName))
- .assertNext(actual -> assertEquals(actual.getStatusCode(), HttpResponseStatus.NO_CONTENT.code()))
+ .assertNext(actual -> assertEquals(204, actual.getStatusCode()))
.expectComplete()
.verify(DEFAULT_TIMEOUT);
}
@@ -555,7 +554,7 @@ void deleteSubscriptionWithResponse() {
// Act & Assert
StepVerifier.create(client.deleteSubscriptionWithResponse(topicName, subscriptionName))
- .assertNext(actual -> assertEquals(actual.getStatusCode(), HttpResponseStatus.NO_CONTENT.code()))
+ .assertNext(actual -> assertEquals(204, actual.getStatusCode()))
.expectComplete()
.verify(DEFAULT_TIMEOUT);
}
@@ -581,7 +580,7 @@ void deleteTopicWithResponse() {
// Act & Assert
StepVerifier.create(client.deleteTopicWithResponse(topicName))
- .assertNext(actual -> assertEquals(actual.getStatusCode(), HttpResponseStatus.NO_CONTENT.code()))
+ .assertNext(actual -> assertEquals(204, actual.getStatusCode()))
.expectComplete()
.verify(DEFAULT_TIMEOUT);
}
diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/administration/ServiceBusAdministrationClientTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/administration/ServiceBusAdministrationClientTest.java
index 9d8174d3acc22..c5c780ed6db3e 100644
--- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/administration/ServiceBusAdministrationClientTest.java
+++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/administration/ServiceBusAdministrationClientTest.java
@@ -36,7 +36,6 @@
import com.azure.messaging.servicebus.administration.models.QueueProperties;
import com.azure.messaging.servicebus.administration.models.QueueRuntimeProperties;
import com.azure.messaging.servicebus.administration.models.SubscriptionProperties;
-import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -508,7 +507,7 @@ void deleteRuleWithResponse() {
final Response actual = client.deleteRuleWithResponse(topicName, subscriptionName, ruleName, context);
// Assert
- assertEquals(actual.getStatusCode(), HttpResponseStatus.NO_CONTENT.code());
+ assertEquals(204, actual.getStatusCode());
}
@Test
@@ -534,7 +533,7 @@ void deleteSubscriptionWithResponse() {
final Response actual = client.deleteSubscriptionWithResponse(topicName, subscriptionName, context);
// Assert
- assertEquals(actual.getStatusCode(), HttpResponseStatus.NO_CONTENT.code());
+ assertEquals(204, actual.getStatusCode());
}
@Test
@@ -560,7 +559,7 @@ void deleteTopicWithResponse() {
final Response actual = client.deleteTopicWithResponse(topicName, context);
// Assert
- assertEquals(actual.getStatusCode(), HttpResponseStatus.NO_CONTENT.code());
+ assertEquals(204, actual.getStatusCode());
}
/**
diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/CHANGELOG.md b/sdk/spring/spring-cloud-azure-autoconfigure/CHANGELOG.md
index 5114a28e73ae7..8c3881a6746dc 100644
--- a/sdk/spring/spring-cloud-azure-autoconfigure/CHANGELOG.md
+++ b/sdk/spring/spring-cloud-azure-autoconfigure/CHANGELOG.md
@@ -10,6 +10,8 @@
### Other Changes
+- Aligned Reactor version resolution and test HTTP transport dependencies with Netty 4.2, and added offline transport compatibility tests.
+
## 7.4.0 (2026-07-24)
Please refer to [spring/CHANGELOG.md](https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/spring/CHANGELOG.md#740-2026-07-24) for more details.
@@ -253,4 +255,3 @@ Please refer to [spring/CHANGELOG.md](https://github.com/Azure/azure-sdk-for-jav
## 4.0.0-beta.2 (2021-11-22)
Please refer to [spring/CHANGELOG.md](https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/spring/CHANGELOG.md#400-beta2-2021-11-22) for more details.
-
diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/pom.xml b/sdk/spring/spring-cloud-azure-autoconfigure/pom.xml
index 2c1dc467d8885..48e5a0dd36102 100644
--- a/sdk/spring/spring-cloud-azure-autoconfigure/pom.xml
+++ b/sdk/spring/spring-cloud-azure-autoconfigure/pom.xml
@@ -102,6 +102,12 @@
lettuce-core
7.5.2.RELEASE
true
+
+
+ io.projectreactor
+ reactor-core
+
+
org.springframework.boot
@@ -193,7 +199,7 @@
com.azure
azure-core
- 1.60.0
+ 1.61.0-beta.1
true
@@ -417,6 +423,13 @@
+
+
+ com.azure
+ azure-core-http-netty
+ 1.17.0-beta.1
+ test
+
org.springframework.boot
spring-boot-starter-test
diff --git a/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml b/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml
index 1b31055bb9cf4..93557e043243e 100644
--- a/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml
+++ b/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml
@@ -106,7 +106,7 @@ currently released version and a lower version is resolved. -->
io.netty
netty-buffer
- 4.2.17.Final
+ 4.2.19.Final
io.netty
diff --git a/sdk/voicelive/azure-ai-voicelive/pom.xml b/sdk/voicelive/azure-ai-voicelive/pom.xml
index 2957a0dac503e..4e816b571c224 100644
--- a/sdk/voicelive/azure-ai-voicelive/pom.xml
+++ b/sdk/voicelive/azure-ai-voicelive/pom.xml
@@ -84,7 +84,7 @@ Code generated by Microsoft (R) TypeSpec Code Generator.
io.projectreactor
reactor-test
- 3.7.19
+ 3.8.7
test
diff --git a/sdk/webpubsub/azure-messaging-webpubsub-chat/pom.xml b/sdk/webpubsub/azure-messaging-webpubsub-chat/pom.xml
index f95a74d16a943..884bafaebba30 100644
--- a/sdk/webpubsub/azure-messaging-webpubsub-chat/pom.xml
+++ b/sdk/webpubsub/azure-messaging-webpubsub-chat/pom.xml
@@ -48,12 +48,12 @@
com.azure
azure-core
- 1.60.0
+ 1.61.0-beta.1
com.azure
azure-core-http-netty
- 1.16.8
+ 1.17.0-beta.1
com.nimbusds
@@ -63,7 +63,7 @@
com.azure
azure-core-test
- 1.27.0-beta.19
+ 1.27.0-beta.20
test
@@ -75,7 +75,7 @@
io.projectreactor.netty
reactor-netty-http
- 1.2.18
+ 1.3.7
test
@@ -90,7 +90,7 @@
com.nimbusds:nimbus-jose-jwt:[9.37.3]
- io.projectreactor.netty:reactor-netty-http:[1.2.18]
+ io.projectreactor.netty:reactor-netty-http:[1.3.7]
diff --git a/sdk/webpubsub/azure-messaging-webpubsub-client/CHANGELOG.md b/sdk/webpubsub/azure-messaging-webpubsub-client/CHANGELOG.md
index 5f0708972dc2d..cdb638dfd811e 100644
--- a/sdk/webpubsub/azure-messaging-webpubsub-client/CHANGELOG.md
+++ b/sdk/webpubsub/azure-messaging-webpubsub-client/CHANGELOG.md
@@ -14,7 +14,7 @@
#### Dependency Updates
-- Upgraded `netty-codec-http` from `4.1.137.Final` to version `4.1.138.Final`.
+- Migrated `netty-codec-http` to `4.2.19.Final` and updated WebSocket event loops for Netty 4.2.
## 1.1.11 (2026-10-06)
diff --git a/sdk/webpubsub/azure-messaging-webpubsub-client/pom.xml b/sdk/webpubsub/azure-messaging-webpubsub-client/pom.xml
index b5c069ce771a1..d4235659fa57a 100644
--- a/sdk/webpubsub/azure-messaging-webpubsub-client/pom.xml
+++ b/sdk/webpubsub/azure-messaging-webpubsub-client/pom.xml
@@ -48,21 +48,27 @@
com.azure
azure-core
- 1.60.0
+ 1.61.0-beta.1
io.netty
netty-codec-http
- 4.1.138.Final
+ 4.2.19.Final
com.azure
azure-core-test
- 1.27.0-beta.19
+ 1.27.0-beta.20
+ test
+
+
+ com.azure
+ azure-core-http-netty
+ 1.17.0-beta.1
test
@@ -101,7 +107,7 @@
- io.netty:netty-codec-http:[4.1.138.Final]
+ io.netty:netty-codec-http:[4.2.19.Final]
diff --git a/sdk/webpubsub/azure-messaging-webpubsub-client/src/main/java/com/azure/messaging/webpubsub/client/implementation/websocket/WebSocketSessionNettyImpl.java b/sdk/webpubsub/azure-messaging-webpubsub-client/src/main/java/com/azure/messaging/webpubsub/client/implementation/websocket/WebSocketSessionNettyImpl.java
index 8229e60f16cf8..9f46a915ee102 100644
--- a/sdk/webpubsub/azure-messaging-webpubsub-client/src/main/java/com/azure/messaging/webpubsub/client/implementation/websocket/WebSocketSessionNettyImpl.java
+++ b/sdk/webpubsub/azure-messaging-webpubsub-client/src/main/java/com/azure/messaging/webpubsub/client/implementation/websocket/WebSocketSessionNettyImpl.java
@@ -10,11 +10,15 @@
import com.azure.messaging.webpubsub.client.implementation.models.WebPubSubMessage;
import com.azure.messaging.webpubsub.client.models.ConnectFailedException;
import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
-import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
@@ -29,6 +33,7 @@
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.util.internal.SystemPropertyUtil;
import javax.net.ssl.SSLException;
import java.net.URI;
@@ -75,7 +80,7 @@ protected void initChannel(SocketChannel ch) {
p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
}
- p.addLast(new HttpClientCodec(), new HttpObjectAggregator(8192), WebSocketClientCompressionHandler.INSTANCE,
+ p.addLast(new HttpClientCodec(), new HttpObjectAggregator(8192), new WebSocketClientCompressionHandler(0),
handler);
}
}
@@ -123,7 +128,7 @@ void connect() throws URISyntaxException, SSLException, InterruptedException, Ex
sslCtx = null;
}
- group = new NioEventLoopGroup();
+ group = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
handshaker = WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, protocol, true,
new DefaultHttpHeaders().add(HttpHeaderName.USER_AGENT.getCaseInsensitiveName(), userAgent));
@@ -133,6 +138,13 @@ void connect() throws URISyntaxException, SSLException, InterruptedException, Ex
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
+ .option(ChannelOption.ALLOCATOR,
+ // If io.netty.allocator.type was configured, respect the configuration. Otherwise, fallback to the
+ // default setting from Netty 4.1 which was the pooled allocator. In the future, this should be
+ // validated for if this should use the new Netty 4.2 default which is an adaptive allocator.
+ SystemPropertyUtil.contains("io.netty.allocator.type")
+ ? ByteBufAllocator.DEFAULT
+ : PooledByteBufAllocator.DEFAULT)
.handler(new WebSocketChannelHandler(host, port, sslCtx, clientHandler));
final CompletableFuture handshakeCallbackFuture = new CompletableFuture<>();
diff --git a/sdk/webpubsub/azure-messaging-webpubsub-client/src/main/java/module-info.java b/sdk/webpubsub/azure-messaging-webpubsub-client/src/main/java/module-info.java
index b701da2f86b2b..478c4b9eb9a9d 100644
--- a/sdk/webpubsub/azure-messaging-webpubsub-client/src/main/java/module-info.java
+++ b/sdk/webpubsub/azure-messaging-webpubsub-client/src/main/java/module-info.java
@@ -9,6 +9,7 @@
requires io.netty.transport;
requires io.netty.handler;
requires io.netty.codec;
+ requires io.netty.codec.compression;
requires io.netty.codec.http;
exports com.azure.messaging.webpubsub.client;