From 390ef65bf23c9843c406bbf8754658ba1c9c1199 Mon Sep 17 00:00:00 2001 From: Mark Brophy Date: Tue, 26 Oct 2021 12:23:16 +0100 Subject: [PATCH 1/3] Support BinderChannelBuilder.forTarget. Allows this class to be used with custom name resolvers. --- .../grpc/binder/BinderChannelSmokeTest.java | 11 +- .../io/grpc/binder/BinderChannelBuilder.java | 56 +++++++-- .../testing/FakeNameResolverProvider.java | 106 ++++++++++++++++++ 3 files changed, 160 insertions(+), 13 deletions(-) create mode 100644 testing/src/main/java/io/grpc/testing/FakeNameResolverProvider.java diff --git a/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java b/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java index dd9cf26bd3d..99ffe2bd078 100644 --- a/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java +++ b/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java @@ -44,6 +44,7 @@ import io.grpc.stub.ClientCalls; import io.grpc.stub.ServerCalls; import io.grpc.stub.StreamObserver; +import io.grpc.testing.FakeNameResolverProvider; import io.grpc.testing.TestUtils; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -117,7 +118,7 @@ public void setUp() throws Exception { .build(), TestUtils.recordRequestHeadersInterceptor(headersCapture)); - AndroidComponentAddress serverAddress = HostServices.allocateService(appContext); + serverAddress = HostServices.allocateService(appContext); HostServices.configureService(serverAddress, HostServices.serviceParamsBuilder() .setServerFactory((service, receiver) -> @@ -192,6 +193,14 @@ public void testStreamingCallOptionHeaders() throws Exception { assertThat(headersCapture.get().get(GrpcUtil.TIMEOUT_KEY)).isGreaterThan(0); } + @Test + public void testConnectViaTargetUri() throws Exception { + String targetUri = "fake://server"; + FakeNameResolverProvider.register(targetUri, serverAddress); + channel = BinderChannelBuilder.forTarget(targetUri, appContext).build(); + assertThat(doCall("Hello").get()).isEqualTo("Hello"); + } + private static String createLargeString(int size) { StringBuilder sb = new StringBuilder(); while (sb.length() < size) { diff --git a/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java b/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java index 99191cfad3c..91e4e8f1c76 100644 --- a/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java +++ b/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java @@ -67,13 +67,35 @@ public final class BinderChannelBuilder *

You the caller are responsible for managing the lifecycle of any channels built by the * resulting builder. They will not be shut down automatically. * - * @param targetAddress the {@link AndroidComponentAddress} referencing the service to bind to. + * @param directAddress the {@link AndroidComponentAddress} referencing the service to bind to. * @param sourceContext the context to bind from (e.g. The current Activity or Application). * @return a new builder */ public static BinderChannelBuilder forAddress( - AndroidComponentAddress targetAddress, Context sourceContext) { - return new BinderChannelBuilder(targetAddress, sourceContext); + AndroidComponentAddress directAddress, Context sourceContext) { + return new BinderChannelBuilder( + checkNotNull(directAddress, "directAddress"), null, sourceContext); + } + + /** + * Creates a channel builder that will bind to a remote Android service, via a string + * target name which will be resolved. + * + *

The underlying Android binding will be torn down when the channel becomes idle. This happens + * after 30 minutes without use by default but can be configured via {@link + * ManagedChannelBuilder#idleTimeout(long, TimeUnit)} or triggered manually with {@link + * ManagedChannel#enterIdle()}. + * + *

You the caller are responsible for managing the lifecycle of any channels built by the + * resulting builder. They will not be shut down automatically. + * + * @param target A target uri which should resolve into an {@link AndroidComponentAddress} + * referencing the service to bind to. + * @param sourceContext the context to bind from (e.g. The current Activity or Application). + * @return a new builder + */ + public static BinderChannelBuilder forTarget(String target, Context sourceContext) { + return new BinderChannelBuilder(null, checkNotNull(target, "target"), sourceContext); } /** @@ -88,7 +110,7 @@ public static BinderChannelBuilder forAddress(String name, int port) { /** * Always fails. Call {@link #forAddress(AndroidComponentAddress, Context)} instead. */ - @DoNotCall("Unsupported. Use forAddress(AndroidComponentAddress, Context) instead") + @DoNotCall("Unsupported. Use forTarget(String, Context) instead") public static BinderChannelBuilder forTarget(String target) { throw new UnsupportedOperationException( "call forAddress(AndroidComponentAddress, Context) instead"); @@ -104,9 +126,11 @@ public static BinderChannelBuilder forTarget(String target) { private BindServiceFlags bindServiceFlags; private BinderChannelBuilder( - AndroidComponentAddress targetAddress, + @Nullable AndroidComponentAddress directAddress, + @Nullable String target, Context sourceContext) { - mainThreadExecutor = ContextCompat.getMainExecutor(sourceContext); + mainThreadExecutor = + ContextCompat.getMainExecutor(checkNotNull(sourceContext, "sourceContext")); securityPolicy = SecurityPolicies.internalOnly(); inboundParcelablePolicy = InboundParcelablePolicy.DEFAULT; bindServiceFlags = BindServiceFlags.DEFAULTS; @@ -126,12 +150,20 @@ public ClientTransportFactory buildClientTransportFactory() { } } - managedChannelImplBuilder = - new ManagedChannelImplBuilder( - targetAddress, - targetAddress.getAuthority(), - new BinderChannelTransportFactoryBuilder(), - null); + if (directAddress != null) { + managedChannelImplBuilder = + new ManagedChannelImplBuilder( + directAddress, + directAddress.getAuthority(), + new BinderChannelTransportFactoryBuilder(), + null); + } else { + managedChannelImplBuilder = + new ManagedChannelImplBuilder( + target, + new BinderChannelTransportFactoryBuilder(), + null); + } } @Override diff --git a/testing/src/main/java/io/grpc/testing/FakeNameResolverProvider.java b/testing/src/main/java/io/grpc/testing/FakeNameResolverProvider.java new file mode 100644 index 00000000000..9cda1366450 --- /dev/null +++ b/testing/src/main/java/io/grpc/testing/FakeNameResolverProvider.java @@ -0,0 +1,106 @@ +/* + * Copyright 2021 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.testing; + +import com.google.common.collect.ImmutableList; +import io.grpc.EquivalentAddressGroup; +import io.grpc.NameResolver; +import io.grpc.NameResolverProvider; +import io.grpc.NameResolverRegistry; +import io.grpc.Status; +import java.net.SocketAddress; +import java.net.URI; + +/** A name resolver to always resolve the given URI into the given address. */ +public final class FakeNameResolverProvider extends NameResolverProvider { + + /** + * Register a new resolver. + * + * @param targetUri The URI to resolve when requested. + * @param address The address to return for the target URI. + */ + public static final void register(String targetUri, SocketAddress address) { + NameResolverRegistry.getDefaultRegistry().register( + new FakeNameResolverProvider(URI.create(targetUri), address)); + } + + private final URI targetUri; + private final SocketAddress address; + + private FakeNameResolverProvider(URI targetUri, SocketAddress address) { + this.targetUri = targetUri; + this.address = address; + } + + @Override + public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { + if (targetUri.equals(this.targetUri)) { + return new FakeNameResolver(address); + } + return null; + } + + @Override + protected boolean isAvailable() { + return true; + } + + @Override + protected int priority() { + return 5; // Default + } + + @Override + public String getDefaultScheme() { + return targetUri.getScheme(); + } + + /** A single name resolver. */ + private static final class FakeNameResolver extends NameResolver { + private static final String AUTHORITY = "fake-authority"; + + private final SocketAddress address; + private volatile boolean shutdown; + + private FakeNameResolver(SocketAddress address) { + this.address = address; + } + + @Override + public void start(Listener2 listener) { + if (shutdown) { + listener.onError(Status.FAILED_PRECONDITION.withDescription("Resolver is shutdown")); + } else { + listener.onResult( + ResolutionResult.newBuilder() + .setAddresses(ImmutableList.of(new EquivalentAddressGroup(address))) + .build()); + } + } + + @Override + public String getServiceAuthority() { + return AUTHORITY; + } + + @Override + public void shutdown() { + shutdown = true; + } + } +} From aa5660d46d4e6aaa417f06166e6564d052599eec Mon Sep 17 00:00:00 2001 From: Mark Brophy Date: Fri, 29 Oct 2021 09:49:32 +0100 Subject: [PATCH 2/3] Add ExperimentalApi annotation. --- .../src/main/java/io/grpc/testing/FakeNameResolverProvider.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testing/src/main/java/io/grpc/testing/FakeNameResolverProvider.java b/testing/src/main/java/io/grpc/testing/FakeNameResolverProvider.java index 9cda1366450..82aa3f41efd 100644 --- a/testing/src/main/java/io/grpc/testing/FakeNameResolverProvider.java +++ b/testing/src/main/java/io/grpc/testing/FakeNameResolverProvider.java @@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableList; import io.grpc.EquivalentAddressGroup; +import io.grpc.ExperimentalApi; import io.grpc.NameResolver; import io.grpc.NameResolverProvider; import io.grpc.NameResolverRegistry; @@ -26,6 +27,7 @@ import java.net.URI; /** A name resolver to always resolve the given URI into the given address. */ +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8636") public final class FakeNameResolverProvider extends NameResolverProvider { /** From fdfb50e470a86a489315b6a22f42078f4f6738ca Mon Sep 17 00:00:00 2001 From: Mark Brophy Date: Mon, 1 Nov 2021 11:02:34 +0000 Subject: [PATCH 3/3] Address review comments. --- .../grpc/binder/BinderChannelSmokeTest.java | 15 ++++++++------ .../testing/FakeNameResolverProvider.java | 20 +++---------------- 2 files changed, 12 insertions(+), 23 deletions(-) rename testing/src/main/java/io/grpc/{ => internal}/testing/FakeNameResolverProvider.java (78%) diff --git a/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java b/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java index 99ffe2bd078..41ea76146de 100644 --- a/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java +++ b/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java @@ -36,15 +36,16 @@ import io.grpc.ManagedChannel; import io.grpc.Metadata; import io.grpc.MethodDescriptor; +import io.grpc.NameResolverRegistry; import io.grpc.Server; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptors; import io.grpc.ServerServiceDefinition; import io.grpc.internal.GrpcUtil; +import io.grpc.internal.testing.FakeNameResolverProvider; import io.grpc.stub.ClientCalls; import io.grpc.stub.ServerCalls; import io.grpc.stub.StreamObserver; -import io.grpc.testing.FakeNameResolverProvider; import io.grpc.testing.TestUtils; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -67,6 +68,7 @@ public final class BinderChannelSmokeTest { private static final int SLIGHTLY_MORE_THAN_ONE_BLOCK = 16 * 1024 + 100; private static final String MSG = "Some text which will be repeated many many times"; + private static final String SERVER_TARGET_URI = "fake://server"; final MethodDescriptor method = MethodDescriptor.newBuilder(StringMarshaller.INSTANCE, StringMarshaller.INSTANCE) @@ -86,7 +88,7 @@ public final class BinderChannelSmokeTest { .setType(MethodDescriptor.MethodType.BIDI_STREAMING) .build(); - AndroidComponentAddress serverAddress; + FakeNameResolverProvider fakeNameResolverProvider; ManagedChannel channel; AtomicReference headersCapture = new AtomicReference<>(); @@ -118,7 +120,9 @@ public void setUp() throws Exception { .build(), TestUtils.recordRequestHeadersInterceptor(headersCapture)); - serverAddress = HostServices.allocateService(appContext); + AndroidComponentAddress serverAddress = HostServices.allocateService(appContext); + fakeNameResolverProvider = new FakeNameResolverProvider(SERVER_TARGET_URI, serverAddress); + NameResolverRegistry.getDefaultRegistry().register(fakeNameResolverProvider); HostServices.configureService(serverAddress, HostServices.serviceParamsBuilder() .setServerFactory((service, receiver) -> @@ -133,6 +137,7 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { channel.shutdownNow(); + NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverProvider); HostServices.awaitServiceShutdown(); } @@ -195,9 +200,7 @@ public void testStreamingCallOptionHeaders() throws Exception { @Test public void testConnectViaTargetUri() throws Exception { - String targetUri = "fake://server"; - FakeNameResolverProvider.register(targetUri, serverAddress); - channel = BinderChannelBuilder.forTarget(targetUri, appContext).build(); + channel = BinderChannelBuilder.forTarget(SERVER_TARGET_URI, appContext).build(); assertThat(doCall("Hello").get()).isEqualTo("Hello"); } diff --git a/testing/src/main/java/io/grpc/testing/FakeNameResolverProvider.java b/testing/src/main/java/io/grpc/internal/testing/FakeNameResolverProvider.java similarity index 78% rename from testing/src/main/java/io/grpc/testing/FakeNameResolverProvider.java rename to testing/src/main/java/io/grpc/internal/testing/FakeNameResolverProvider.java index 82aa3f41efd..d056707b719 100644 --- a/testing/src/main/java/io/grpc/testing/FakeNameResolverProvider.java +++ b/testing/src/main/java/io/grpc/internal/testing/FakeNameResolverProvider.java @@ -14,38 +14,24 @@ * limitations under the License. */ -package io.grpc.testing; +package io.grpc.internal.testing; import com.google.common.collect.ImmutableList; import io.grpc.EquivalentAddressGroup; -import io.grpc.ExperimentalApi; import io.grpc.NameResolver; import io.grpc.NameResolverProvider; -import io.grpc.NameResolverRegistry; import io.grpc.Status; import java.net.SocketAddress; import java.net.URI; /** A name resolver to always resolve the given URI into the given address. */ -@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8636") public final class FakeNameResolverProvider extends NameResolverProvider { - /** - * Register a new resolver. - * - * @param targetUri The URI to resolve when requested. - * @param address The address to return for the target URI. - */ - public static final void register(String targetUri, SocketAddress address) { - NameResolverRegistry.getDefaultRegistry().register( - new FakeNameResolverProvider(URI.create(targetUri), address)); - } - private final URI targetUri; private final SocketAddress address; - private FakeNameResolverProvider(URI targetUri, SocketAddress address) { - this.targetUri = targetUri; + public FakeNameResolverProvider(String targetUri, SocketAddress address) { + this.targetUri = URI.create(targetUri); this.address = address; }