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 @@
-
-
-
+
+
+