From 6559f963a77b33aaa712aa948cb2cec414173421 Mon Sep 17 00:00:00 2001 From: Santiago Pericas-Geertsen Date: Tue, 19 Mar 2024 16:52:07 -0400 Subject: [PATCH] Basic support for client interceptors with service stubs --- .../io/helidon/webclient/grpc/GrpcClient.java | 20 ++++++ .../webclient/grpc/GrpcClientImpl.java | 7 ++ webclient/tests/grpc/pom.xml | 2 +- .../webclient/grpc/tests/GrpcBaseTest.java | 60 ++++++++++++++++- .../grpc/tests/GrpcInterceptorStubTest.java | 67 +++++++++++++++++++ .../grpc/tests/GrpcInterceptorTest.java | 42 +----------- .../webclient/grpc/tests/GrpcTest.java | 1 + 7 files changed, 155 insertions(+), 44 deletions(-) create mode 100644 webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcInterceptorStubTest.java diff --git a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClient.java b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClient.java index c7b1e1795bc..6fb03c4d040 100644 --- a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClient.java +++ b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClient.java @@ -16,6 +16,7 @@ package io.helidon.webclient.grpc; +import java.util.Collection; import java.util.function.Consumer; import io.helidon.builder.api.RuntimeType; @@ -23,6 +24,7 @@ import io.helidon.webclient.spi.Protocol; import io.grpc.Channel; +import io.grpc.ClientInterceptor; /** * gRPC client. @@ -94,4 +96,22 @@ static GrpcClient create() { * @return a new gRPC channel */ Channel channel(); + + /** + * Create a gRPC channel for this client that can be used to create stubs. + * + * @param interceptors the array of client interceptors + * @return a new gRPC channel + */ + Channel channel(ClientInterceptor... interceptors); + + /** + * Create a gRPC channel for this client that can be used to create stubs. + * + * @param interceptors the list of client interceptors + * @return a new gRPC channel + */ + default Channel channel(Collection interceptors) { + return channel(interceptors.toArray(new ClientInterceptor[] {})); + } } diff --git a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClientImpl.java b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClientImpl.java index ec66b7cc28a..a30d01828ab 100644 --- a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClientImpl.java +++ b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClientImpl.java @@ -20,6 +20,8 @@ import io.helidon.webclient.http2.Http2Client; import io.grpc.Channel; +import io.grpc.ClientInterceptor; +import io.grpc.ClientInterceptors; class GrpcClientImpl implements GrpcClient { private final WebClient webClient; @@ -54,4 +56,9 @@ public GrpcServiceClient serviceClient(GrpcServiceDescriptor descriptor) { public Channel channel() { return new GrpcChannel(this); } + + @Override + public Channel channel(ClientInterceptor... interceptors) { + return ClientInterceptors.intercept(channel(), interceptors); + } } diff --git a/webclient/tests/grpc/pom.xml b/webclient/tests/grpc/pom.xml index 34d4c34cf32..a9fef9a5d7d 100644 --- a/webclient/tests/grpc/pom.xml +++ b/webclient/tests/grpc/pom.xml @@ -137,7 +137,7 @@ - com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier} + com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier} grpc-java io.grpc:protoc-gen-grpc-java:${version.lib.grpc}:exe:${os.detected.classifier} diff --git a/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcBaseTest.java b/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcBaseTest.java index 234d60a7d37..705876fbde9 100644 --- a/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcBaseTest.java +++ b/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcBaseTest.java @@ -20,17 +20,30 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.MethodDescriptor; +import io.grpc.stub.StreamObserver; +import io.helidon.common.Weight; import io.helidon.common.configurable.Resource; import io.helidon.webserver.WebServerConfig; import io.helidon.webserver.grpc.GrpcRouting; import io.helidon.webserver.testing.junit5.SetUpRoute; import io.helidon.webserver.testing.junit5.SetUpServer; - -import io.grpc.stub.StreamObserver; +import org.junit.jupiter.api.BeforeEach; class GrpcBaseTest { + private final List> calledInterceptors = new CopyOnWriteArrayList<>(); + + protected List> calledInterceptors() { + return calledInterceptors; + } + @SetUpServer public static void setup(WebServerConfig.Builder builder) { builder.tls(tls -> tls.privateKey(key -> key @@ -64,6 +77,11 @@ static void setUpRoute(GrpcRouting.Builder routing) { GrpcStubTest::echo); } + @BeforeEach + void setUpTest() { + calledInterceptors.clear(); + } + static void upper(Strings.StringMessage req, StreamObserver streamObserver) { Strings.StringMessage msg = Strings.StringMessage.newBuilder() @@ -178,4 +196,42 @@ public void onCompleted() { } }; } + + class BaseInterceptor implements ClientInterceptor { + @Override + public ClientCall interceptCall(MethodDescriptor method, + CallOptions callOptions, + Channel next) { + calledInterceptors.add(getClass()); + return next.newCall(method, callOptions); + } + } + + @Weight(10.0) + class Weight10Interceptor extends BaseInterceptor { + } + + @Weight(50.0) + class Weight50Interceptor extends BaseInterceptor { + } + + @Weight(100.0) + class Weight100Interceptor extends BaseInterceptor { + } + + @Weight(500.0) + class Weight500Interceptor extends BaseInterceptor { + } + + @Weight(1000.0) + class Weight1000Interceptor extends BaseInterceptor { + } + + List allInterceptors() { + return List.of(new Weight10Interceptor(), + new Weight50Interceptor(), + new Weight100Interceptor(), + new Weight500Interceptor(), + new Weight1000Interceptor()); + } } diff --git a/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcInterceptorStubTest.java b/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcInterceptorStubTest.java new file mode 100644 index 00000000000..c602c488349 --- /dev/null +++ b/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcInterceptorStubTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * 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.helidon.webclient.grpc.tests; + +import io.grpc.Channel; +import io.helidon.common.configurable.Resource; +import io.helidon.common.tls.Tls; +import io.helidon.webclient.api.WebClient; +import io.helidon.webclient.grpc.GrpcClient; +import io.helidon.webserver.WebServer; +import io.helidon.webserver.testing.junit5.ServerTest; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + +/** + * Tests gRPC client using stubs and TLS. + */ +@ServerTest +class GrpcInterceptorStubTest extends GrpcBaseTest { + + private final WebClient webClient; + + private GrpcInterceptorStubTest(WebServer server) { + Tls clientTls = Tls.builder() + .trust(trust -> trust + .keystore(store -> store + .passphrase("password") + .trustStore(true) + .keystore(Resource.create("client.p12")))) + .build(); + this.webClient = WebClient.builder() + .tls(clientTls) + .baseUri("https://localhost:" + server.port()) + .build(); + } + + @Test + void testUnaryUpper() { + GrpcClient grpcClient = webClient.client(GrpcClient.PROTOCOL); + Channel channel = grpcClient.channel(allInterceptors()); // channel with all interceptors + StringServiceGrpc.StringServiceBlockingStub service = StringServiceGrpc.newBlockingStub(channel); + Strings.StringMessage res = service.upper(newStringMessage("hello")); + assertThat(res.getText(), is("HELLO")); + assertThat(calledInterceptors(), contains(Weight1000Interceptor.class, + Weight500Interceptor.class, + Weight100Interceptor.class, + Weight50Interceptor.class, + Weight10Interceptor.class)); + } +} diff --git a/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcInterceptorTest.java b/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcInterceptorTest.java index 692b405ce11..6f279e5e8fd 100644 --- a/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcInterceptorTest.java +++ b/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcInterceptorTest.java @@ -16,15 +16,6 @@ package io.helidon.webclient.grpc.tests; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.ClientInterceptor; -import io.grpc.MethodDescriptor; -import io.helidon.common.Weight; import io.helidon.common.configurable.Resource; import io.helidon.common.tls.Tls; import io.helidon.webclient.grpc.GrpcClient; @@ -46,7 +37,6 @@ class GrpcInterceptorTest extends GrpcBaseTest { private final GrpcClient grpcClient; private final GrpcServiceDescriptor serviceDescriptor; - private final List> calledInterceptors = new CopyOnWriteArrayList<>(); private GrpcInterceptorTest(WebServer server) { Tls clientTls = Tls.builder() @@ -80,40 +70,10 @@ void testUnaryUpper() { Strings.StringMessage res = grpcClient.serviceClient(serviceDescriptor) .unary("Upper", newStringMessage("hello")); assertThat(res.getText(), is("HELLO")); - assertThat(calledInterceptors, contains(Weight1000Interceptor.class, + assertThat(calledInterceptors(), contains(Weight1000Interceptor.class, Weight500Interceptor.class, Weight100Interceptor.class, Weight50Interceptor.class, Weight10Interceptor.class)); } - - class BaseInterceptor implements ClientInterceptor { - @Override - public ClientCall interceptCall(MethodDescriptor method, - CallOptions callOptions, - Channel next) { - calledInterceptors.add(getClass()); - return next.newCall(method, callOptions); - } - } - - @Weight(10.0) - class Weight10Interceptor extends BaseInterceptor { - } - - @Weight(50.0) - class Weight50Interceptor extends BaseInterceptor { - } - - @Weight(100.0) - class Weight100Interceptor extends BaseInterceptor { - } - - @Weight(500.0) - class Weight500Interceptor extends BaseInterceptor { - } - - @Weight(1000.0) - class Weight1000Interceptor extends BaseInterceptor { - } } diff --git a/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcTest.java b/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcTest.java index ef933137376..c3a7c67666b 100644 --- a/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcTest.java +++ b/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcTest.java @@ -47,6 +47,7 @@ class GrpcTest extends GrpcBaseTest { private static final long TIMEOUT_SECONDS = 10; private final GrpcClient grpcClient; + private final GrpcServiceDescriptor serviceDescriptor; private GrpcTest(WebServer server) {