Implementing a gRPC service
CDI Beanとして公開されたgRPCサービスの実装は、quarkus-grpcによって自動的に登録、提供されます。
Implementing a gRPC service requires the gRPC classes to be generated.
Place your proto files in src/main/proto and run mvn compile.
|
生成されたコード
Quarkusは、 proto ファイルで宣言されたサービスに対して、いくつかの実装クラスを生成します。
-
Mutiny APIを使用した サービスインターフェース
-
クラス名は
${JAVA_PACKAGE}.${NAME_OF_THE_SERVICE}
-
-
gRPC APIを利用した 実装のベース のクラス
-
クラス名は以下のように構成されています。
${JAVA_PACKAGE}.${NAME_OF_THE_SERVICE}Grpc.${NAME_OF_THE_SERVICE}ImplBase
-
例えば、以下の proto ファイルスニペットを使用した場合は、
option java_package = "hello"; (1)
service Greeter { (2)
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
| 1 | hello は、生成されたクラスのjavaパッケージです。 |
| 2 | Greeter はサービス名です。 |
サービス・インターフェースは hello.Greeter であり、実装ベースは hello.GreeterGrpc.GreeterImplBase という抽象的な静的ネストクラスです。
| You’ll need to implement the service interface or extend the base class with your service implementation bean as described in the following sections. |
Mutiny APIによるサービスの実装
To implement a gRPC service using the Mutiny API, create a class that implements the service interface.
Then, implement the methods defined in the service interface.
If you don’t want to implement a service method just throw a java.lang.UnsupportedOperationException from the method body (the exception will be automatically converted to the appropriate gRPC exception).
Finally, implement the service and add the @GrpcService annotation:
import io.quarkus.grpc.GrpcService;
import hello.Greeter;
@GrpcService (1)
public class HelloService implements Greeter { (2)
@Override
public Uni<HelloReply> sayHello(HelloRequest request) {
return Uni.createFrom().item(() ->
HelloReply.newBuilder().setMessage("Hello " + request.getName()).build()
);
}
}
| 1 | gRPCサービス実装Beanは、 @GrpcService アノテーションを付けなければならず、他のCDI修飾子を宣言してはいけません。すべてのgRPCサービスは、 jakarta.inject.Singleton のスコープを持ちます。さらに、サービス呼び出しの間、リクエストコンテキストが有効になります。 |
| 2 | hello.Greeter は、生成されたサービスインターフェースです。 |
The service implementation bean can also extend the Mutiny implementation base, where the class name is structured as follows: Mutiny${NAME_OF_THE_SERVICE}Grpc.${NAME_OF_THE_SERVICE}ImplBase.
|
デフォルトのgRPC APIでのサービス実装
デフォルトのgRPC APIを使ってgRPCサービスを実装するには、デフォルトの実装ベースを拡張したクラスを作成します。次に、サービス・インターフェースで定義されているメソッドを上書きします。最後に、サービスを実装し、 @GrpcService アノテーションを追加します。
import io.quarkus.grpc.GrpcService;
@GrpcService
public class HelloService extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
String name = request.getName();
String message = "Hello " + name;
responseObserver.onNext(HelloReply.newBuilder().setMessage(message).build());
responseObserver.onCompleted();
}
}
ブロッキングサービスの実装
デフォルトでは、gRPCサービスからのすべてのメソッドはイベントループ上で実行されます。そのため、ブロックしては いけません 。サービスロジックをブロックする必要がある場合は、メソッドに io.smallrye.common.annotation.Blocking アノテーションを追加します。
@Override
@Blocking
public Uni<HelloReply> sayHelloBlocking(HelloRequest request) {
// Do something blocking before returning the Uni
}
ストリームの処理
gRPCでは、ストリームを受信して返すことができます。
service Streaming {
rpc Source(Empty) returns (stream Item) {} // Returns a stream
rpc Sink(stream Item) returns (Empty) {} // Reads a stream
rpc Pipe(stream Item) returns (stream Item) {} // Reads a streams and return a streams
}
Mutinyを使うと、以下のように実装できます。
import io.quarkus.grpc.GrpcService;
@GrpcService
public class StreamingService implements Streaming {
@Override
public Multi<Item> source(Empty request) {
// Just returns a stream emitting an item every 2ms and stopping after 10 items.
return Multi.createFrom().ticks().every(Duration.ofMillis(2))
.select().first(10)
.map(l -> Item.newBuilder().setValue(Long.toString(l)).build());
}
@Override
public Uni<Empty> sink(Multi<Item> request) {
// Reads the incoming streams, consume all the items.
return request
.map(Item::getValue)
.map(Long::parseLong)
.collect().last()
.map(l -> Empty.newBuilder().build());
}
@Override
public Multi<Item> pipe(Multi<Item> request) {
// Reads the incoming stream, compute a sum and return the cumulative results
// in the outbound stream.
return request
.map(Item::getValue)
.map(Long::parseLong)
.onItem().scan(() -> 0L, Long::sum)
.onItem().transform(l -> Item.newBuilder().setValue(Long.toString(l)).build());
}
}
ヘルスチェック
実装されたサービスでは、Quarkus gRPCは以下の形式でヘルスチェックを公開しています。
syntax = "proto3";
package grpc.health.v1;
message HealthCheckRequest {
string service = 1;
}
message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0;
SERVING = 1;
NOT_SERVING = 2;
}
ServingStatus status = 1;
}
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}
クライアントは、特定のサービスのヘルス状態を取得するために完全修飾されたサービス名を指定したり、gRPCサーバーの一般的な状態を取得するためにサービス名の指定を省略することができます。
詳細については、 gRPCのドキュメント を確認してください。
さらに、Quarkus SmallRye Healthがアプリケーションに追加された場合、gRPCサービスの状態に関するレディネスチェックがMicroProfile Healthエンドポイントレスポンスに追加されます( /q/health )。
リフレクションサービス
Quarkus gRPC Serverは、 リフレクションサービス を実装しています。このサービスを使用すると、 grpcurl や grpcox などのツールがサービスと対話できるようになります。
リフレクションサービスは、 開発 モードではデフォルトで有効になっています。テストモードやプロダクションモードでは、 quarkus.grpc.server.enable-reflection-service を true に設定して明示的に有効にする必要があります。
Quarkus exposes both the reflection service v1 and v1alpha.
|
スケーリング
デフォルトでは、quarkus-grpcは単一のイベントループ上で動作する単一のgRPCサーバーを起動します。
サーバーをスケールさせたい場合は、 quarkus.grpc.server.instances を設定することで、サーバーのインスタンス数をセットできます。
サーバー構成
ビルド時に固定される設定プロパティ - それ以外の設定プロパティは実行時に上書き可能
Configuration property |
タイプ |
デフォルト |
|---|---|---|
The max inbound message size in bytes. Environment variable: Show more |
int |
|
Enables the gRPC Reflection Service. By default, the reflection service is only exposed in Environment variable: Show more |
boolean |
|
gRPC compression, e.g. "gzip" Environment variable: Show more |
string |
When you disable quarkus.grpc.server.use-separate-server, you are then using the new Vert.x gRPC server implementation
which uses the existing HTTP server. Which means that the server port is now 8080 (or the port configured with quarkus.http.port).
Also, most of the other configuration properties are no longer applied, since it’s the HTTP server that should already be properly configured.
|
When you enable quarkus.grpc.server.xds.enabled, it’s the xDS that should handle most of the configuration above.
|
構成の例
TLSを有効にする
TLSを有効にするには、以下の設定を使用します。
設定のすべてのパスは、クラスパス上のリソース (通常は src/main/resources やそのサブフォルダーから) か外部ファイルを指定することに注意してください。
quarkus.grpc.server.ssl.certificate=tls/server.pem
quarkus.grpc.server.ssl.key=tls/server.key
When SSL/TLS is configured, plain-text is automatically disabled.
|
相互認証付きTLS
相互認証付きのTLSを使用するには、以下の設定を使用します。
quarkus.grpc.server.ssl.certificate=tls/server.pem
quarkus.grpc.server.ssl.key=tls/server.key
quarkus.grpc.server.ssl.trust-store=tls/ca.jks
quarkus.grpc.server.ssl.trust-store-password=*****
quarkus.grpc.server.ssl.client-auth=REQUIRED
カスタムサーバーの構築
Quarkus が gRPC サーバーインスタンスを構築する際、ユーザーは独自の Server(Builder) カスタマイザーを適用できます。カスタマイザーは priority によって適用され、数字が大きいほど後に適用されます。カスタマイザーは、Quarkus がユーザーのサーバー設定を適用する前に適用されます。たとえば、いくつかの初期デフォルト設定に最適です。
customize メソッドは2つあり、1つは gRPC の ServerBuilder をパラメーターとして使用し、Quarkus のレガシー gRPC サポートで使用されます。もう1つは GrpcServerOptions を使用し、新しい Vert.x gRPC サポートで使用されます。ユーザーは、gRPC サポートタイプの使用に応じて適切な customize メソッドを実装するか、カスタマイザーが gRPC タイプに依存しない場合は両方を実装する必要があります。
public interface ServerBuilderCustomizer<T extends ServerBuilder<T>> {
/**
* Customize a ServerBuilder instance.
*
* @param config server's configuration
* @param builder Server builder instance
*/
default void customize(GrpcServerConfiguration config, T builder) {
}
/**
* Customize a GrpcServerOptions instance.
*
* @param config server's configuration
* @param options GrpcServerOptions instance
*/
default void customize(GrpcServerConfiguration config, GrpcServerOptions options) {
}
/**
* Priority by which the customizers are applied.
* Higher priority is applied later.
*
* @return the priority
*/
default int priority() {
return 0;
}
}
Domain Socket
The gRPC server uses the Quarkus HTTP server, so you can configure it to listen on a Unix domain socket using the standard HTTP configuration properties:
quarkus.http.domain-socket-enabled=true
quarkus.http.domain-socket=/var/run/grpc.sock
When a domain socket is enabled, the gRPC server automatically accepts gRPC requests on it — no gRPC-specific configuration is required.
To listen only on the domain socket (disabling TCP), set:
quarkus.http.host-enabled=false
Unix domain sockets require JDK 16+ and are not available on Windows. Native transport (quarkus.vertx.native-transport) is not required.
|
サーバーインターセプター
gRPCサーバーのインターセプターでは、サービスが呼び出される前に、認証などのロジックを実行することができるようになります。
io.grpc.ServerInterceptor を実装した @ApplicationScoped Beanを作成することで、gRPC サーバーのインターセプターを実装できます。
@ApplicationScoped
// add @GlobalInterceptor for interceptors meant to be invoked for every service
public class MyInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall,
Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
// ...
}
}
プロデューサー・メソッドをグローバル・インターセプターとしてアノテーションすることも可能です:
import io.quarkus.grpc.GlobalInterceptor;
import jakarta.enterprise.inject.Produces;
public class MyProducer {
@GlobalInterceptor
@Produces
public MyInterceptor myInterceptor() {
return new MyInterceptor();
}
}
| Check the ServerInterceptor JavaDoc to properly implement your interceptor. |
公開されているすべてのサービスにインターセプターを適用するには、 @io.quarkus.grpc.GlobalInterceptor でアノテーションを付けます。インターセプターを単一のサービスに適用するには、 @io.quarkus.grpc.RegisterInterceptor でそのサービスに登録します。
import io.quarkus.grpc.GrpcService;
import io.quarkus.grpc.RegisterInterceptor;
@GrpcService
@RegisterInterceptor(MyInterceptor.class)
public class StreamingService implements Streaming {
// ...
}
複数のサーバーインターセプターがある場合、 jakarta.enterprise.inject.spi.Prioritized インターフェースを実装することで、それらを順番に並べることができます。すべてのグローバルインターセプターは、サービス固有のインターセプターの前に起動されることに注意してください。
@ApplicationScoped
public class MyInterceptor implements ServerInterceptor, Prioritized {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall,
Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
// ...
}
@Override
public int getPriority() {
return 10;
}
}
最高の優先度を持つインターセプターが最初に呼び出されます。インターセプターが Prioritized インターフェイスを実装していない場合に使用されるデフォルトの優先度は 0 です。
必要に応じて、Vert.x の RoutingContext インスタンスを gRPC サービスに注入するサポートもあります。Quarkus はデフォルトではこれを実行しないため、RoutingContextGrpcInterceptor を gRPC サービスに追加する必要があります。
@GrpcService
@RegisterInterceptor(RoutingContextGrpcInterceptor.class)
public class HelloWorldService extends GreeterGrpc.GreeterImplBase {
@Inject
RoutingContext context;
// ...
}
サービスのテスト
gRPCサービスをテストする最も簡単な方法は、 gRPCサービスの使用 で説明したように、gRPCクライアントを使用することです。
なお、TLS を使用していない公開サービスのテストにクライアントを使用する場合は、設定を行う必要はありませんのでご注意ください。例えば、上記で定義した HelloService をテストするには、次のようなテストを作成します。
public class HelloServiceTest implements Greeter {
@GrpcClient
Greeter client;
@Test
void shouldReturnHello() {
CompletableFuture<String> message = new CompletableFuture<>();
client.sayHello(HelloRequest.newBuilder().setName("Quarkus").build())
.subscribe().with(reply -> message.complete(reply.getMessage()));
assertThat(message.get(5, TimeUnit.SECONDS)).isEqualTo("Hello Quarkus");
}
}
手動でサービスを試す
開発モードでは、Quarkus Dev UI で gRPC サービスを試すことができます。 その場合は、 http://localhost:8080/q/dev-ui にアクセスし、gRPC タイルの下にある Services をクリックしてください。
Dev UIにアクセスするには、アプリケーションが ”通常の” HTTPポートを公開する必要があることに注意してください。アプリケーションがHTTPエンドポイントを公開していない場合は、 quarkus-vertx-http に依存する専用のプロファイルを作成することができます。
<profiles>
<profile>
<id>development</id>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
これがあれば、次のようにしてDevモードを実行できます: mvn quarkus:dev -Pdevelopment
Gradleを使用している場合は、 quarkusDev タスクの依存関係を追加するだけです。
dependencies {
quarkusDev 'io.quarkus:quarkus-vertx-http'
}
gRPCサーバーのメトリクス
メトリクス収集の有効化
アプリケーションが quarkus-micrometer エクステンションも使用している場合、gRPCサーバーのメトリクスは自動的に有効になります。 Micrometerは、アプリケーションが実装するすべてのgRPCサービスのメトリクスを収集します。
例えば、メトリクスをPrometheusにエクスポートすると、以下のように取得できます。
# HELP grpc_server_responses_sent_messages_total The total number of responses sent
# TYPE grpc_server_responses_sent_messages_total counter
grpc_server_responses_sent_messages_total{method="SayHello",methodType="UNARY",service="helloworld.Greeter",} 6.0
# HELP grpc_server_processing_duration_seconds The total time taken for the server to complete the call
# TYPE grpc_server_processing_duration_seconds summary
grpc_server_processing_duration_seconds_count{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 6.0
grpc_server_processing_duration_seconds_sum{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 0.016216771
# HELP grpc_server_processing_duration_seconds_max The total time taken for the server to complete the call
# TYPE grpc_server_processing_duration_seconds_max gauge
grpc_server_processing_duration_seconds_max{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 0.007985236
# HELP grpc_server_requests_received_messages_total The total number of requests received
# TYPE grpc_server_requests_received_messages_total counter
grpc_server_requests_received_messages_total{method="SayHello",methodType="UNARY",service="helloworld.Greeter",} 6.0
サービス名、メソッド、タイプは tags で確認できます。
By default, processing duration is exported as a Prometheus summary (_count, _sum, _max).
To publish aggregatable histogram buckets (for example for histogram_quantile), enable:
quarkus.micrometer.binder.grpc-server.histogram=true
This uses Micrometer’s default percentile histogram buckets (about 1ms to 30s).
Optionally add extra SLO boundaries and/or clamp the published bucket range to reduce cardinality:
quarkus.micrometer.binder.grpc-server.slos=5ms,10ms,25ms,50ms,100ms,1s
quarkus.micrometer.binder.grpc-server.minimum-expected-value=1ms
quarkus.micrometer.binder.grpc-server.maximum-expected-value=10s
Histograms are disabled by default because they increase memory usage and metric cardinality.
メトリクス収集の無効化
quarkus-micrometer を使用しているときに gRPC サーバーメトリクスを無効にするには、アプリケーションの設定に以下のプロパティを追加します。
quarkus.micrometer.binder.grpc-server.enabled=false
仮想スレッドの使用
gRPCサービスの実装で仮想スレッドを使用するには、専用の ガイド を参照してください。
gRPC サーバーの認可
Quarkus には、既存の Vert.x HTTP サーバーを使用する Vert.x gRPC サポートが有効な場合に、アノテーションを使用した認可 を可能にする組み込みのセキュリティーが含まれています。
Quarkus Security エクステンションを追加する
セキュリティー機能は Quarkus Security エクステンションによって提供されるため、 pom.xml ファイルに次の依存関係が含まれていることを確認してください。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security</artifactId>
</dependency>
既存の Maven プロジェクトに Quarkus Security エクステンションを追加するには、プロジェクトのベースディレクトリーで次のコマンドを実行します:
quarkus extension add security
./mvnw quarkus:add-extension -Dextensions='security'
./gradlew addExtension --extensions='security'
サポートされる認証メカニズムの概要
サポートされている認証メカニズムの一部は Quarkus に組み込まれていますが、それ以外ではエクステンションを追加する必要があります。 次の表では、Quarkus で使用できるサポート対象メカニズムとその認証要件をマッピングしています。
| 認証要件 | 認証メカニズム |
|---|---|
ユーザー名とパスワード |
|
クライアント証明書 |
|
カスタム要件 |
|
ベアラーアクセストークン |
OIDCベアラートークン認証 、 JWT 、 OAuth2 |
選択した認証要件に応じた IdentityProvider を提供するエクステンションを、必ず 1 つ以上インストールします。
ユーザー名とパスワードに基づいて IdentityProvider を提供する方法の例については、Basic 認証ガイド を参照してください。
If you use a separate HTTP server to serve gRPC requests, カスタム認証 is your only option.
Set the quarkus.grpc.server.use-separate-server configuration property to false so that you can use other mechanisms.
|
セキュアな gRPC サービス
gRPC サービスは、次の例のように、標準セキュリティーアノテーション を使用して保護できます。
package org.acme.grpc.auth;
import hello.Greeter;
import io.quarkus.grpc.GrpcService;
import jakarta.annotation.security.RolesAllowed;
@GrpcService
public class HelloService implements Greeter {
@RolesAllowed("admin")
@Override
public Uni<HelloReply> sayHello(HelloRequest request) {
return Uni.createFrom().item(() ->
HelloReply.newBuilder().setMessage("Hello " + request.getName()).build()
);
}
}
Most of the examples of the supported mechanisms send authentication headers, please refer to the gRPC Headers section of the Consuming a gRPC Service guide for more information about the gRPC headers.
Basic認証
Quarkus Security は、Basic 認証 の組み込み認証サポートを提供します。
quarkus.grpc.server.use-separate-server=false
quarkus.http.auth.basic=true (1)
| 1 | Basic 認証を有効にします。 |
package org.acme.grpc.auth;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import org.acme.proto.Greeter;
import org.acme.proto.HelloRequest;
import io.grpc.Metadata;
import io.quarkus.grpc.GrpcClient;
import io.quarkus.grpc.GrpcClientUtils;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
@QuarkusTest
public class GreeterServiceTest {
private static final Metadata.Key<String> AUTHORIZATION = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER);
@GrpcClient
Greeter greeterClient;
@Test
void shouldReturnHello() throws ExecutionException, InterruptedException, TimeoutException {
Metadata headers = new Metadata();
// Set the headers - Basic auth for testing
headers.put(AUTHORIZATION, "Basic YWxpY2U6YWxpY2U="); // alice:alice with "admin" role
var client = GrpcClientUtils.attachHeaders(greeterClient, headers);
// Call the client
CompletableFuture<String> message = new CompletableFuture<>();
client.sayHello(HelloRequest.newBuilder().setName("Quarkus").build())
.subscribe().with(reply -> message.complete(reply.getMessage()));
// Get the values
String theValue = message.get(5, TimeUnit.SECONDS);
// Assert
assertThat(theValue, is("Hello Quarkus"));
}
}
相互 TLS 認証
Quarkus には相互 TLS (mTLS) 認証があり、X.509 証明書に基づいてユーザーを認証できます。 すべての gRPC サービスに認証を適用する最も簡単な方法については、このガイドの 相互認証付きTLS セクションを参照してください。 ただし、Quarkus Security はロールマッピングをサポートしており、これを使用することでより詳細なアクセス制御を実行できます。
quarkus.grpc.server.use-separate-server=false
quarkus.http.insecure-requests=disabled
quarkus.http.ssl.certificate.files=tls/server.pem
quarkus.http.ssl.certificate.key-files=tls/server.key
quarkus.http.ssl.certificate.trust-store-file=tls/ca.jks
quarkus.http.ssl.certificate.trust-store-password=**********
quarkus.http.ssl.client-auth=required
quarkus.http.auth.certificate-role-properties=role-mappings.txt (1)
quarkus.native.additional-build-args=-H:IncludeResources=.*\\.txt
| 1 | 証明書ロールマッピングを追加します。 |
testclient=admin (1)
| 1 | testclient 証明書の CN (共通名) を SecurityIdentity ロール admin にマッピングします。 |
カスタム認証
You can always implement one or more GrpcSecurityMechanism beans if above-mentioned mechanisms provided by Quarkus do no meet your needs.
GrpcSecurityMechanismpackage org.acme.grpc.auth;
import jakarta.inject.Singleton;
import io.grpc.Metadata;
import io.quarkus.security.credential.PasswordCredential;
import io.quarkus.security.identity.request.AuthenticationRequest;
import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest;
@Singleton
public class CustomGrpcSecurityMechanism implements GrpcSecurityMechanism {
private static final Metadata.Key<String> AUTHORIZATION = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER);
@Override
public boolean handles(Metadata metadata) {
String authString = metadata.get(AUTHORIZATION);
return authString != null && authString.startsWith("Custom ");
}
@Override
public AuthenticationRequest createAuthenticationRequest(Metadata metadata) {
final String authString = metadata.get(AUTHORIZATION);
final String userName;
final String password;
// here comes your application logic that transforms 'authString' to user name and password
return new UsernamePasswordAuthenticationRequest(userName, new PasswordCredential(password));
}
}