From e84abc5138d2b0a3f816cb59c57849487204841e Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 20 Jan 2026 18:02:40 -0800 Subject: [PATCH 1/4] Move FlagResetRule from core to api/testFixtures --- .../src/testFixtures/java/io/grpc/testing}/FlagResetRule.java | 2 +- core/src/test/java/io/grpc/internal/DnsNameResolverTest.java | 1 + .../java/io/grpc/internal/ManagedChannelImplBuilderTest.java | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) rename {core/src/testFixtures/java/io/grpc/internal => api/src/testFixtures/java/io/grpc/testing}/FlagResetRule.java (99%) diff --git a/core/src/testFixtures/java/io/grpc/internal/FlagResetRule.java b/api/src/testFixtures/java/io/grpc/testing/FlagResetRule.java similarity index 99% rename from core/src/testFixtures/java/io/grpc/internal/FlagResetRule.java rename to api/src/testFixtures/java/io/grpc/testing/FlagResetRule.java index 96125cd0c8a..820cb43e5a4 100644 --- a/core/src/testFixtures/java/io/grpc/internal/FlagResetRule.java +++ b/api/src/testFixtures/java/io/grpc/testing/FlagResetRule.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.internal; +package io.grpc.testing; import java.util.ArrayDeque; import java.util.Deque; diff --git a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java index c6067d305b5..e63bf82e6cb 100644 --- a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java +++ b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java @@ -60,6 +60,7 @@ import io.grpc.internal.JndiResourceResolverFactory.JndiResourceResolver; import io.grpc.internal.JndiResourceResolverFactory.RecordFetcher; import io.grpc.internal.SharedResourceHolder.Resource; +import io.grpc.testing.FlagResetRule; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplBuilderTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplBuilderTest.java index 8bf10de3949..04a41a6f84d 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplBuilderTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplBuilderTest.java @@ -52,6 +52,7 @@ import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder; import io.grpc.internal.ManagedChannelImplBuilder.FixedPortProvider; import io.grpc.internal.ManagedChannelImplBuilder.UnsupportedClientTransportFactoryBuilder; +import io.grpc.testing.FlagResetRule; import io.grpc.testing.GrpcCleanupRule; import java.net.InetSocketAddress; import java.net.SocketAddress; From 68e038266c8ee89a71bdc17c3d350022a9bd8456 Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 20 Jan 2026 17:39:14 -0800 Subject: [PATCH 2/4] move GrpcUtil.getFlag to api --- api/src/main/java/io/grpc/FeatureFlags.java | 38 +++++++++++++++++++ .../java/io/grpc/InternalFeatureFlags.java | 26 +++++++++++++ .../main/java/io/grpc/internal/GrpcUtil.java | 15 +------- 3 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 api/src/main/java/io/grpc/FeatureFlags.java create mode 100644 api/src/main/java/io/grpc/InternalFeatureFlags.java diff --git a/api/src/main/java/io/grpc/FeatureFlags.java b/api/src/main/java/io/grpc/FeatureFlags.java new file mode 100644 index 00000000000..e406ab9c99f --- /dev/null +++ b/api/src/main/java/io/grpc/FeatureFlags.java @@ -0,0 +1,38 @@ +/* + * Copyright 2026 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc; + +import com.google.common.base.Strings; + +class FeatureFlags { + static boolean getFlag(String envVarName, boolean enableByDefault) { + String envVar = System.getenv(envVarName); + if (envVar == null) { + envVar = System.getProperty(envVarName); + } + if (envVar != null) { + envVar = envVar.trim(); + } + if (enableByDefault) { + return Strings.isNullOrEmpty(envVar) || Boolean.parseBoolean(envVar); + } else { + return !Strings.isNullOrEmpty(envVar) && Boolean.parseBoolean(envVar); + } + } + + private FeatureFlags() {} +} diff --git a/api/src/main/java/io/grpc/InternalFeatureFlags.java b/api/src/main/java/io/grpc/InternalFeatureFlags.java new file mode 100644 index 00000000000..1f4145efc58 --- /dev/null +++ b/api/src/main/java/io/grpc/InternalFeatureFlags.java @@ -0,0 +1,26 @@ +/* + * Copyright 2026 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc; + +@Internal +public class InternalFeatureFlags { + public static boolean getFlag(String envVarName, boolean enableByDefault) { + return FeatureFlags.getFlag(envVarName, enableByDefault); + } + + private InternalFeatureFlags() {} +} diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index 1b5feeccb4a..ab8d8396dee 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -24,7 +24,6 @@ import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.common.base.Stopwatch; -import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -32,6 +31,7 @@ import io.grpc.ClientStreamTracer; import io.grpc.ClientStreamTracer.StreamInfo; import io.grpc.InternalChannelz.SocketStats; +import io.grpc.InternalFeatureFlags; import io.grpc.InternalLogId; import io.grpc.InternalMetadata; import io.grpc.InternalMetadata.TrustedAsciiMarshaller; @@ -958,18 +958,7 @@ public static String encodeAuthority(String authority) { } public static boolean getFlag(String envVarName, boolean enableByDefault) { - String envVar = System.getenv(envVarName); - if (envVar == null) { - envVar = System.getProperty(envVarName); - } - if (envVar != null) { - envVar = envVar.trim(); - } - if (enableByDefault) { - return Strings.isNullOrEmpty(envVar) || Boolean.parseBoolean(envVar); - } else { - return !Strings.isNullOrEmpty(envVar) && Boolean.parseBoolean(envVar); - } + return InternalFeatureFlags.getFlag(envVarName, enableByDefault); } From 41ae217fb4cafe4390c1e6ea1781886c5f661072 Mon Sep 17 00:00:00 2001 From: John Cormie Date: Wed, 14 Jan 2026 11:18:24 -0800 Subject: [PATCH 3/4] Move RFC 3986 feature flag to 'api' --- api/src/main/java/io/grpc/FeatureFlags.java | 16 ++++++++++++++++ .../main/java/io/grpc/InternalFeatureFlags.java | 15 +++++++++++++++ .../grpc/internal/ManagedChannelImplBuilder.java | 13 ++----------- .../internal/ManagedChannelImplBuilderTest.java | 3 ++- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/io/grpc/FeatureFlags.java b/api/src/main/java/io/grpc/FeatureFlags.java index e406ab9c99f..0e414ed7b31 100644 --- a/api/src/main/java/io/grpc/FeatureFlags.java +++ b/api/src/main/java/io/grpc/FeatureFlags.java @@ -16,9 +16,25 @@ package io.grpc; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; class FeatureFlags { + private static boolean enableRfc3986Uris = getFlag("GRPC_ENABLE_RFC3986_URIS", false); + + /** Whether to parse targets as RFC 3986 URIs (true), or use {@link java.net.URI} (false). */ + @VisibleForTesting + static boolean setRfc3986UrisEnabled(boolean value) { + boolean prevValue = enableRfc3986Uris; + enableRfc3986Uris = value; + return prevValue; + } + + /** Whether to parse targets as RFC 3986 URIs (true), or use {@link java.net.URI} (false). */ + static boolean getRfc3986UrisEnabled() { + return enableRfc3986Uris; + } + static boolean getFlag(String envVarName, boolean enableByDefault) { String envVar = System.getenv(envVarName); if (envVar == null) { diff --git a/api/src/main/java/io/grpc/InternalFeatureFlags.java b/api/src/main/java/io/grpc/InternalFeatureFlags.java index 1f4145efc58..a1e771a7571 100644 --- a/api/src/main/java/io/grpc/InternalFeatureFlags.java +++ b/api/src/main/java/io/grpc/InternalFeatureFlags.java @@ -16,8 +16,23 @@ package io.grpc; +import com.google.common.annotations.VisibleForTesting; + +/** Global variables that govern major changes to the behavior of more than one grpc module. */ @Internal public class InternalFeatureFlags { + + /** Whether to parse targets as RFC 3986 URIs (true), or use {@link java.net.URI} (false). */ + @VisibleForTesting + public static boolean setRfc3986UrisEnabled(boolean value) { + return FeatureFlags.setRfc3986UrisEnabled(value); + } + + /** Whether to parse targets as RFC 3986 URIs (true), or use {@link java.net.URI} (false). */ + public static boolean getRfc3986UrisEnabled() { + return FeatureFlags.getRfc3986UrisEnabled(); + } + public static boolean getFlag(String envVarName, boolean enableByDefault) { return FeatureFlags.getFlag(envVarName, enableByDefault); } diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java b/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java index 628224b826e..128c929ec0e 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java @@ -38,6 +38,7 @@ import io.grpc.EquivalentAddressGroup; import io.grpc.InternalChannelz; import io.grpc.InternalConfiguratorRegistry; +import io.grpc.InternalFeatureFlags; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.MethodDescriptor; @@ -106,16 +107,6 @@ public static ManagedChannelBuilder forTarget(String target) { */ static final long IDLE_MODE_MIN_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1); - private static boolean enableRfc3986Uris = GrpcUtil.getFlag("GRPC_ENABLE_RFC3986_URIS", false); - - /** Whether to parse targets as RFC 3986 URIs (true), or use {@link java.net.URI} (false). */ - @VisibleForTesting - static boolean setRfc3986UrisEnabled(boolean value) { - boolean prevValue = ManagedChannelImplBuilder.enableRfc3986Uris; - ManagedChannelImplBuilder.enableRfc3986Uris = value; - return prevValue; - } - private static final ObjectPool DEFAULT_EXECUTOR_POOL = SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR); @@ -731,7 +722,7 @@ public ManagedChannel build() { ClientTransportFactory clientTransportFactory = clientTransportFactoryBuilder.buildClientTransportFactory(); ResolvedNameResolver resolvedResolver = - enableRfc3986Uris + InternalFeatureFlags.getRfc3986UrisEnabled() ? getNameResolverProviderRfc3986(target, nameResolverRegistry) : getNameResolverProvider(target, nameResolverRegistry); resolvedResolver.checkAddressTypes(clientTransportFactory.getSupportedSocketAddressTypes()); diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplBuilderTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplBuilderTest.java index 04a41a6f84d..f92838e96df 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplBuilderTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplBuilderTest.java @@ -40,6 +40,7 @@ import io.grpc.DecompressorRegistry; import io.grpc.InternalConfigurator; import io.grpc.InternalConfiguratorRegistry; +import io.grpc.InternalFeatureFlags; import io.grpc.InternalManagedChannelBuilder.InternalInterceptorFactory; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; @@ -129,7 +130,7 @@ public static Iterable data() { @Before public void setUp() throws Exception { flagResetRule.setFlagForTest( - ManagedChannelImplBuilder::setRfc3986UrisEnabled, enableRfc3986UrisParam); + InternalFeatureFlags::setRfc3986UrisEnabled, enableRfc3986UrisParam); builder = new ManagedChannelImplBuilder( DUMMY_TARGET, From 0d8258edbb1b188af2ef62d558c02bc295413d92 Mon Sep 17 00:00:00 2001 From: John Cormie Date: Thu, 15 Jan 2026 15:54:50 -0800 Subject: [PATCH 4/4] api: Use io.grpc.Uri for target parsing in ManagedChannelRegistry --- .../java/io/grpc/ManagedChannelRegistry.java | 7 ++++-- .../io/grpc/ManagedChannelRegistryTest.java | 24 +++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/io/grpc/ManagedChannelRegistry.java b/api/src/main/java/io/grpc/ManagedChannelRegistry.java index aed5eca9abf..2e794295c2e 100644 --- a/api/src/main/java/io/grpc/ManagedChannelRegistry.java +++ b/api/src/main/java/io/grpc/ManagedChannelRegistry.java @@ -160,8 +160,11 @@ ManagedChannelBuilder newChannelBuilder(NameResolverRegistry nameResolverRegi String target, ChannelCredentials creds) { NameResolverProvider nameResolverProvider = null; try { - URI uri = new URI(target); - nameResolverProvider = nameResolverRegistry.getProviderForScheme(uri.getScheme()); + String scheme = + FeatureFlags.getRfc3986UrisEnabled() + ? Uri.parse(target).getScheme() + : new URI(target).getScheme(); + nameResolverProvider = nameResolverRegistry.getProviderForScheme(scheme); } catch (URISyntaxException ignore) { // bad URI found, just ignore and continue } diff --git a/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java b/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java index 30de2477d77..f954178b6dd 100644 --- a/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java +++ b/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java @@ -20,17 +20,23 @@ import static org.junit.Assert.fail; import com.google.common.collect.ImmutableSet; +import io.grpc.testing.FlagResetRule; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; /** Unit tests for {@link ManagedChannelRegistry}. */ -@RunWith(JUnit4.class) +@RunWith(Parameterized.class) public class ManagedChannelRegistryTest { private String target = "testing123"; private ChannelCredentials creds = new ChannelCredentials() { @@ -40,6 +46,20 @@ public ChannelCredentials withoutBearerTokens() { } }; + @Rule public final FlagResetRule flagResetRule = new FlagResetRule(); + + @Parameters(name = "enableRfc3986UrisParam={0}") + public static Iterable data() { + return Arrays.asList(new Object[][] {{true}, {false}}); + } + + @Parameter public boolean enableRfc3986UrisParam; + + @Before + public void setUp() { + flagResetRule.setFlagForTest(FeatureFlags::setRfc3986UrisEnabled, enableRfc3986UrisParam); + } + @Test public void register_unavailableProviderThrows() { ManagedChannelRegistry reg = new ManagedChannelRegistry();