From 5cc7a6171cc8d77c9a713533e04afbf2bd19e6d6 Mon Sep 17 00:00:00 2001 From: Santiago Pericas-Geertsen Date: Tue, 29 Oct 2024 12:46:47 -0400 Subject: [PATCH] New gRPC SE example that implements a random number generator service. Example created for blog article. --- examples/webserver/grpc-random/README.md | 21 +++ examples/webserver/grpc-random/pom.xml | 139 ++++++++++++++++++ .../examples/webserver/grpc/random/Main.java | 49 ++++++ .../webserver/grpc/random/RandomService.java | 91 ++++++++++++ .../webserver/grpc/random/package-info.java | 20 +++ .../grpc-random/src/main/proto/random.proto | 32 ++++ .../src/main/resources/application.yaml | 30 ++++ .../grpc-random/src/main/resources/client.p12 | Bin 0 -> 4181 bytes .../src/main/resources/logging.properties | 24 +++ .../grpc-random/src/main/resources/server.p12 | Bin 0 -> 4133 bytes .../grpc/random/RandomServiceTest.java | 128 ++++++++++++++++ .../src/test/resources/application.yaml | 30 ++++ examples/webserver/pom.xml | 1 + 13 files changed, 565 insertions(+) create mode 100644 examples/webserver/grpc-random/README.md create mode 100644 examples/webserver/grpc-random/pom.xml create mode 100644 examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/Main.java create mode 100644 examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/RandomService.java create mode 100644 examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/package-info.java create mode 100644 examples/webserver/grpc-random/src/main/proto/random.proto create mode 100644 examples/webserver/grpc-random/src/main/resources/application.yaml create mode 100644 examples/webserver/grpc-random/src/main/resources/client.p12 create mode 100644 examples/webserver/grpc-random/src/main/resources/logging.properties create mode 100644 examples/webserver/grpc-random/src/main/resources/server.p12 create mode 100644 examples/webserver/grpc-random/src/test/java/io/helidon/examples/webserver/grpc/random/RandomServiceTest.java create mode 100644 examples/webserver/grpc-random/src/test/resources/application.yaml diff --git a/examples/webserver/grpc-random/README.md b/examples/webserver/grpc-random/README.md new file mode 100644 index 000000000..f7bee41f4 --- /dev/null +++ b/examples/webserver/grpc-random/README.md @@ -0,0 +1,21 @@ +# Helidon gRPC SE Randome Example + + +This example shows a simple _Random_ service written with the Helidon gRPC SE +API. See `RandomService` for the service implementation and `RandomServiceTest` +for how to use the Helidon's `WebClient` to access the service. . + +The gRPC service definition is found in the `random.proto` file which is compiled +using `protoc` at build time. + +## Build and run tests + +```shell +mvn package +``` + +## Run the app + +```shell +java -jar target/helidon-examples-webserver-grpc-random.jar +``` diff --git a/examples/webserver/grpc-random/pom.xml b/examples/webserver/grpc-random/pom.xml new file mode 100644 index 000000000..312c01499 --- /dev/null +++ b/examples/webserver/grpc-random/pom.xml @@ -0,0 +1,139 @@ + + + + 4.0.0 + + io.helidon.applications + helidon-se + 4.2.0-SNAPSHOT + + + + io.helidon.examples.webserver + helidon-examples-webserver-grpc-random + 1.0.0-SNAPSHOT + Helidon Examples WebServer gRPC Random + + + Application demonstrates the use of gRPC with a service that returns random numbers. + + + + io.helidon.examples.webserver.grpc.random.Main + + + + + io.grpc + grpc-api + + + io.helidon.config + helidon-config-yaml + + + io.helidon.grpc + helidon-grpc-core + + + io.helidon.webserver + helidon-webserver-grpc + + + io.helidon.logging + helidon-logging-jul + + + com.google.protobuf + protobuf-java + + + io.helidon.webclient + helidon-webclient-grpc + + + + javax.annotation + javax.annotation-api + provided + + + io.helidon.webserver.testing.junit5 + helidon-webserver-testing-junit5 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + io.helidon.logging + helidon-logging-jul + test + + + + + + + kr.motd.maven + os-maven-plugin + ${version.plugin.os} + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + + compile + compile-custom + + + + + com.google.protobuf:protoc:${version.lib.google-protobuf}:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${version.lib.grpc}:exe:${os.detected.classifier} + + + + + + + \ No newline at end of file diff --git a/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/Main.java b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/Main.java new file mode 100644 index 000000000..81376ada6 --- /dev/null +++ b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/Main.java @@ -0,0 +1,49 @@ +/* + * 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.examples.webserver.grpc.random; + +import io.helidon.config.Config; +import io.helidon.logging.common.LogConfig; +import io.helidon.webserver.WebServer; +import io.helidon.webserver.grpc.GrpcRouting; + +class Main { + + private Main() { + } + + /** + * Main method. + * + * @param args ignored + */ + public static void main(String[] args) { + LogConfig.configureRuntime(); + + // initialize global config from default configuration + Config config = Config.create(); + Config.global(config); + Config serverConfig = config.get("server"); + + // start server and register gRPC routing and health check + WebServer.builder() + .config(serverConfig) + .addRouting(GrpcRouting.builder().service(new RandomService())) + .build() + .start(); + } +} diff --git a/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/RandomService.java b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/RandomService.java new file mode 100644 index 000000000..973f62867 --- /dev/null +++ b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/RandomService.java @@ -0,0 +1,91 @@ +/* + * 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.examples.webserver.grpc.random; + +import java.util.Random; + +import io.helidon.examples.webserver.grpc.random.Random.ParamMessage; +import io.helidon.examples.webserver.grpc.random.Random.RandomMessage; +import io.helidon.webserver.grpc.GrpcService; + +import com.google.protobuf.Descriptors; +import io.grpc.stub.StreamObserver; + +class RandomService implements GrpcService { + + private final Random random = new Random(); + + @Override + public Descriptors.FileDescriptor proto() { + return io.helidon.examples.webserver.grpc.random.Random.getDescriptor(); + } + + @Override + public void update(Routing router) { + router.unary("RandomSingle", this::randomSingle) + .bidi("RandomMany", this::randomMany); + } + + private void randomSingle(ParamMessage request, StreamObserver response) { + int bound = request.getNumber(); + response.onNext(newRandomMessage(random.nextInt(bound))); + response.onCompleted(); + } + + private StreamObserver randomMany(StreamObserver response) { + return new StreamObserver<>() { + + private int bound = Integer.MIN_VALUE; + private int count = Integer.MIN_VALUE; + + @Override + public void onNext(ParamMessage paramMessage) { + // collect bound and count, in that order + if (bound == Integer.MIN_VALUE) { + bound = paramMessage.getNumber(); + } else if (count == Integer.MIN_VALUE) { + count = paramMessage.getNumber(); + } else { + onError(new IllegalStateException("Received extra input params")); + } + } + + @Override + public void onError(Throwable throwable) { + response.onError(throwable); + } + + @Override + public void onCompleted() { + // verify input params received + if (bound == Integer.MIN_VALUE || count == Integer.MIN_VALUE) { + onError(new IllegalStateException("Did not receive all input params")); + } + + // send stream of random numbers + for (int i = 0; i < count; i++) { + response.onNext(newRandomMessage(random.nextInt(bound))); + } + response.onCompleted(); + } + }; + } + + private static RandomMessage newRandomMessage(int random) { + return RandomMessage.newBuilder().setNumber(random).build(); + } +} diff --git a/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/package-info.java b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/package-info.java new file mode 100644 index 000000000..690840655 --- /dev/null +++ b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Example of gRPC in webserver. + */ +package io.helidon.examples.webserver.grpc.random; diff --git a/examples/webserver/grpc-random/src/main/proto/random.proto b/examples/webserver/grpc-random/src/main/proto/random.proto new file mode 100644 index 000000000..559d69522 --- /dev/null +++ b/examples/webserver/grpc-random/src/main/proto/random.proto @@ -0,0 +1,32 @@ +/* + * 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. + */ + + +syntax = "proto3"; +option java_package = "io.helidon.examples.webserver.grpc.random"; + +service RandomService { + rpc RandomSingle (ParamMessage) returns (RandomMessage) {} + rpc RandomMany (stream ParamMessage) returns (stream RandomMessage) {} +} + +message ParamMessage { + int32 number = 1; +} + +message RandomMessage { + int32 number = 1; +} diff --git a/examples/webserver/grpc-random/src/main/resources/application.yaml b/examples/webserver/grpc-random/src/main/resources/application.yaml new file mode 100644 index 000000000..327fc590e --- /dev/null +++ b/examples/webserver/grpc-random/src/main/resources/application.yaml @@ -0,0 +1,30 @@ +# +# 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. +# + +server: + port: 8080 + tls: + trust: + keystore: + passphrase: "password" + trust-store: true + resource: + resource-path: "server.p12" + private-key: + keystore: + passphrase: "password" + resource: + resource-path: "server.p12" diff --git a/examples/webserver/grpc-random/src/main/resources/client.p12 b/examples/webserver/grpc-random/src/main/resources/client.p12 new file mode 100644 index 0000000000000000000000000000000000000000..4eb3b8325cd0190163032aca86e1d4fc94856822 GIT binary patch literal 4181 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 U@+;F6i_J(6+nRj}N+2@-U-0Veh5!Hn literal 0 HcmV?d00001 diff --git a/examples/webserver/grpc-random/src/test/java/io/helidon/examples/webserver/grpc/random/RandomServiceTest.java b/examples/webserver/grpc-random/src/test/java/io/helidon/examples/webserver/grpc/random/RandomServiceTest.java new file mode 100644 index 000000000..312d9ab33 --- /dev/null +++ b/examples/webserver/grpc-random/src/test/java/io/helidon/examples/webserver/grpc/random/RandomServiceTest.java @@ -0,0 +1,128 @@ +/* + * 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.examples.webserver.grpc.random; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import io.helidon.common.configurable.Resource; +import io.helidon.common.tls.Tls; +import io.helidon.examples.webserver.grpc.random.Random.ParamMessage; +import io.helidon.webclient.api.WebClient; +import io.helidon.webclient.grpc.GrpcClient; +import io.helidon.webserver.Router; +import io.helidon.webserver.WebServer; +import io.helidon.webserver.grpc.GrpcRouting; +import io.helidon.webserver.testing.junit5.ServerTest; +import io.helidon.webserver.testing.junit5.SetUpRoute; +import io.helidon.examples.webserver.grpc.random.Random.ParamMessage; +import io.helidon.examples.webserver.grpc.random.Random.RandomMessage; +import io.helidon.examples.webserver.grpc.random.RandomServiceGrpc; +import io.helidon.examples.webserver.grpc.random.RandomServiceGrpc.RandomServiceBlockingStub; + +import io.grpc.stub.StreamObserver; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.lessThan; + +/** + * Tests gRPC Strings service using {@link io.helidon.webclient.api.WebClient}. + */ +@ServerTest +class RandomServiceTest { + private static final long TIMEOUT_SECONDS = 10; + private static final int BOUND = 100; + private static final int COUNT = 10; + + private final WebClient webClient; + + private RandomServiceTest(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(); + } + + @SetUpRoute + static void routing(Router.RouterBuilder router) { + router.addRouting(GrpcRouting.builder().service(new RandomService())); + } + + @Test + void testRandomSingle() { + GrpcClient grpcClient = webClient.client(GrpcClient.PROTOCOL); + RandomServiceBlockingStub service = RandomServiceGrpc.newBlockingStub(grpcClient.channel()); + RandomMessage res = service.randomSingle(newParamMessage(BOUND)); + assertThat(res.getNumber(), is(lessThan(BOUND))); + } + + @Test + void testRandomMany() throws InterruptedException, TimeoutException, ExecutionException { + GrpcClient grpcClient = webClient.client(GrpcClient.PROTOCOL); + RandomServiceGrpc.RandomServiceStub service = RandomServiceGrpc.newStub(grpcClient.channel()); + CompletableFuture> future = new CompletableFuture<>(); + StreamObserver req = service.randomMany(multiStreamObserver(future)); + req.onNext(newParamMessage(BOUND)); + req.onNext(newParamMessage(COUNT)); + req.onCompleted(); + Iterator res = future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + int n = 0; + for (; res.hasNext(); n++) { + assertThat(res.next().getNumber(), is(lessThan(BOUND))); + } + assertThat(n, is(COUNT)); + } + + static ParamMessage newParamMessage(int n) { + return ParamMessage.newBuilder().setNumber(n).build(); + } + + static StreamObserver multiStreamObserver(CompletableFuture> future) { + return new StreamObserver<>() { + private final List value = new ArrayList<>(); + + @Override + public void onNext(ResT value) { + this.value.add(value); + } + + @Override + public void onError(Throwable t) { + future.completeExceptionally(t); + } + + @Override + public void onCompleted() { + future.complete(value.iterator()); + } + }; + } +} diff --git a/examples/webserver/grpc-random/src/test/resources/application.yaml b/examples/webserver/grpc-random/src/test/resources/application.yaml new file mode 100644 index 000000000..00b3aef2e --- /dev/null +++ b/examples/webserver/grpc-random/src/test/resources/application.yaml @@ -0,0 +1,30 @@ +# +# 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. +# + +server: + port: 0 + tls: + trust: + keystore: + passphrase: "password" + trust-store: true + resource: + resource-path: "server.p12" + private-key: + keystore: + passphrase: "password" + resource: + resource-path: "server.p12" diff --git a/examples/webserver/pom.xml b/examples/webserver/pom.xml index 84c6b2190..1e75eb90c 100644 --- a/examples/webserver/pom.xml +++ b/examples/webserver/pom.xml @@ -39,6 +39,7 @@ echo fault-tolerance grpc + grpc-random imperative multiport mutual-tls