Skip to content

Commit 2b86f8f

Browse files
authored
api: Add Grpc.newChannelBuilder accepting NameResolverRegistry (#11901)
This introduces a new `Grpc.newChannelBuilder` overload that allows callers to pass an explicit `NameResolverRegistry`. Fixes #11055
1 parent 49a7e89 commit 2b86f8f

18 files changed

Lines changed: 606 additions & 35 deletions

api/src/main/java/io/grpc/Grpc.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,34 @@ public static ManagedChannelBuilder<?> newChannelBuilder(
108108
return ManagedChannelRegistry.getDefaultRegistry().newChannelBuilder(target, creds);
109109
}
110110

111+
/**
112+
* Creates a channel builder with a target string, credentials, and a specific
113+
* name resolver registry.
114+
*
115+
* <p>The provided {@code nameResolverRegistry} is used to resolve the target address
116+
* into physical addresses (e.g., DNS or custom schemes).
117+
*
118+
* @param target the target URI for the channel, such as {@code "localhost:8080"}
119+
* or {@code "dns:///example.com"}
120+
* @param creds the channel credentials to use for secure communication
121+
* @param nameResolverRegistry the registry used to look up {@link NameResolver}
122+
* providers for the target
123+
* @return a {@link ManagedChannelBuilder} instance configured with the given parameters
124+
* @throws IllegalArgumentException if no provider is available for the given target
125+
* or credentials
126+
* @since 1.83.0
127+
*/
128+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/12694")
129+
public static ManagedChannelBuilder<?> newChannelBuilder(
130+
String target,
131+
ChannelCredentials creds,
132+
NameResolverRegistry nameResolverRegistry) {
133+
return ManagedChannelRegistry.getDefaultRegistry().newChannelBuilder(
134+
nameResolverRegistry,
135+
target,
136+
creds);
137+
}
138+
111139
/**
112140
* Creates a channel builder from a host, port, and credentials. The host and port are combined to
113141
* form an authority string and then passed to {@link #newChannelBuilder(String,

api/src/main/java/io/grpc/ManagedChannelProvider.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,31 @@ protected NewChannelBuilderResult newChannelBuilder(String target, ChannelCreden
8181
return NewChannelBuilderResult.error("ChannelCredentials are unsupported");
8282
}
8383

84+
/**
85+
* Creates a channel builder using the provided target, credentials, and resolution
86+
* components.
87+
*
88+
* <p>This method allows for fine-grained control over name resolution by providing
89+
* both a {@link NameResolverRegistry} and a specific {@link NameResolverProvider}.
90+
* This returns a {@link NewChannelBuilderResult},
91+
* which may contain an error string if the provided credentials or target are
92+
* not supported by this provider.
93+
*
94+
* @param target the target URI for the channel
95+
* @param creds the channel credentials to use
96+
* @param nameResolverRegistry the registry used for looking up name resolvers
97+
* @param nameResolverProvider a specific provider to use, or {@code null} to
98+
* search the registry
99+
* @return a {@link NewChannelBuilderResult} containing either the builder or an
100+
* error description
101+
* @since 1.83.0
102+
*/
103+
protected NewChannelBuilderResult newChannelBuilder(String target, ChannelCredentials creds,
104+
NameResolverRegistry nameResolverRegistry,
105+
NameResolverProvider nameResolverProvider) {
106+
return newChannelBuilder(target, creds);
107+
}
108+
84109
/**
85110
* Returns the {@link SocketAddress} types this ManagedChannelProvider supports.
86111
*/

api/src/main/java/io/grpc/ManagedChannelRegistry.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ ManagedChannelBuilder<?> newChannelBuilder(String target, ChannelCredentials cre
158158
return newChannelBuilder(NameResolverRegistry.getDefaultRegistry(), target, creds);
159159
}
160160

161-
@VisibleForTesting
162161
ManagedChannelBuilder<?> newChannelBuilder(NameResolverRegistry nameResolverRegistry,
163162
String target, ChannelCredentials creds) {
164163
NameResolverProvider nameResolverProvider = null;
@@ -198,7 +197,7 @@ ManagedChannelBuilder<?> newChannelBuilder(NameResolverRegistry nameResolverRegi
198197
continue;
199198
}
200199
ManagedChannelProvider.NewChannelBuilderResult result
201-
= provider.newChannelBuilder(target, creds);
200+
= provider.newChannelBuilder(target, creds, nameResolverRegistry, nameResolverProvider);
202201
if (result.getChannelBuilder() != null) {
203202
return result.getChannelBuilder();
204203
}

api/src/main/java/io/grpc/NameResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public abstract static class Factory {
190190
*/
191191
public NameResolver newNameResolver(Uri targetUri, final Args args) {
192192
// Not every io.grpc.Uri can be converted but in the ordinary ManagedChannel creation flow,
193-
// any IllegalArgumentException thrown here would happened anyway, just earlier. That's
193+
// any IllegalArgumentException thrown here would have happened anyway, just earlier. That's
194194
// because parse/toString is transparent so java.net.URI#create here sees the original target
195195
// string just like it did before the io.grpc.Uri migration.
196196
//

api/src/main/java/io/grpc/NameResolverProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public abstract class NameResolverProvider extends NameResolver.Factory {
6565
*
6666
* @since 1.40.0
6767
* */
68-
protected String getScheme() {
68+
public String getScheme() {
6969
return getDefaultScheme();
7070
}
7171

api/src/test/java/io/grpc/ManagedChannelRegistryTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,53 @@ public NewChannelBuilderResult newChannelBuilder(
243243
mcb);
244244
}
245245

246+
@Test
247+
public void newChannelBuilder_propagatesRegistry() {
248+
final NameResolverRegistry nameResolverRegistry = new NameResolverRegistry();
249+
class SocketAddress1 extends SocketAddress {
250+
}
251+
252+
ManagedChannelRegistry registry = new ManagedChannelRegistry();
253+
class MockChannelBuilder extends ForwardingChannelBuilder2<MockChannelBuilder> {
254+
@Override
255+
public ManagedChannelBuilder<?> delegate() {
256+
throw new UnsupportedOperationException();
257+
}
258+
}
259+
260+
final ManagedChannelBuilder<?> mcb = new MockChannelBuilder();
261+
registry.register(new BaseProvider(true, 4) {
262+
@Override
263+
protected Collection<Class<? extends SocketAddress>> getSupportedSocketAddressTypes() {
264+
return Collections.singleton(SocketAddress1.class);
265+
}
266+
267+
@Override
268+
public NewChannelBuilderResult newChannelBuilder(
269+
String passedTarget, ChannelCredentials passedCreds,
270+
NameResolverRegistry passedRegistry, NameResolverProvider passedProvider) {
271+
assertThat(passedRegistry).isSameInstanceAs(nameResolverRegistry);
272+
return NewChannelBuilderResult.channelBuilder(mcb);
273+
}
274+
});
275+
276+
// ManagedChannelRegistry.newChannelBuilder(NameResolverRegistry, String, ChannelCredentials)
277+
// gets the scheme from target. Then it gets NameResolverProvider from registry for that scheme.
278+
// Then it gets producedSocketAddressTypes from that provider.
279+
// Then it finds a ManagedChannelProvider that supports those types.
280+
// So we need a registered NameResolverProvider for the scheme.
281+
nameResolverRegistry.register(new BaseNameResolverProvider(true, 5, "sc1") {
282+
@Override
283+
public Collection<Class<? extends SocketAddress>> getProducedSocketAddressTypes() {
284+
return Collections.singleton(SocketAddress1.class);
285+
}
286+
});
287+
288+
assertThat(
289+
registry.newChannelBuilder(nameResolverRegistry, "sc1:" + target, creds)).isSameInstanceAs(
290+
mcb);
291+
}
292+
246293
@Test
247294
public void newChannelBuilder_unsupportedSocketAddressTypes() {
248295
NameResolverRegistry nameResolverRegistry = new NameResolverRegistry();

core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java

Lines changed: 84 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ public static ManagedChannelBuilder<?> forTarget(String target) {
156156
private final List<ClientInterceptor> interceptors = new ArrayList<>();
157157
NameResolverRegistry nameResolverRegistry = NameResolverRegistry.getDefaultRegistry();
158158

159+
@Nullable
160+
NameResolverProvider nameResolverProvider;
161+
159162
final List<ClientTransportFilter> transportFilters = new ArrayList<>();
160163

161164
final String target;
@@ -291,18 +294,53 @@ public ManagedChannelImplBuilder(
291294
String target, @Nullable ChannelCredentials channelCreds, @Nullable CallCredentials callCreds,
292295
ClientTransportFactoryBuilder clientTransportFactoryBuilder,
293296
@Nullable ChannelBuilderDefaultPortProvider channelBuilderDefaultPortProvider) {
297+
this(
298+
target,
299+
channelCreds,
300+
callCreds,
301+
clientTransportFactoryBuilder,
302+
channelBuilderDefaultPortProvider,
303+
null,
304+
null);
305+
}
306+
307+
/**
308+
* Creates a new managed channel builder with a target string, which can be
309+
* either a valid {@link io.grpc.NameResolver}-compliant URI, or an authority
310+
* string. Transport
311+
* implementors must provide client transport factory builder, and may set
312+
* custom channel default
313+
* port provider.
314+
*
315+
* @param channelCreds The ChannelCredentials provided by the user.
316+
* These may be used when
317+
* creating derivative channels.
318+
* @param nameResolverRegistry the registry used to look up name resolvers.
319+
* @param nameResolverProvider the provider used to look up name resolvers.
320+
*/
321+
public ManagedChannelImplBuilder(
322+
String target, @Nullable ChannelCredentials channelCreds, @Nullable CallCredentials callCreds,
323+
ClientTransportFactoryBuilder clientTransportFactoryBuilder,
324+
@Nullable ChannelBuilderDefaultPortProvider channelBuilderDefaultPortProvider,
325+
@Nullable NameResolverRegistry nameResolverRegistry,
326+
@Nullable NameResolverProvider nameResolverProvider) {
294327
this.target = checkNotNull(target, "target");
295328
this.channelCredentials = channelCreds;
296329
this.callCredentials = callCreds;
297330
this.clientTransportFactoryBuilder = checkNotNull(clientTransportFactoryBuilder,
298331
"clientTransportFactoryBuilder");
299332
this.directServerAddress = null;
300333

301-
if (channelBuilderDefaultPortProvider != null) {
302-
this.channelBuilderDefaultPortProvider = channelBuilderDefaultPortProvider;
303-
} else {
304-
this.channelBuilderDefaultPortProvider = new ManagedChannelDefaultPortProvider();
305-
}
334+
this.channelBuilderDefaultPortProvider =
335+
channelBuilderDefaultPortProvider != null
336+
? channelBuilderDefaultPortProvider
337+
: new ManagedChannelDefaultPortProvider();
338+
this.nameResolverRegistry =
339+
nameResolverRegistry != null
340+
? nameResolverRegistry
341+
: NameResolverRegistry.getDefaultRegistry();
342+
this.nameResolverProvider = nameResolverProvider;
343+
306344
// TODO(dnvindhya): Move configurator to all the individual builders
307345
InternalConfiguratorRegistry.configureChannelBuilder(this);
308346
}
@@ -422,6 +460,7 @@ public ManagedChannelImplBuilder nameResolverFactory(NameResolver.Factory resolv
422460
Preconditions.checkState(directServerAddress == null,
423461
"directServerAddress is set (%s), which forbids the use of NameResolverFactory",
424462
directServerAddress);
463+
425464
if (resolverFactory != null) {
426465
NameResolverRegistry reg = new NameResolverRegistry();
427466
if (resolverFactory instanceof NameResolverProvider) {
@@ -723,8 +762,8 @@ public ManagedChannel build() {
723762
clientTransportFactoryBuilder.buildClientTransportFactory();
724763
ResolvedNameResolver resolvedResolver =
725764
InternalFeatureFlags.getRfc3986UrisEnabled()
726-
? getNameResolverProviderRfc3986(target, nameResolverRegistry)
727-
: getNameResolverProvider(target, nameResolverRegistry);
765+
? getNameResolverProviderRfc3986(target, nameResolverRegistry, nameResolverProvider)
766+
: getNameResolverProvider(target, nameResolverRegistry, nameResolverProvider);
728767
resolvedResolver.checkAddressTypes(clientTransportFactory.getSupportedSocketAddressTypes());
729768
return new ManagedChannelOrphanWrapper(new ManagedChannelImpl(
730769
this,
@@ -845,7 +884,8 @@ void checkAddressTypes(
845884

846885
@VisibleForTesting
847886
static ResolvedNameResolver getNameResolverProvider(
848-
String target, NameResolverRegistry nameResolverRegistry) {
887+
String target, NameResolverRegistry nameResolverRegistry,
888+
NameResolverProvider nameResolverProvider) {
849889
// Finding a NameResolver. Try using the target string as the URI. If that fails, try prepending
850890
// "dns:///".
851891
NameResolverProvider provider = null;
@@ -860,19 +900,33 @@ static ResolvedNameResolver getNameResolverProvider(
860900
if (targetUri != null) {
861901
// For "localhost:8080" this would likely cause provider to be null, because "localhost" is
862902
// parsed as the scheme. Will hit the next case and try "dns:///localhost:8080".
863-
provider = nameResolverRegistry.getProviderForScheme(targetUri.getScheme());
903+
// Use the explicit provider if its scheme matches the target URI.
904+
if (nameResolverProvider != null
905+
&& targetUri.getScheme().equals(nameResolverProvider.getScheme())) {
906+
provider = nameResolverProvider;
907+
} else {
908+
provider = nameResolverRegistry.getProviderForScheme(targetUri.getScheme());
909+
}
864910
}
865911

866912
if (provider == null && !URI_PATTERN.matcher(target).matches()) {
867-
// It doesn't look like a URI target. Maybe it's an authority string. Try with the default
868-
// scheme from the registry.
913+
// It doesn't look like a URI target. Maybe it's an authority string. Try with
914+
// the default scheme from the registry (if provider is not specified) or
915+
// the provider's default scheme (if provider is specified).
916+
String scheme = nameResolverProvider != null
917+
? nameResolverProvider.getScheme()
918+
: nameResolverRegistry.getDefaultScheme();
869919
try {
870-
targetUri = new URI(nameResolverRegistry.getDefaultScheme(), "", "/" + target, null);
920+
targetUri = new URI(scheme, "", "/" + target, null);
871921
} catch (URISyntaxException e) {
872-
// Should not be possible.
922+
// Should not be possible
873923
throw new IllegalArgumentException(e);
874924
}
875-
provider = nameResolverRegistry.getProviderForScheme(targetUri.getScheme());
925+
if (nameResolverProvider != null) {
926+
provider = nameResolverProvider;
927+
} else {
928+
provider = nameResolverRegistry.getProviderForScheme(targetUri.getScheme());
929+
}
876930
}
877931

878932
if (provider == null) {
@@ -886,7 +940,8 @@ static ResolvedNameResolver getNameResolverProvider(
886940

887941
@VisibleForTesting
888942
static ResolvedNameResolver getNameResolverProviderRfc3986(
889-
String target, NameResolverRegistry nameResolverRegistry) {
943+
String target, NameResolverRegistry nameResolverRegistry,
944+
NameResolverProvider nameResolverProvider) {
890945
// Finding a NameResolver. Try using the target string as the URI. If that fails, try prepending
891946
// "dns:///".
892947
NameResolverProvider provider = null;
@@ -901,15 +956,25 @@ static ResolvedNameResolver getNameResolverProviderRfc3986(
901956
if (targetUri != null) {
902957
// For "localhost:8080" this would likely cause provider to be null, because "localhost" is
903958
// parsed as the scheme. Will hit the next case and try "dns:///localhost:8080".
904-
provider = nameResolverRegistry.getProviderForScheme(targetUri.getScheme());
959+
// Use the explicit provider if its scheme matches the target URI.
960+
if (nameResolverProvider != null
961+
&& targetUri.getScheme().equals(nameResolverProvider.getScheme())) {
962+
provider = nameResolverProvider;
963+
} else {
964+
provider = nameResolverRegistry.getProviderForScheme(targetUri.getScheme());
965+
}
905966
}
906967

907968
if (provider == null && !URI_PATTERN.matcher(target).matches()) {
908-
// It doesn't look like a URI target. Maybe it's an authority string. Try with the default
909-
// scheme from the registry.
969+
// It doesn't look like a URI target. Maybe it's an authority string. Try with
970+
// the default scheme from the registry (if provider is not specified) or
971+
// the provider's default scheme (if provider is specified).
972+
String scheme = nameResolverProvider != null
973+
? nameResolverProvider.getScheme()
974+
: nameResolverRegistry.getDefaultScheme();
910975
targetUri =
911976
Uri.newBuilder()
912-
.setScheme(nameResolverRegistry.getDefaultScheme())
977+
.setScheme(scheme)
913978
.setHost("")
914979
.setPath("/" + target)
915980
.build();

0 commit comments

Comments
 (0)