From 7df3f5f93756ee2a7ad8ae39d27221a67827cad9 Mon Sep 17 00:00:00 2001 From: tkote <7146253+tkote@users.noreply.github.com> Date: Wed, 25 Dec 2024 02:02:53 +0000 Subject: [PATCH] Merge branch 'er-4.1.6' --- README.md | 206 ++++++++++++------ pom-docker.xml | 2 +- pom.xml | 78 +++++-- .../oracle/demo/grpc/HelloWorldResource.java | 57 +++++ .../oracle/demo/grpc/HelloWorldService.java | 74 +++++++ .../demo/grpc/HelloWorldServiceClient.java | 24 ++ src/main/proto/helloworld.proto | 42 ++++ src/main/resources/WEB/graphql/ui/index.html | 6 +- src/main/resources/application.yaml | 18 +- src/main/resources/fullchain.pem | 47 ++++ .../demo/grpc/HelloWorldResourceTest.java | 34 +++ .../demo/grpc/HelloWorldServiceTest.java | 134 ++++++++++++ 12 files changed, 619 insertions(+), 103 deletions(-) create mode 100644 src/main/java/oracle/demo/grpc/HelloWorldResource.java create mode 100644 src/main/java/oracle/demo/grpc/HelloWorldService.java create mode 100644 src/main/java/oracle/demo/grpc/HelloWorldServiceClient.java create mode 100644 src/main/proto/helloworld.proto create mode 100644 src/main/resources/fullchain.pem create mode 100644 src/test/java/oracle/demo/grpc/HelloWorldResourceTest.java create mode 100644 src/test/java/oracle/demo/grpc/HelloWorldServiceTest.java diff --git a/README.md b/README.md index 7548c61..bde8300 100644 --- a/README.md +++ b/README.md @@ -67,14 +67,10 @@ src/main │ │ └── FaultToleranceTester.java │ ├── graphql [GraphQL] │ │ └── CountryGraphQLApi.java -│ ├── grpc [拡張機能 gRPC - 4.x には無し] -│ │ └── protobuf -│ │ ├── GreeterSimpleService.java -│ │ ├── GreeterService.java -│ │ ├── GrpcResource.java -│ │ └── helloworld [ビルド時に生成される] -│ │ ├── GreeterGrpc.java -│ │ └── Helloworld.java +│ ├── grpc [拡張機能 gRPC] +│ │ ├── HelloWorldResource.java +│ │ ├── HelloWorldServiceClient.java +│ │ └── HelloWorldService.java │ ├── health [ヘルスチェック] │ │ ├── HealthCheckHelper.java │ │ ├── HealthCheckResource.java @@ -944,98 +940,166 @@ $ docker rm oracledb ## § gRPC デモ (oracle.demo.grpc パッケージ) -**注意! 4.x には実装がありません** +Helidon MP はアノテーションを使って簡単に gRPC サーバー&クライアントを実装することができます。 +4.1 から Virtual Thread を使った新しい実装となりました。また、これに伴い 3.x では別だったポートが REST と同じポートになりました。 -Helidon MP はアノテーションを使って簡単に gRPC サーバーを実装することができます。 -gRPCの転送データのフォーマットである protobuf を用意する必要がありますが、このデモでは、ビルド時の `mvn -P protoc initialize` で必要な Java ソースファイルを生成しています。 +### proto ファイルから必要な Java ソースファイルの生成 -```bash -# REST -> gRPC Client -> gRPC Server と呼び出される +gRPC で扱う Protocol Buffers の定義ファイルは src/main/proto/helloworld.proto にあります。grpc.io が提供する Java example とのインターオペラビリティを確認するために、[同じ定義ファイル](https://github.com/grpc/grpc/blob/master/examples/protos/helloworld.proto)を使っています。 -$ curl localhost:8080/grpc-protobuf/client -Hello world +```proto +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} + rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {} + + rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} ``` -注: 2.3.0 から Java シリアライゼーションを用いた方法は depricated になりました。 +この proto ファイルから Java での実装に必要なソースファイルを生成するために、pom.xml では以下の設定を行なっています。 +```xml + + + + + kr.motd.maven + os-maven-plugin + ${version.plugin.os} + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + + compile + test-compile + + + + + + +``` + +これにより compile フェーズで proto ファイルがプリコンパイルされ、target/generated-sources に必要なソースファイルが生成されます。 -### protobuf版 (oracle.demo.grpc.protobuf パッケージ) に関する補足 -[gRPC Java Quickstart](https://grpc.io/docs/languages/java/quickstart/) と同じprotoファイルを用いて、互換性のある実装を行っていますので、Quickstart で作成したクライアントから Helidon の gRPC サーバーを呼び出すことができます。 +### gRPC サーバーの実装 -protobuf ペイロードを使ったサーバー実装は更に POJO + Annotaion を使った方法と、GrpcMpExtension を使って従来型のサービス実装クラスをデプロイする方法の、2種類を提供しています。おすすめは POJO + Annotaion です。 +サーバーのメソッドの書き方は、[Helidon 3 のドキュメンテーション](https://helidon.io/docs/v3/mp/grpc/server#_defining_service_methods)に解説がありますので、Unary, ServerStreaming, ClientStreaming, Bidirectional 各々に適した引数と返り値を指定して下さい。 ストリーミングを扱う場合、java.util.stream.Stream、io.grpc.stub.StreamObserver、java.util.concurrent.CompletableFuture などの選択肢があります。 -1. POJO + Annotaion を使った方法(デフォルト 有効) -Helidonが提供するアノテーションを使って、シンプルなコーディングができます。 ```java -@Grpc(name = "helloworld.Greeter") +@Grpc.GrpcService("helloworld.Greeter") @ApplicationScoped -public class GreeterSimpleService{ +public class HelloWorldService { - @Unary(name = "SayHello") - public HelloReply sayHello(HelloRequest req) { - System.out.println("gRPC GreeterSimpleService called - name: " + req.getName()); - return HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + @Grpc.Unary("SayHello") + public HelloReply sayHello(HelloRequest request) { + // ここに実装を書く } -} -``` - * 関連するファイル -```text -oracle.demo.grpc.protobuf.GreeterSimpleService + + @Grpc.ServerStreaming("SayHelloStreamReply") + public Stream sayHelloStreamReply(HelloRequest request) { + // ここに実装を書く + } + + @Grpc.Bidirectional("SayHelloBidiStream") + public StreamObserver sayHelloBidiStream(StreamObserver observer) { + // ここに実装を書く - Bidirectionalの場合は、この引数/返り値のパターンのみ指定可能 + } ``` -2. GrpcMpExtensionを使って従来型のサービス実装クラスをデプロイする方法(デフォルト 無効) -protobufコンパイラで生成されたJavaクラスを直接使用する方式です。 +### gRPC クライアントの実装 + +クライアントは、以下のようにインターフェースにアノテーションを付けてあげれば +Helidon が動的に Proxy を作ってくれます。 + ```java -class GreeterService extends GreeterGrpc.GreeterImplBase { - @Override - public void sayHello(HelloRequest req, StreamObserver observer) { - System.out.println("gRPC GreeterService called - name: " + req.getName()); - HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); - observer.onNext(reply); - observer.onCompleted(); - } +@Grpc.GrpcService("helloworld.Greeter") +@Grpc.GrpcChannel("helloworld-channel") +public interface HelloWorldServiceClient { + + @Grpc.Unary("SayHello") + HelloReply sayHello(HelloRequest request); + + @Grpc.ServerStreaming("SayHelloStreamReply") + Stream sayHelloStreamReply(HelloRequest request); + + @Grpc.Bidirectional("SayHelloBidiStream") + public StreamObserver sayHelloBidiStream(StreamObserver observer); + } ``` - * 関連するファイル -```text -oracle.demo.grpc.protobuf.GreeterService -oracle.demo.grpc.protobuf.GrpcExtension -META-INF/services/io.helidon.microprofile.grpc.server.spi.GrpcMpExtension +クライアントの実装は、テストのソースを確認して下さい。 + +### application.yaml の書き方 + +クライアントは、channel を使って呼び出し先のサーバーを指定します。インターフェースのアノテーションで指定している channel 名に対する設定を行なって下さい。 + +```yaml +grpc: + client: + channels: + - name: "helloworld-channel" + host: localhost + port: 8080 + tls: + enabled: false ``` +tls.enabled を明示的に false にしないと TLS 接続をしようとするので注意して下さい。 -
-実装の切り替え方 ( POJO + Annotaion 方式 → GrpcMpExtension 方式 ) +### 実行 + +Unary のメソッドだけ、REST 経由で呼びだせるようにしています。 + +```bash +# REST -> gRPC Client -> gRPC Server と呼び出される + +$ curl localhost:8080/grpc/sayHello +Hello world -1. META-INF/services/io.helidon.microprofile.grpc.server.spi.GrpcMpExtension を編集する -```text -# コメントアウトを外す -# oracle.demo.grpc.protobuf.GrpcExtension -oracle.demo.grpc.protobuf.GrpcExtension ``` -2. oracle.demo.grpc.protobuf.GreeterSimpleService を編集する -```java -// @Grpc アノテーションをコメントアウトする -// @Grpc(name = "helloworld.Greeter") -@ApplicationScoped -public class GreeterSimpleService{ +[gRPC Java Quickstart](https://grpc.io/docs/languages/java/quickstart/) で作成したサーバー/クライアントから Helidon の gRPC サーバーを呼び出すことができます。 + +Quickstart のサーバーと Helidon を起動して以下のコマンドを実行する - @Unary(name = "SayHello") - public HelloReply sayHello(HelloRequest req) { - System.out.println("gRPC GreeterSimpleService called - name: " + req.getName()); - return HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); - } -} ``` -
-
+curl http://localhost:8080/grpc/sayHello?port=50051 +``` -### gRPC - protoファイルのコンパイルについて +Helidon を以下のオプションで起動して、Quickstart のクライアントを実行する -pom.xml の通常ビルドフェーズとは独立してprotoファイルのコンパイルを行うプロファイルを定義しています。 -[ビルド方法](#ビルド方法) にあるとおり、protoc を使ってまず最初に proto ファイルから Java ソースを生成して、srcディレクトリにコピーをします。詳細は、pom.xml の内容を確認して下さい。 +``` +java -Dserver.port=50051 -jar target/helidon-mp-demo.jar +``` [目次に戻る](#目次)
diff --git a/pom-docker.xml b/pom-docker.xml index 19c6575..3dbd426 100644 --- a/pom-docker.xml +++ b/pom-docker.xml @@ -5,7 +5,7 @@ oracle.demo helidon-mp-demo-docker-build - 4.0.11.0 + 4.1.6.0 helidon-mp-demo-docker-build diff --git a/pom.xml b/pom.xml index 25a6f85..3da1866 100644 --- a/pom.xml +++ b/pom.xml @@ -6,16 +6,17 @@ io.helidon.applications helidon-mp - 4.0.11 + 4.1.6 me.opc-helidon helidon-mp-demo - 4.0.11.0 + 4.1.6.0 ${project.artifactId} + @@ -185,12 +186,22 @@ helidon-microprofile-rest-client - + io.helidon.microprofile.openapi helidon-microprofile-openapi runtime + + io.helidon.integrations.openapi-ui + helidon-integrations-openapi-ui + runtime + + + io.smallrye + smallrye-open-api-ui + runtime + @@ -198,6 +209,24 @@ helidon-microprofile-telemetry + + + io.helidon.grpc + helidon-grpc-core + + + io.helidon.microprofile.grpc + helidon-microprofile-grpc-core + + + io.helidon.microprofile.grpc + helidon-microprofile-grpc-server + + + io.helidon.microprofile.grpc + helidon-microprofile-grpc-client + + io.helidon.microprofile.messaging @@ -232,6 +261,8 @@ helidon-lra-coordinator-narayana-client + + com.github.ricksbrown @@ -266,6 +297,15 @@ + + + + kr.motd.maven + os-maven-plugin + ${version.plugin.os} + + + org.apache.maven.plugins @@ -285,6 +325,20 @@ + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + + compile + test-compile + + + + @@ -354,7 +408,6 @@ - @@ -367,23 +420,6 @@ - - - openapi-ui - - - io.helidon.integrations.openapi-ui - helidon-integrations-openapi-ui - runtime - - - io.smallrye - smallrye-open-api-ui - runtime - - - - \ No newline at end of file diff --git a/src/main/java/oracle/demo/grpc/HelloWorldResource.java b/src/main/java/oracle/demo/grpc/HelloWorldResource.java new file mode 100644 index 0000000..7f8cfa0 --- /dev/null +++ b/src/main/java/oracle/demo/grpc/HelloWorldResource.java @@ -0,0 +1,57 @@ +package oracle.demo.grpc; + +import java.util.Objects; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; + +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.helidon.grpc.api.Grpc; +import io.helidon.microprofile.grpc.client.GrpcConfigurablePort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; + +@Path("/grpc") +@ApplicationScoped +public class HelloWorldResource { + + private final Logger logger = Logger.getLogger(HelloWorldResource.class.getName()); + + @Inject + @Grpc.GrpcProxy + private HelloWorldServiceClient client; + + @GET + @Path("/sayHello") + @Produces(MediaType.TEXT_PLAIN) + public String sayHello(@QueryParam("name") String name, @QueryParam("port") Integer port) { + + if(Objects.nonNull(port) && client instanceof GrpcConfigurablePort c) { + c.channelPort(port); + } + + String param = Optional.ofNullable(name).orElse("world"); + logger.log(Level.INFO, "Calling SayHello with name = {0}", param); + + HelloRequest request = HelloRequest.newBuilder().setName(param).build(); + + try { + HelloReply response = client.sayHello(request); + String msg = response.getMessage(); + logger.log(Level.INFO, "reply: {0}", msg); + return msg; + + }catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + throw new RuntimeException(e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/src/main/java/oracle/demo/grpc/HelloWorldService.java b/src/main/java/oracle/demo/grpc/HelloWorldService.java new file mode 100644 index 0000000..718a81a --- /dev/null +++ b/src/main/java/oracle/demo/grpc/HelloWorldService.java @@ -0,0 +1,74 @@ +package oracle.demo.grpc; + +import java.util.logging.Logger; +import java.util.stream.Stream; + +import io.helidon.grpc.api.Grpc; + +import io.grpc.stub.StreamObserver; +import jakarta.enterprise.context.ApplicationScoped; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; + + +@Grpc.GrpcService("helloworld.Greeter") +@ApplicationScoped +public class HelloWorldService { + + private final Logger logger = Logger.getLogger(HelloWorldService.class.getName()); + + @Grpc.Unary("SayHello") + public HelloReply sayHello(HelloRequest request) { + logger.info("gRPC SayHello - name: " + request.getName()); + String reply = "Hello " + request.getName(); + return HelloReply.newBuilder().setMessage(reply).build(); + } + + @Grpc.ServerStreaming("SayHelloStreamReply") + public Stream sayHelloStreamReply(HelloRequest request) { + String name = request.getName(); + logger.info("gRPC SayHelloStreamReply - name: " + name); + String[] parts = {"Hello", name}; + return Stream.of(parts).map(s -> HelloReply.newBuilder().setMessage(s).build()); + } + + @Grpc.Bidirectional("SayHelloBidiStream") + public StreamObserver sayHelloBidiStream(StreamObserver observer) { + logger.info("gRPC SayHelloBidiStream"); + logger.info("StreamObserver: " + observer.getClass().getName()); + return new HelloRequestStreamObserver(observer); + } + + + public class HelloRequestStreamObserver implements StreamObserver{ + + private final Logger logger = Logger.getLogger(HelloRequestStreamObserver.class.getName()); + + private StreamObserver reply; + + public HelloRequestStreamObserver(StreamObserver reply){ + this.reply = reply; + } + + @Override + public void onNext(HelloRequest value) { + logger.info("onNext(): " + value.getName()); + reply.onNext(HelloReply.newBuilder().setMessage(value.getName()).build()); + } + + @Override + public void onError(Throwable t) { + logger.warning("onError(): " + t.getMessage()); + } + + @Override + public void onCompleted() { + logger.info("onCompleted()"); + reply.onCompleted(); + } + + } + + +} + diff --git a/src/main/java/oracle/demo/grpc/HelloWorldServiceClient.java b/src/main/java/oracle/demo/grpc/HelloWorldServiceClient.java new file mode 100644 index 0000000..8930e44 --- /dev/null +++ b/src/main/java/oracle/demo/grpc/HelloWorldServiceClient.java @@ -0,0 +1,24 @@ +package oracle.demo.grpc; + +import java.util.stream.Stream; + +import io.helidon.grpc.api.Grpc; +import io.grpc.stub.StreamObserver; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; + +@Grpc.GrpcService("helloworld.Greeter") +@Grpc.GrpcChannel("helloworld-channel") +public interface HelloWorldServiceClient { + + @Grpc.Unary("SayHello") + HelloReply sayHello(HelloRequest request); + + @Grpc.ServerStreaming("SayHelloStreamReply") + Stream sayHelloStreamReply(HelloRequest request); + + @Grpc.Bidirectional("SayHelloBidiStream") + public StreamObserver sayHelloBidiStream(StreamObserver observer); + +} + diff --git a/src/main/proto/helloworld.proto b/src/main/proto/helloworld.proto new file mode 100644 index 0000000..9a0b59c --- /dev/null +++ b/src/main/proto/helloworld.proto @@ -0,0 +1,42 @@ +// Copyright 2015 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. + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} + + rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {} + + rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/src/main/resources/WEB/graphql/ui/index.html b/src/main/resources/WEB/graphql/ui/index.html index 9bd534f..97ea701 100644 --- a/src/main/resources/WEB/graphql/ui/index.html +++ b/src/main/resources/WEB/graphql/ui/index.html @@ -8,9 +8,9 @@
- - - + + +