Skip to content

Commit

Permalink
Make transport.Bootstrap usable with no netty-resolver on classpath (#…
Browse files Browse the repository at this point in the history
…13488)

Motivation:
Since some address families (e.g. unix sockets) do not need name
resolver, It should be possible to exclude netty-resolver from
classpath.

Currently excluding name resolver leads to NoClassDefFoundError:
AddressResolverGroup during Bootstrap instantiation.

Modification:

Add disableResolver() method to Bootstrap.

Change BootstrapConfig.resolver() return null if
Bootstrap.disableResolver() is called.

Introduce ExternalAddressResolver class to hold AddressResolver related
references to avoid NoClassDefFoundError if Bootstrap.disableResolver()
called and no netty-resolver is on classpath.

Result:

Bootstrap is usable with no netty-resolver on classpath.

---------

Co-authored-by: Chris Vest <mr.chrisvest@gmail.com>
  • Loading branch information
2 people authored and normanmaurer committed Jul 18, 2023
1 parent 1ea8240 commit 9ce06cc
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 11 deletions.
62 changes: 53 additions & 9 deletions transport/src/main/java/io/netty5/bootstrap/Bootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,19 @@ public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel, ChannelFact

private static final Logger logger = LoggerFactory.getLogger(Bootstrap.class);

private static final AddressResolverGroup<?> DEFAULT_RESOLVER = DefaultAddressResolverGroup.INSTANCE;

private final BootstrapConfig config = new BootstrapConfig(this);

@SuppressWarnings("unchecked")
private volatile AddressResolverGroup<SocketAddress> resolver =
(AddressResolverGroup<SocketAddress>) DEFAULT_RESOLVER;
private ExternalAddressResolver externalResolver;
private volatile boolean disableResolver;
private volatile SocketAddress remoteAddress;
volatile ChannelFactory<? extends Channel> channelFactory;

public Bootstrap() { }

private Bootstrap(Bootstrap bootstrap) {
super(bootstrap);
resolver = bootstrap.resolver;
externalResolver = bootstrap.externalResolver;
disableResolver = bootstrap.disableResolver;
remoteAddress = bootstrap.remoteAddress;
channelFactory = bootstrap.channelFactory;
}
Expand All @@ -77,7 +75,18 @@ private Bootstrap(Bootstrap bootstrap) {
*/
@SuppressWarnings("unchecked")
public Bootstrap resolver(AddressResolverGroup<?> resolver) {
this.resolver = (AddressResolverGroup<SocketAddress>) (resolver == null ? DEFAULT_RESOLVER : resolver);
this.externalResolver = resolver == null ? null : new ExternalAddressResolver(resolver);
disableResolver = false;
return this;
}

/**
* Disables address name resolution. Name resolution may be re-enabled with
* {@link Bootstrap#resolver(AddressResolverGroup)}
*/
public Bootstrap disableResolver() {
externalResolver = null;
disableResolver = true;
return this;
}

Expand Down Expand Up @@ -215,8 +224,20 @@ private Future<Channel> doResolveAndConnect(final SocketAddress remoteAddress, f
private void doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
final SocketAddress localAddress, final Promise<Channel> promise) {
try {
if (disableResolver) {
doConnect(remoteAddress, localAddress, channel, promise);
return;
}

final EventLoop eventLoop = channel.executor();
final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);
AddressResolver<SocketAddress> resolver;
try {
resolver = ExternalAddressResolver.getOrDefault(externalResolver).getResolver(eventLoop);
} catch (Throwable cause) {
channel.close();
promise.setFailure(cause);
return;
}

if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
// Resolver has no idea about what to do with the specified remote address, or it's resolved already.
Expand Down Expand Up @@ -326,6 +347,29 @@ final SocketAddress remoteAddress() {
}

final AddressResolverGroup<?> resolver() {
return resolver;
if (disableResolver) {
return null;
}
return ExternalAddressResolver.getOrDefault(externalResolver);
}

/* Holder to avoid NoClassDefFoundError in case netty-resolver dependency is excluded
(e.g. some address families do not need name resolution) */
static final class ExternalAddressResolver {
final AddressResolverGroup<SocketAddress> resolverGroup;

@SuppressWarnings("unchecked")
ExternalAddressResolver(AddressResolverGroup<?> resolverGroup) {
this.resolverGroup = (AddressResolverGroup<SocketAddress>) resolverGroup;
}

@SuppressWarnings("unchecked")
static AddressResolverGroup<SocketAddress> getOrDefault(ExternalAddressResolver externalResolver) {
if (externalResolver == null) {
AddressResolverGroup<?> defaultResolverGroup = DefaultAddressResolverGroup.INSTANCE;
return (AddressResolverGroup<SocketAddress>) defaultResolverGroup;
}
return externalResolver.resolverGroup;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public SocketAddress remoteAddress() {
}

/**
* Returns the configured {@link AddressResolverGroup} or the default if non is configured yet.
* Returns the configured {@link AddressResolverGroup}, {@code null} if resolver was disabled
* with {@link Bootstrap#disableResolver()}, or the default if not configured yet.
*/
public AddressResolverGroup<?> resolver() {
return bootstrap.resolver();
Expand All @@ -54,7 +55,11 @@ public AddressResolverGroup<?> resolver() {
public String toString() {
StringBuilder buf = new StringBuilder(super.toString());
buf.setLength(buf.length() - 1);
buf.append(", resolver: ").append(resolver());
AddressResolverGroup<?> resolver = resolver();
if (resolver != null) {
buf.append(", resolver: ")
.append(resolver);
}
SocketAddress remoteAddress = remoteAddress();
if (remoteAddress != null) {
buf.append(", remoteAddress: ")
Expand Down
25 changes: 25 additions & 0 deletions transport/src/test/java/io/netty5/bootstrap/BootstrapTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.netty5.resolver.AbstractAddressResolver;
import io.netty5.resolver.AddressResolver;
import io.netty5.resolver.AddressResolverGroup;
import io.netty5.resolver.DefaultAddressResolverGroup;
import io.netty5.util.AttributeKey;
import io.netty5.util.concurrent.EventExecutor;
import io.netty5.util.concurrent.Future;
Expand Down Expand Up @@ -57,6 +58,8 @@
import static org.hamcrest.MatcherAssert.assertThat;
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.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;
Expand Down Expand Up @@ -261,6 +264,24 @@ public void testLateRegistrationConnect() throws Throwable {
}
}

@Test
void testResolverDefault() throws Exception {
Bootstrap bootstrap = new Bootstrap();

assertTrue(bootstrap.config().toString().contains("resolver:"));
assertNotNull(bootstrap.config().resolver());
assertEquals(DefaultAddressResolverGroup.class, bootstrap.config().resolver().getClass());
}

@Test
void testResolverDisabled() throws Exception {
Bootstrap bootstrap = new Bootstrap();
bootstrap.disableResolver();

assertFalse(bootstrap.config().toString().contains("resolver:"));
assertNull(bootstrap.config().resolver());
}

@Test
public void testAsyncResolutionSuccess() throws Exception {
final Bootstrap bootstrapA = new Bootstrap();
Expand All @@ -273,6 +294,10 @@ public void testAsyncResolutionSuccess() throws Exception {
bootstrapB.group(groupB);
bootstrapB.channel(LocalServerChannel.class);
bootstrapB.childHandler(dummyHandler);

assertTrue(bootstrapA.config().toString().contains("resolver:"));
assertThat(bootstrapA.resolver(), instanceOf(TestAddressResolverGroup.class));

SocketAddress localAddress = bootstrapB.bind(LocalAddress.ANY).asStage().get().localAddress();

// Connect to the server using the asynchronous resolver.
Expand Down

0 comments on commit 9ce06cc

Please sign in to comment.