From 744e32943e376083644ba2503db857d6fda7f65b Mon Sep 17 00:00:00 2001 From: Keith Lustria Date: Mon, 18 Jul 2022 17:50:13 -0700 Subject: [PATCH 1/2] Update/Reformat Helidon MP and SE gRPC documents --- docs/config/config_reference.adoc | 3 + ...don_grpc_client_GrpcChannelDescriptor.adoc | 54 + ...o_helidon_grpc_core_GrpcTlsDescriptor.adoc | 53 + ...n_grpc_server_GrpcServerConfiguration.adoc | 52 + docs/mp/grpc/{mp-clients.adoc => client.adoc} | 260 ++--- ...ervices.adoc => server-side-services.adoc} | 106 +- docs/mp/introduction/introduction.adoc | 2 +- docs/se/grpc/client-configuration.adoc | 59 -- docs/se/grpc/client-introduction.adoc | 88 -- ...client-implementation.adoc => client.adoc} | 315 +++--- docs/se/grpc/configuration.adoc | 69 -- docs/se/grpc/health-checks.adoc | 119 --- docs/se/grpc/interceptors.adoc | 126 --- docs/se/grpc/introduction.adoc | 79 -- docs/se/grpc/marshalling.adoc | 2 +- docs/se/grpc/metrics.adoc | 196 ---- docs/se/grpc/routing.adoc | 104 -- docs/se/grpc/security.adoc | 233 ----- docs/se/grpc/server.adoc | 933 ++++++++++++++++++ docs/se/grpc/service-implementation.adoc | 165 ---- docs/se/introduction.adoc | 2 +- docs/sitegen.yaml | 22 +- grpc/client/pom.xml | 12 + .../grpc/client/GrpcChannelDescriptor.java | 11 +- grpc/client/src/main/java/module-info.java | 2 + grpc/core/pom.xml | 12 + .../helidon/grpc/core/GrpcTlsDescriptor.java | 8 + grpc/core/src/main/java/module-info.java | 2 + grpc/server/pom.xml | 12 + .../grpc/server/GrpcServerConfiguration.java | 19 + grpc/server/src/main/java/module-info.java | 2 + 31 files changed, 1581 insertions(+), 1541 deletions(-) create mode 100644 docs/config/io_helidon_grpc_client_GrpcChannelDescriptor.adoc create mode 100644 docs/config/io_helidon_grpc_core_GrpcTlsDescriptor.adoc create mode 100644 docs/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc rename docs/mp/grpc/{mp-clients.adoc => client.adoc} (69%) rename docs/mp/grpc/{mp-server-side-services.adoc => server-side-services.adoc} (73%) delete mode 100644 docs/se/grpc/client-configuration.adoc delete mode 100644 docs/se/grpc/client-introduction.adoc rename docs/se/grpc/{client-implementation.adoc => client.adoc} (57%) delete mode 100644 docs/se/grpc/configuration.adoc delete mode 100644 docs/se/grpc/health-checks.adoc delete mode 100644 docs/se/grpc/interceptors.adoc delete mode 100644 docs/se/grpc/introduction.adoc delete mode 100644 docs/se/grpc/metrics.adoc delete mode 100644 docs/se/grpc/routing.adoc delete mode 100644 docs/se/grpc/security.adoc create mode 100644 docs/se/grpc/server.adoc delete mode 100644 docs/se/grpc/service-implementation.adoc diff --git a/docs/config/config_reference.adoc b/docs/config/config_reference.adoc index 02c45cdf6ea..4db53a1cff7 100644 --- a/docs/config/config_reference.adoc +++ b/docs/config/config_reference.adoc @@ -34,6 +34,9 @@ The following section lists all configurable types in Helidon. - xref:{rootdir}/config/io_helidon_faulttolerance_Retry_DelayingRetryPolicy.adoc[DelayingRetryPolicy (faulttolerance.Retry)] - xref:{rootdir}/config/io_helidon_security_providers_common_EvictableCache.adoc[EvictableCache (security.providers.common)] - xref:{rootdir}/config/io_helidon_security_providers_google_login_GoogleTokenProvider.adoc[GoogleTokenProvider (security.providers.google.login)] +- xref:{rootdir}/config/io_helidon_grpc_client_GrpcChannelDescriptor.adoc[GrpcChannelDescriptor (grpc.client)] +- xref:{rootdir}/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc[GrpcServerConfiguration (grpc.server)] +- xref:{rootdir}/config/io_helidon_grpc_core_GrpcTlsDescriptor.adoc[GrpcTlsDescriptor (grpc.core)] - xref:{rootdir}/config/io_helidon_security_providers_header_HeaderAtnProvider.adoc[HeaderAtnProvider (security.providers.header)] - xref:{rootdir}/config/io_helidon_security_providers_httpsign_SignedHeadersConfig_HeadersConfig.adoc[HeadersConfig (security.providers.httpsign.SignedHeadersConfig)] - xref:{rootdir}/config/io_helidon_health_HealthSupport.adoc[HealthSupport (health)] diff --git a/docs/config/io_helidon_grpc_client_GrpcChannelDescriptor.adoc b/docs/config/io_helidon_grpc_client_GrpcChannelDescriptor.adoc new file mode 100644 index 00000000000..63e4b534082 --- /dev/null +++ b/docs/config/io_helidon_grpc_client_GrpcChannelDescriptor.adoc @@ -0,0 +1,54 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2022 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. + +/////////////////////////////////////////////////////////////////////////////// + +ifndef::rootdir[:rootdir: {docdir}/..] +:description: Configuration of io.helidon.grpc.client.GrpcChannelDescriptor +:keywords: helidon, config, io.helidon.grpc.client.GrpcChannelDescriptor +:basic-table-intro: The table below lists the configuration keys that configure io.helidon.grpc.client.GrpcChannelDescriptor +include::{rootdir}/includes/attributes.adoc[] + += GrpcChannelDescriptor (grpc.client) Configuration + +// tag::config[] + + +Type: link:{javadoc-base-url}/io.helidon.grpc.client/io/helidon/grpc/client/GrpcChannelDescriptor.html[io.helidon.grpc.client.GrpcChannelDescriptor] + + + + +== Configuration options + + + +Optional configuration options: +[cols="3,3,2,5a"] + +|=== +|key |type |default value |description + +|`host` |string |`localhost` |Set the host name to connect. +|`port` |int |`1408` |Set the port that will be used to connect to the server. +|`target` |string |{nbsp} |Set the target string, which can be either a valid io.grpc.NameResolver + compliant URI, or an authority string. +|`tls` |xref:{rootdir}/config/io_helidon_grpc_core_GrpcTlsDescriptor.adoc[GrpcTlsDescriptor] |{nbsp} |Set the GrpcTlsDescriptor. If `tlsDescriptor` is null or if the `tlsDescriptor.isEnabled()` is false, + then no TLS will be used. + +|=== + +// end::config[] \ No newline at end of file diff --git a/docs/config/io_helidon_grpc_core_GrpcTlsDescriptor.adoc b/docs/config/io_helidon_grpc_core_GrpcTlsDescriptor.adoc new file mode 100644 index 00000000000..ac508a5292b --- /dev/null +++ b/docs/config/io_helidon_grpc_core_GrpcTlsDescriptor.adoc @@ -0,0 +1,53 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2022 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. + +/////////////////////////////////////////////////////////////////////////////// + +ifndef::rootdir[:rootdir: {docdir}/..] +:description: Configuration of io.helidon.grpc.core.GrpcTlsDescriptor +:keywords: helidon, config, io.helidon.grpc.core.GrpcTlsDescriptor +:basic-table-intro: The table below lists the configuration keys that configure io.helidon.grpc.core.GrpcTlsDescriptor +include::{rootdir}/includes/attributes.adoc[] + += GrpcTlsDescriptor (grpc.core) Configuration + +// tag::config[] + + +Type: link:{javadoc-base-url}/io.helidon.grpc.core/io/helidon/grpc/core/GrpcTlsDescriptor.html[io.helidon.grpc.core.GrpcTlsDescriptor] + + + + +== Configuration options + + + +Optional configuration options: +[cols="3,3,2,5a"] + +|=== +|key |type |default value |description + +|`enabled` |boolean |`true` |Enable or disable TLS. If enabled is false then the rest of the TLS configuration properties are ignored. +|`jdk-ssl` |boolean |{nbsp} |Sets the type of SSL implementation to be used. +|`tls-ca-cert` |xref:{rootdir}/config/io_helidon_common_configurable_Resource.adoc[Resource] |{nbsp} |Set the CA (certificate authority) certificate path. +|`tls-cert` |xref:{rootdir}/config/io_helidon_common_configurable_Resource.adoc[Resource] |{nbsp} |Set the client tlsCert path. Required only if mutual auth is desired. +|`tls-key` |xref:{rootdir}/config/io_helidon_common_configurable_Resource.adoc[Resource] |{nbsp} |Set the client private key path. Required only if mutual auth is desired. + +|=== + +// end::config[] \ No newline at end of file diff --git a/docs/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc b/docs/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc new file mode 100644 index 00000000000..9c02067f5e7 --- /dev/null +++ b/docs/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2022 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. + +/////////////////////////////////////////////////////////////////////////////// + +ifndef::rootdir[:rootdir: {docdir}/..] +:description: Configuration of io.helidon.grpc.server.GrpcServerConfiguration +:keywords: helidon, config, io.helidon.grpc.server.GrpcServerConfiguration +:basic-table-intro: The table below lists the configuration keys that configure io.helidon.grpc.server.GrpcServerConfiguration +include::{rootdir}/includes/attributes.adoc[] + += GrpcServerConfiguration (grpc.server) Configuration + +// tag::config[] + + +Type: link:{javadoc-base-url}/io.helidon.grpc.server/io/helidon/grpc/server/GrpcServerConfiguration.html[io.helidon.grpc.server.GrpcServerConfiguration] + + + + +== Configuration options + + + +Optional configuration options: +[cols="3,3,2,5a"] + +|=== +|key |type |default value |description + +|`name` |string |`grpc.server` |Name of the gRPC server +|`native` |boolean |`false` |Specify if native transport should be used. +|`port` |int |`1408` |Specify the gRPC server port +|`workers` |string |`Number of processors available to the JVM` |Specify the count of threads in pool used to process HTTP requests. + +|=== + +// end::config[] \ No newline at end of file diff --git a/docs/mp/grpc/mp-clients.adoc b/docs/mp/grpc/client.adoc similarity index 69% rename from docs/mp/grpc/mp-clients.adoc rename to docs/mp/grpc/client.adoc index ca6a3e5379c..c0f0b6a2a82 100644 --- a/docs/mp/grpc/mp-clients.adoc +++ b/docs/mp/grpc/client.adoc @@ -16,17 +16,26 @@ /////////////////////////////////////////////////////////////////////////////// -= gRPC MicroProfile Clients += gRPC MicroProfile Client :description: Building Helidon gRPC MicroProfile Clients -:keywords: helidon, grpc, microprofile, micro-profile +:keywords: helidon, java, grpc, microprofile, micro-profile, mp :feature-name: gRPC MicroProfile Clients :rootdir: {docdir}/../.. :microprofile-bundle: false include::{rootdir}/includes/mp.adoc[] +== Contents + +- <> +- <> +- <> +- <> +- <> + +== Overview Building Java gRPC clients using the Helidon MP gRPC APIs is very simple and removes a lot of the boiler plate code typically -associated to more traditional approaches to writing gRPC Java clients. At it simplest a gRPC Java client can be written using +associated to more traditional approaches of writing gRPC Java clients. At its simplest, a gRPC Java client can be written using nothing more than a suitably annotated interface. include::{rootdir}/includes/dependencies.adoc[] @@ -39,105 +48,32 @@ include::{rootdir}/includes/dependencies.adoc[] ---- +== API -== Building a gRPC Client -There are a few steps to building and using a gRPC client in Helidon MP. +The following annotations are used to work with Helidon MP gRPC Clients: -As discussed in the section on xref:mp-server-side-services.adoc[Server-Side Services] there are four different types of gRPC method. +* `@GrpcChannel` - an annotation used to inject a gRPC channel. +* `@InProcessGrpcChannel` - an annotation used to tell the Helidon MP gRPC API to inject an in-process channel. +* `@GrpcProxy` - an annotation used to mark an injection point for a gRPC service client proxy. +* `@Grpc` - an annotation used to mark a class as representing a gRPC service. -* Unary - a simple method with at most a single request value and returning at most a single response value. -* Server Streaming - a method that takes at most a single request value but may return zero or more response values. -* Client Streaming - a request that takes one or more request values and returns at most one response value. -* Bi-directional Streaming - a method that can take one or more request values and return zero or more response values. - -An as with the server-side APIS, the Helidon MP gRPC client APIs support a number of different method signatures for each of the -different gRPC method types. - -=== The Client Service Interface -The next step is to produce an interface with the service methods that the client requires. - -For example, suppose we have a simple server side service that has a unary method to convert a string to uppercase. -[source,java] -.Simple gRPC Service ----- -@ApplicationScoped -@io.helidon.microprofile.grpc.core.Grpc -public interface StringService { - - @io.helidon.microprofile.grpc.core.Unary - public String upper(String s) { - return s == null ? null : s.toUpperCase(); - } -} ----- - -The service has been written using the Helidon MP APIs but could just as easily be a traditional gRPC Java service generated from -Protobuf files. The client API is agnostic of the server side implementation, it only cares about the method type, the request -and response types and the type of Marshaller used to serialize the request and response. - -To write a client for the StringService all that is required is an interface. - -[source,java] -.Simple gRPC Service ----- -@ApplicationScoped -@io.helidon.microprofile.grpc.core.Grpc -public interface StringService { - - @io.helidon.microprofile.grpc.core.Unary - public String upper(String s); -} ----- - -There is no need to write any code to implement the client. The Helidon MP gRPC APIs will create a dynamic proxy for the interface -using the information from the annotations and method signatures. - -The interface in the example above used the same method signature as the server but this does not have to be the case, the -interface could have used any supported signature for a unary method, so for example it could just have easily been the standard -unary method signature: - -[source,java] -.Simple gRPC Service ----- -@ApplicationScoped -@io.helidon.microprofile.grpc.core.Grpc -public interface StringService { - - @io.helidon.microprofile.grpc.core.Unary - public void upper(String s, StreamObserver response); -} ----- - -We could also have made the client asynchronous by using one of the async method signatures: - -[source,java] -.Simple gRPC Service ----- -@ApplicationScoped -@io.helidon.microprofile.grpc.core.Grpc -public interface StringService { - - @io.helidon.microprofile.grpc.core.Unary - public CompletableFuture upper(String s); -} ----- - - -=== Configuring Channels -For a gRPC client to connect to a server it requires a Channel. The Helidon MP gRPC APIs provide a way to inject channels into +== Configuration +For a gRPC client to connect to a server, it requires a Channel. The Helidon MP gRPC APIs provide a way to inject channels into CDI beans that require them. +include::{rootdir}/config/io_helidon_grpc_client_GrpcChannelDescriptor.adoc[leveloffset=1, tag=config] + Channels are configured in the `grpc` section of the Helidon application configuration. The examples below use an `application.yaml` file but there are many other ways to use and override xref:../config/introduction.adoc[configuration in Helidon] +.General form of gRPC Channels configuration [source,yaml] -.application.yaml ---- grpc: - channels: <1> - test-server: <2> - host: localhost <3> - port: 1408 <4> + channels: # <1> + test-server: # <2> + host: localhost # <3> + port: 1408 # <4> ---- <1> Channels are configured in the`channels` section <2> Each sub-section is the Channel name that is then used to refer to this Channel in the application code @@ -146,8 +82,9 @@ grpc: While most client application only connect to a single server it is possible to configure multiple named channels if the client needs to connect to multiple servers. + +.Multiple gRPC Channels configuration example [source,yaml] -.application.yaml ---- grpc: channels: @@ -160,22 +97,24 @@ grpc: ---- The above example shows two channel configurations, one named `london` and the other `new-york`. -==== Configuring TLS +=== Configuring TLS It is also possible to configure a Channel to use TLS if the server is using TLS. +include::{rootdir}/config/io_helidon_grpc_core_GrpcTlsDescriptor.adoc[leveloffset=3] + +.TLS on gRPC Channels configuration example [source,yaml] -.application.yaml ---- grpc: channels: test-server: host: localhost port: 1408 - tls: <1> - enabled: true <2> - tls-cert-path: /certs/foo.cert <3> - tls-key-path: /certs/foo.key <4> - tls-ca-cert-path: /certs/ca.cert <5> + tls: # <1> + enabled: true # <2> + tls-cert-path: /certs/foo.cert # <3> + tls-key-path: /certs/foo.key # <4> + tls-ca-cert-path: /certs/ca.cert # <5> ---- <1> The `tls` section of the channel configuration is used to configure TLS. <2> The `enabled` value is used to enable or disable TLS for this channel. @@ -189,34 +128,36 @@ as a file. If `/certs/foo.cert` was a resource on the classpath the configuratio `tls-cert-resource-path` to load `/certs/foo.cert` from the classpath. The same applies to the `tls-key` and `tls-ca-cert` configuration keys. See the `io.helidon.common.configurable.Resource` class for details. +== Usage === Using Channels -Once one or more channels have been configured they can be used by client code. The simplest way to use a channel is to inject it -into beans using CDI. The Helidon gRPC client APIs have CDI producers that can provide `io.grpc.Channel` instances. +Once one or more channels have been configured, then they can be used by client code. The simplest way to use a channel is +to inject it into beans using CDI. The Helidon gRPC client APIs have CDI producers that can provide `io.grpc.Channel` instances. For example, a class might have an injectable `io.grpc.Channel` field: -[source,java] + .gRPC Channel Injection +[source,java] ---- - @Inject <1> - @GrpcChannel(name = "test-server") <2> + @Inject // <1> + @GrpcChannel(name = "test-server") // <2> private Channel channel; ---- <1> The `@Inject` annotation tells CDI to inject the channel. <2> The `@GrpcChannel` annotation is the qualifier that supplies the Channel name. This is the same name as used in the channel -configuration in the configuration examples above. +configuration in the examples provided in the <>. -When an instance of the CDI bean with the channel field is instantiated a channel will be injected into it. +When an instance of the CDI bean with the channel field is instantiated, a channel will be injected into it. ==== The In-Process Channel -If code is running in an application that is executing as part of a Helidon MP gRPC server there is a special in-process channel +If code is running in an application that is executing as part of a Helidon MP gRPC server, there is a special in-process channel available. This allows code executing on the server to make calls to gRPC services deployed on that server in the same way an -external client does. To inject an in-process channel a different qualifier annotation is used. +external client does. To inject an in-process channel, a different qualifier annotation is used. -[source,java] .gRPC in-Process Channel Injection +[source,java] ---- - @Inject <1> - @InProcessGrpcChannel <2> + @Inject // <1> + @InProcessGrpcChannel // <2> private Channel channel; ---- <1> The `@Inject` annotation is used the same as previously. @@ -224,10 +165,10 @@ external client does. To inject an in-process channel a different qualifier anno === Using the Client Interface in an Application -Now that there is a client interface and a Channel configuration we can use these in the client application. The simplest way is +Now that there is a client interface and a Channel configuration, we can then use these in the client application. The simplest way is to use the client in a CDI microprofile application. -In the application class that requires the client we can declare a field of the same type as the client service interface. +We can declare a field of the same type as the client service interface in the application class that requires the client. The field is then annotated so that CDI will inject the client proxy into the field. [source,java] @@ -236,10 +177,11 @@ The field is then annotated so that CDI will inject the client proxy into the fi @ApplicationScoped public class Client { - @Inject <1> - @GrpcProxy <2> - @GrpcChannel(name = "test-server") <3> + @Inject // <1> + @GrpcProxy // <2> + @GrpcChannel(name = "test-server") // <3> private StringService stringService; +} ---- <1> The `@Inject` annotation tells the CDI to inject the client implementation; the gRPC MP APIs have a bean provider that does this. @@ -247,8 +189,90 @@ public class Client { <3> The `@GrpcChannel` annotation identifies the gRPC channel to be used by the client. The name used in the annotation refers to a channel name in the application configuration. -Now when the CDI container instantiates instances of the `Client` it will inject a dynamic proxy into the `stringService` field +When the CDI container instantiates instances of the `Client`, it will inject a dynamic proxy into the `stringService` field and then any code in methods in the `Client` class can call methods on the `StringService` which will be translated to gRPC calls. -In the example above there is no need to directly use a `Channel` directly. The correct channel is added to the dynamic client +In the example above there is no need to use a `Channel` directly. The correct channel is added to the dynamic client proxy internally by the Helidon MP gRPC APIs. + +=== Building a gRPC Client +There are a few steps to building and using a gRPC client in Helidon MP. + +As discussed in the xref:server-side-services.adoc#_defining_service_methods[Defining Service methods] section of the xref:server-side-services.adoc[Server-Side Services] there are four different types of gRPC method. + +* Unary - a simple method with at most a single request value and returning at most a single response value. +* Server Streaming - a method that takes at most a single request value but may return zero or more response values. +* Client Streaming - a request that takes one or more request values and returns at most one response value. +* Bi-directional Streaming - a method that can take one or more request values and return zero or more response values. + +And as with the server-side APIs, the Helidon MP gRPC client APIs support a number of different method signatures for each of the +different gRPC method types. + +==== The Client Service Interface +The next step is to produce an interface with the service methods that the client requires. + +For example, suppose we have a simple server side service that has a unary method to convert a string to uppercase. +[source,java] +.Simple gRPC Service +---- +@ApplicationScoped +@io.helidon.microprofile.grpc.core.Grpc +public interface StringService { + + @io.helidon.microprofile.grpc.core.Unary + public String upper(String s) { + return s == null ? null : s.toUpperCase(); + } +} +---- + +The service has been written using the Helidon MP APIs but could just as easily be a traditional gRPC Java service generated from +Protobuf files. The client API is agnostic of the server side implementation, it only cares about the method type, the request +and response types and the type of Marshaller used to serialize the request and response. + +To write a client for the StringService all that is required is an interface. + +[source,java] +.Simple gRPC Service +---- +@ApplicationScoped +@io.helidon.microprofile.grpc.core.Grpc +public interface StringService { + + @io.helidon.microprofile.grpc.core.Unary + public String upper(String s); +} +---- + +There is no need to write any code to implement the client. The Helidon MP gRPC APIs will create a dynamic proxy for the interface +using the information from the annotations and method signatures. + +The interface in the example above used the same method signature as the server but this does not have to be the case. It +could have used any supported signature for a unary method. For example, it could just have easily been written using the standard +unary method signature: + +[source,java] +.Simple gRPC Service +---- +@ApplicationScoped +@io.helidon.microprofile.grpc.core.Grpc +public interface StringService { + + @io.helidon.microprofile.grpc.core.Unary + public void upper(String s, StreamObserver response); +} +---- + +We could also have made the client asynchronous by using one of the async method signatures: + +[source,java] +.Simple gRPC Service +---- +@ApplicationScoped +@io.helidon.microprofile.grpc.core.Grpc +public interface StringService { + + @io.helidon.microprofile.grpc.core.Unary + public CompletableFuture upper(String s); +} +---- diff --git a/docs/mp/grpc/mp-server-side-services.adoc b/docs/mp/grpc/server-side-services.adoc similarity index 73% rename from docs/mp/grpc/mp-server-side-services.adoc rename to docs/mp/grpc/server-side-services.adoc index ef0660643da..e24b05efb7d 100644 --- a/docs/mp/grpc/mp-server-side-services.adoc +++ b/docs/mp/grpc/server-side-services.adoc @@ -18,16 +18,24 @@ = gRPC MicroProfile Server Services :description: Helidon gRPC MicroProfile Server-Side Services -:keywords: helidon, grpc, microprofile, micro-profile +:keywords: helidon, java, grpc, microprofile, micro-profile, mp :feature-name: gRPC MicroProfile Server :rootdir: {docdir}/../.. :microprofile-bundle: false include::{rootdir}/includes/mp.adoc[] +== Contents + +- <> +- <> +- <> +- <> + +== Overview The gRPC Microprofile APIs are an extension to xref:../introduction/introduction.adoc[Helidon MP] to allow building of gRPC services and clients that integrate with the Microprofile APIs. Using Helidon gRPC MP makes building gRPC services -and clients an easier process that the traditional approach using Protobuf files and code generation. Services can be built +and clients an easier process compared to the traditional approach using Protobuf files and code generation. Services can be built using POJOs that are then discovered and deployed at runtime in the same way the Helidon MP discovers and deploys web resources in the MP http server. @@ -44,7 +52,22 @@ include::{rootdir}/includes/dependencies.adoc[] ---- -== Defining a Service + +== API +The following annotations are used to implement Helidon MP gRPC Services: + +* `@Grpc` - an annotation used to mark a class as representing a gRPC service. + +gRPC types of method: + +* <> - a simple method with at most a single request value and returning at most a single response value. +* <> - a method that takes at most a single request value but may return zero or more response values. +* <> - a request that takes one or more request values and returns at most one response value. +* <> - a method that can take one or more request values and return zero or more response values. + + +== Usage +=== Defining a Service The traditional approach to building Java gRPC services is to write Protobuf files describing the service and then use these to generate service stubs and finally implementing the service methods by extending the generated stub classes. @@ -66,24 +89,25 @@ public class StringService { ---- The code above is a simple service with a single unary method that just converts a String to uppercase. -The important parts in the example are the `@ApplicationScoped`, `@Grpc` and `@Unary` annotations; these, +The important parts in the example are the `@ApplicationScoped`, `@Grpc` and `@Unary` annotations. These, along with other annotations discussed later, allow the gRPC MP APIs to discover, configure and deploy the service. Of course Helidon gRPC MP does not preclude you from using the Protobuf files approach, traditional gRPC Java services also work in a gRPC MP server. -As already shown above a Helidon gRPC MP service is just an annotated POJO. To make a class a service it requires two +As already shown above, a Helidon gRPC MP service is just an annotated POJO. To make a class a service, it requires two annotations. [source,java] ---- -@ApplicationScoped <1> -@io.helidon.microprofile.grpc.core.Grpc <2> +@ApplicationScoped // <1> +@io.helidon.microprofile.grpc.core.Grpc // <2> public class StringService { +} ---- <1> The `ApplicationScoped` annotation is what makes the service implementation a CDI bean and hence discoverable. -<2> The `Grpc` annotation is what defines the class as a gRPC service so that when the bean is discovered it is +<2> The `Grpc` annotation is what defines the class as a gRPC service so that when the bean is discovered, it is then deployed by the gRPC MP server. === Service Name @@ -93,20 +117,20 @@ above the service name will be `StringService`. This can be change by supplying [source,java] ---- @ApplicationScoped -@io.helidon.microprofile.grpc.core.Grpc(name="Strings") <1> +@io.helidon.microprofile.grpc.core.Grpc(name="Strings") // <1> public class StringService { ---- -<1> in the example above the name of the deployed service will be `Strings`. +<1> in the example above, the name of the deployed service will be `Strings`. -== Defining Service Methods -Once a class is properly annotated to make it a gRPC MP service it needs to have service methods that implement the +=== Defining Service Methods +Once a class is properly annotated to make it a gRPC MP service, it needs to have service methods that implement the application business logic. In gRPC there are four different types of method: -* Unary - a simple method with at most a single request value and returning at most a single response value. -* Server Streaming - a method that takes at most a single request value but may return zero or more response values. -* Client Streaming - a request that takes one or more request values and returns at most one response value. -* Bi-directional Streaming - a method that can take one or more request values and return zero or more response values. +* `Unary` - a simple method with at most a single request value and returning at most a single response value. +* `Server Streaming` - a method that takes at most a single request value but may return zero or more response values. +* `Client Streaming` - a request that takes one or more request values and returns at most one response value. +* `Bi-directional Streaming` - a method that can take one or more request values and return zero or more response values. The Helidon gRPC MP API determines a method type by its annotation, which should be one of the following: [source,java] @@ -117,17 +141,17 @@ The Helidon gRPC MP API determines a method type by its annotation, which should @io.helidon.microprofile.grpc.core.Bidirectional ---- -=== Request an Response Types +==== Request and Response Types A gRPC service method typically takes a request parameter and returns a response value (streaming methods may take or return -multiple requests or responses). In traditional gRPC Java the types used for the request and response values must be +multiple requests or responses). In traditional gRPC Java, the types used for the request and response values must be Protobuf serializable classes but this is not the case with Helidon gRPC. Helidon supports xref:../../se/grpc/marshalling.adoc[pluggable Marshallers] and by default will support any Java primitive or Java `Serializable` as well as Protobuf types. Any type that can be marshalled by the built-in marshallers or custom supplied marshaller may be used as a request or response type. -=== Unary Methods +==== Unary Methods A unary gRPC method is the simplest type of service method. Typically a unary method takes a request value and returns a -response value but this does not have to be the case, a unary method could just as easily take no request parameter and/or +response value but this does not have to be the case. A unary method could just as easily take no request parameter and/or return no response. All of the signatures below are valid unary methods in Helidon gRPC MP. @@ -182,10 +206,10 @@ The various signatures supported above allow the service developer to choose the application business logic without needing to worry about handling standard gRPC Java requests and StreamObservers. The standard gRPC Java method signature is in the list above so it can still be used if required. -=== ServerStreaming Methods -A server streaming method receives a requests from the client and when the request stream is complete it sends back a stream +==== ServerStreaming Methods +A server streaming method receives a requests from the client and when the request stream is complete, it sends back a stream of response values. A traditional gRPC Java server streaming method takes two parameters, the request and a `StreamObserver` -that is used to send back the single response in the same way that a unary method sends a response. As with unary methods +that is used to send back the single response in the same way that a unary method sends a response. As with unary methods, Helidon gRPC MP supports different method signatures for server streaming methods. All of the signatures below are valid server streaming methods in Helidon gRPC MP. @@ -212,8 +236,8 @@ public Stream invoke(RequestType req) As with unary methods, the Helidon gRPC MP API supports multiple different method signatures for implementing server streaming methods. -=== ClientStreaming Methods -A client streaming method receives a stream of requests from the client and when the request stream is complete it sends back a +==== ClientStreaming Methods +A client streaming method receives a stream of requests from the client and when the request stream is complete, it sends back a response. A traditional gRPC Java client streaming method takes two `StreamObserver` parameters, one is the stream of client requests and the other is used to send back the single response in the same way that a unary method sends a response. As with unary methods Helidon gRPC MP supports different method signatures for client streaming methods. @@ -232,9 +256,9 @@ public StreamObserver invoke(CompletableFuture observ ---- -=== Bi-Directional Streaming Methods +==== Bi-Directional Streaming Methods A bidirectional streaming method is a method that is a constant stream of client requests and server responses. Other than -the standard gRPC Java `StreamObserver` there are not any other built in types that make sense to use to implement +the standard gRPC Java `StreamObserver`, there are not any other built in types that make sense to use to implement different method signatures for a bidirectional method so the only supported signature is the standard gRPC Java method. [source,java] @@ -243,15 +267,15 @@ different method signatures for a bidirectional method so the only supported sig public StreamObserver invoke(StreamObserver observer) ---- -== Deploying Protobuf Services -Whilst the examples above show how simple it is to write gRPC services with basic POJOs there may be cases where there is a +=== Deploying Protobuf Services +Whilst the examples above show how simple it is to write gRPC services with basic POJOs, there may be cases where there is a requirement to deploy services built the traditional way using gRPC Java generated classes or built as -xref:../../se/grpc/service-implementation.adoc[non-microprofile Helidon gRCP services]. +xref:../../se/grpc/server.adoc#_service_implementation[non-microprofile Helidon gRCP services]. -=== Annotate the Service Implementation -When the gRPC MP server is starting it will discover all CDI beans of type `io.grpc.BindableService`. Service sub-classes +==== Annotate the Service Implementation +When the gRPC MP server is starting, it will discover all CDI beans of type `io.grpc.BindableService`. Service sub-classes implemented the traditional way with code generation are instances of `BindableService` so by annotating the implementation class -with the `@ApplicationScoped` annotation they become discoverable and will be deployed into the gRPC server. +with the `@ApplicationScoped` annotation, they become discoverable and will be deployed into the gRPC server. [source,java] ---- @@ -260,8 +284,7 @@ public class StringService extends StringServiceGrpc.StringServiceImplBase { ---- -In exactly the same way, if a class is an implementation of `io.helidon.grpc.server.GrpcService` then by annotating the class with -the `@ApplicationScoped` annotation it will be discovered and deployed when the MP gRPC server starts. +In exactly the same way, if a class is an implementation of `io.helidon.grpc.server.GrpcService`, then it will be discovered and deployed when the MP gRPC server starts by simply annotating the class with the `@ApplicationScoped` annotation. [source,java] ---- @@ -269,14 +292,13 @@ the `@ApplicationScoped` annotation it will be discovered and deployed when the public class StringService implements GrpcService { ---- -=== Implement a GrpcMpExtension -If it is not possible to annotate the service class (for example the code is built by a third party) another way to deploy none +==== Implement a GrpcMpExtension +If it is not possible to annotate the service class (for example the code is built by a third party), another way to deploy none CDI bean services is to implement a gRPC MP server extension. The extension will then be called when the MP server is starting and be given the chance to add additional services for deployment. An extension should implement the `io.helidon.microprofile.grpc.server.spi.GrpcMpExtension` interface. -For example, assuming that there was a gRPC service class called `StringService` that needed to be deployed an extension class -might look like this: +For example, assuming that there was a gRPC service class called `StringService` that needed to be deployed, an extension class might look like this: [source,java] ---- public class MyExtension @@ -290,9 +312,9 @@ public class MyExtension ---- <1> The `configure` method of the extension will be called to allow the extension to add extra configuration to the server. -<2> In this example an instance of the `StringService` is registered with the routing (as described in -the xref:../../se/grpc/routing.adoc[basic gRPC server documentation]). +<2> In this example, an instance of the `StringService` is registered with the routing (as described in +the xref:../../se/grpc/server.adoc#_grpc_server_routing[basic gRPC server documentation about Routing]). -The `GrpcMpExtension` instances are discovered and loaded using the service loader so for the example above to work a file +The `GrpcMpExtension` instances are discovered and loaded using the service loader so for the example above to work, a file `META-INF/services/io.helidon.microprofile.grpc.server.spi.GrpcMpExtension` would need to be created that contained the names of the service implementations. diff --git a/docs/mp/introduction/introduction.adoc b/docs/mp/introduction/introduction.adoc index d1c818c0c6a..90f3ff4f907 100644 --- a/docs/mp/introduction/introduction.adoc +++ b/docs/mp/introduction/introduction.adoc @@ -98,7 +98,7 @@ Expose GraphQL API using Microprofile GraphQL. //gRPC [CARD] .gRPC -[icon=swap_horiz,link=../grpc/mp-server-side-services.adoc] +[icon=swap_horiz,link=../grpc/server-side-services.adoc] -- Build gRPC servers and clients. -- diff --git a/docs/se/grpc/client-configuration.adoc b/docs/se/grpc/client-configuration.adoc deleted file mode 100644 index 6118f3fca65..00000000000 --- a/docs/se/grpc/client-configuration.adoc +++ /dev/null @@ -1,59 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// - - Copyright (c) 2019, 2022 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. - -/////////////////////////////////////////////////////////////////////////////// - -= gRPC Client Configuration -:description: Helidon gRPC Client Configuration -:keywords: helidon, grpc, java, configuration -:rootdir: {docdir}/../.. - -include::{rootdir}/includes/se.adoc[] - -Configure the gRPC client using the Helidon configuration framework, either programmatically or via a configuration file. - -As mentioned earlier, creating a `GrpcServiceClient` involves: - -1. Creating a `ClientServiceDescriptor` which describes the methods in the service that this client can invoke. -2. Creating a gRPC `Channel` through which the client communicates with the server. - -== Configuring the ClientServiceDescriptor - -=== Configuring the ClientServiceDescriptor in your code - -The only way to configure the `ClientServiceDescriptor` is in your application code. - -[source,java] ----- -ClientServiceDescriptor descriptor = ClientServiceDescriptor + - .builder(HelloService.class) // (1) - .unary("SayHello") // (2) - .build(); // (3) ----- - -1. Create a builder for a `ClientServiceDescriptor` for the `HelloService`. -2. Specify that the `HelloService` has a unary method named `SayHello`. There are many other methods in this class that allow you -to define `ClientStreaming`, `ServerStreaming` and `Bidirectional` methods. -3. Build the `ClientServiceDescriptor`. - -== Configuring the gRPC Channel - -gRPC allows various channel configurations (deadlines, retries, interceptors etc.) - -Please refer to gRPC documentation: https://grpc.io/grpc-java/javadoc/io/grpc/ManagedChannelBuilder.html. - - - diff --git a/docs/se/grpc/client-introduction.adoc b/docs/se/grpc/client-introduction.adoc deleted file mode 100644 index 2914db3ce11..00000000000 --- a/docs/se/grpc/client-introduction.adoc +++ /dev/null @@ -1,88 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// - - Copyright (c) 2019, 2022 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. - -/////////////////////////////////////////////////////////////////////////////// - -= gRPC Client Introduction -:description: Helidon gRPC Client Introduction -:keywords: helidon, grpc, java -:feature-name: gRPC Client -:rootdir: {docdir}/../.. - -include::{rootdir}/includes/se.adoc[] - -Helidon gRPC Client provides a framework for creating link:http://grpc.io/[gRPC] client applications. The client framework -allows a uniform way to access gRPC services that use either Protobuf or some custom serialization format. It also allows access -to gRPC services that use either Java serialization, Protobuf or a custom serialization format. - -The class `GrpcServiceClient` acts as the client object for accessing a gRPC service. Creating a `GrpcServiceClient` involves: - -1. Creating a `ClientServiceDescriptor` which describes the methods in the service that this client can invoke. -2. Creating a gRPC `Channel` through which the client communicates with the server. - -In later sections in this document, you will see how to customize both `ClientServiceDescriptor` and the `Channel`. - -include::{rootdir}/includes/dependencies.adoc[] - -[source,xml] ----- - - io.helidon.grpc - helidon-grpc-client - ----- - -== Quick Start - -First, create and run a minimalist `HelloService` gRPC server application as described in the -xref:introduction.adoc[gRPC Server] documentation. - -Assuming that the server is running on port 1408, create a client as follows: - -[source,java] ----- -public static void main(String[] args) throws Exception { - ClientServiceDescriptor descriptor = ClientServiceDescriptor.builder(HelloService.class) // (1) - .unary("SayHello") // (2) - .build(); - - Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408) // (3) - .usePlaintext() - .build(); - - GrpcServiceClient client = GrpcServiceClient.create(channel, descriptor); // (4) - - CompletionStage future = client.unary("SayHello", "Helidon gRPC!!"); // (5) - System.out.println(future.get()); // (6) - -} ----- - -1. Create a `ClientServiceDescriptor` for the `HelloService`. -2. Add the `SayHello` unary method to the `ClientServiceDescriptor`. This method, by default, uses Java serialization for -marshalling and unmarshalling the request and response values. -3. Create a gRPC `Channel` that is communicates with the server that is running in localhost and on port 1408 (using plaintext). -4. Create the `GrpcServiceClient` that uses the above `Channel` and `ClientServiceDescriptor`. `GrpcClientService` represents -a client that can be used to define the set of methods described by the specified `ClientServiceDescriptor`. In our case, the -`ClientServiceDescriptor` defines one unary method called `SayHello`. -5. Invoke the `SayHello` method which returns a `CompletionStage`. -6. Print the result. - -The example above creates a very simple client to the gRPC server that by default uses Java serialization to marshall -requests and responses. - -We will look into deployment of "standard" gRPC services that use Protobuf for request and response marshalling, as well as -how you can configure custom marshallers, later in this document. diff --git a/docs/se/grpc/client-implementation.adoc b/docs/se/grpc/client.adoc similarity index 57% rename from docs/se/grpc/client-implementation.adoc rename to docs/se/grpc/client.adoc index 7fa9dcdca12..dd1c4ed97e7 100644 --- a/docs/se/grpc/client-implementation.adoc +++ b/docs/se/grpc/client.adoc @@ -16,48 +16,70 @@ /////////////////////////////////////////////////////////////////////////////// -= gRPC Client Implementation -:description: Helidon gRPC Client Implementation -:keywords: helidon, grpc, java += gRPC Client +:description: Helidon gRPC Client +:keywords: helidon, grpc, java, se +:feature-name: gRPC Client :rootdir: {docdir}/../.. include::{rootdir}/includes/se.adoc[] -Helidon gRPC client framework allows you to write gRPC clients to access any gRPC -service implementation. The benefits of using Helidon gRPC Client Framework include: +== Contents -* It provides a number of helper methods that make client implementation - significantly simpler. +- <> +- <> +- <> +- <> +- <> -* It allows you to configure some of the Helidon value-added features, such - as xref:security.adoc[security] and xref:metrics.adoc[metrics collection and interceptors] - down to the method level. +== Overview + +Helidon gRPC Client provides a framework for creating link:http://grpc.io/[gRPC] client applications. The client framework +allows a uniform way to access gRPC services that use either Protobuf or some custom serialization format. The benefits of using Helidon gRPC Client Framework include: +* It provides a number of helper methods that make client implementation +significantly simpler. +* It allows you to configure some of the Helidon value-added features, such +as xref:server.adoc#_security[security], xref:server.adoc#_service_metrics[metrics collection] and xref:server.adoc#_interceptors[interceptors] down to the method level. * It allows you to easily specify custom marshaller for requests and - responses if `protobuf` does not satisfy your needs. +responses if `protobuf` does not satisfy your needs. -== Client Implementation Basics +The class `GrpcServiceClient` acts as the client object for accessing a gRPC service. Creating a `GrpcServiceClient` involves: -* The first step to create a Helidon gRPC client application is to describe the set of methods in the gRPC service. Helidon -gRPC Client Framework (simply called the "Client framework" in the remainder of the document) provides a class called -`ClientServiceDescriptor` to describe the set of methods of a service that the client may invoke. +1. Creating a `ClientServiceDescriptor` which describes the methods in the service that this client can invoke. +2. Creating a gRPC `Channel` through which the client communicates with the server. + +In later sections in this document, you will see how to customize both `ClientServiceDescriptor` and the `Channel`. -There are three ways to build and initialize a `ClientServiceDescriptor`. -** The first option is to initialize `ClientServiceDescriptor` using `protoc` generated artifacts like +include::{rootdir}/includes/dependencies.adoc[] + +[source,xml] +---- + + io.helidon.grpc + helidon-grpc-client + +---- + +== Usage +=== Client Implementation Basics + +. The first step to create a Helidon gRPC client application is to describe the set of methods in the gRPC service. Helidon +gRPC Client Framework (simply called the "Client framework" in the remainder of the document) provides a class called +`ClientServiceDescriptor` to describe the set of methods of a service that the client may invoke. There are three ways to build and initialize a `ClientServiceDescriptor`. +* The first option is to initialize `ClientServiceDescriptor` using `protoc` generated artifacts like `BindableService` or `io.grpc.ServiceDescriptor`. This option is possible if the gRPC service was built using `.proto` file. In this case the set of gRPC methods, their types and the appropriate marshallers are detected automatically. This is certainly the easiest way to initialize a `ClientServiceDescriptor`. -** The second option is to programmatically build the `ClientServiceDescriptor`. This option should be +* The next option is to programmatically build the `ClientServiceDescriptor`. This option should be taken if the service was *not* built from protobuf files or if the `protoc` generated artifacts are not available to the client. -** The third option is to load the method descriptions from a configuration file. (Not yet implemented). +* The last option is to load the method descriptions from a configuration file. (** Not yet implemented**). +. The next step is to create a gRPC `Channel` to use to communicate with the server. +. Finally, you create an instance of `GrpcServiceClient` passing the `ClientMethodDescriptor` and the `Channel` instances. -* The next step is to create a gRPC `Channel` to use to communicate with the server. - -* Finally, you create an instance of `GrpcServiceClient` passing the `ClientMethodDescriptor` and the `Channel` instances. - -== Creating gRPC clients from `protoc` generated artifacts +=== Creating gRPC clients from `protoc` generated artifacts As mentioned above, the easiest way to create a `ClientServiceDescriptor` is to create it from an `io.grpc.ServiceDescriptor` or from a `io.grpc.BindableService`. It is fairly trivial to obtain these from a service generated from artifacts generated @@ -87,7 +109,7 @@ If you run it through `protoc` it will generate a class (among other things) cal Assuming that the `StringService` server is running on port 1408, here is how you can create a Helidon gRPC Client that uses the Client Framework to invoke various types of gRPC methods. -=== Creating and initializing a ClientServiceDescriptor for StringService (generated from `protoc`) +==== Creating and initializing a ClientServiceDescriptor for StringService (generated from `protoc`) Lets build a class called `ProtoBasedStringServiceClient` that invokes the various types of gRPC methods that our `StringService` offers. @@ -101,22 +123,20 @@ public class ProtoBasedStringServiceClient { public ProtoBasedStringServiceClient() { ClientServiceDescriptor desc = ClientServiceDescriptor - .builder(StringService.getServiceDescriptor()) // (1) + .builder(StringService.getServiceDescriptor()) // <1> .build(); - Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408) // (2) + Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408) // <2> .usePlaintext().build(); - this.client = GrpcServiceClient.create(channel, desc); // (3) + this.client = GrpcServiceClient.create(channel, desc); // <3> } /** * Many gRPC methods take a {@link io.grpc.StreamObserver} as an argument. Lets * build a helper class that can be used in our example. */ - public static class StringMessageStream // (4) - implements StreamObserver { - + public static class StringMessageStream implements StreamObserver { // <4> @Override public void onNext(T value) { System.out.println("Received : " + value); @@ -135,22 +155,19 @@ public class ProtoBasedStringServiceClient { } ---- -1. Initialize the builder by specifying the `StringService`'s proto `ServiceDescriptor`. From -the `ServiceDescriptor` the builder detects the service name, the set of method names, and for -each method its type (like Unary, ServerStreaming etc.), the request and response types (and -hence their corresponding Marshallers) etc. - -2. We create a `Channel` to the service that is running on `localhost:1408`. - -3. Finally, we create our `GrpcServiceClient` by using the above mentioned `ClientServiceDescriptor` +<1> Initialize the builder by specifying the `StringService's` proto `ServiceDescriptor`. From +the `ServiceDescriptor` the builder detects the service name, the set of method names, the type of +each method (like Unary, ServerStreaming, etc.), the request and response types (and +hence their corresponding Marshallers), etc. +<2> We create a `Channel` to the service that is running on `localhost:1408`. +<3> Finally, we create our `GrpcServiceClient` by using the above mentioned `ClientServiceDescriptor` and `Channel`. This `client` reference will be used to invoke various gRPC methods in our `StringService` - -4. We define a static inner class that implements the `io.grpc.StreamObserver` interface. An instance +<4> We define a static inner class that implements the `io.grpc.StreamObserver` interface. An instance of this class can be used whereever a `io.grpc.StreamObserver` is required (like server streaming, bi-directional streaming methods). -=== Invoking a unary method on the StringService +==== Invoking a unary method on the StringService The Client Framework provides many helper methods to invoke gRPC unary methods. @@ -165,29 +182,27 @@ public class ProtoBasedStringServiceClient { public void invokeUnaryMethod() throws Exception { StringMessage input = StringMessage.newBuilder().setText("ABC").build(); - CompletableFuture result = client.unary("Lower", input); // (1) + CompletableFuture result = client.unary("Lower", input); // <1> - String lcase = client.blockingUnary("Lower", input); // (2) + String lcase = client.blockingUnary("Lower", input); // <2> StringMessageStream stream = new StringMessageStream(); - client.blockingUnary("Lower", input); // (3) + client.blockingUnary("Lower", input); // <3> } public static class StringMessageStream { /* code omitted */ } } ---- -1. This variant of the `unary` API takes the method name and a request object and returns +<1> This variant of the `unary` API takes the method name and a request object and returns a `CompletableFuture` where `` is the response type. Here we invoke the `Lower` method passing the input `StringMessage`. This method returns a `CompletableFuture` as response thus allowing the client to obtain the result asynchronously. - -2. This is simply a wrapper around the above method. This method blocks till the result is available. - -3. Here we create invoke the `unary` method by passing the `StringMessageStream` whose `onNext` method +<2> This is simply a wrapper around the above method. This method blocks till the result is available. +<3> Here, we create invoke the `unary` method by passing the `StringMessageStream` whose `onNext` method will be called (once) when the result is available. -=== Invoking a client streaming method on the StringService +==== Invoking a client streaming method on the StringService Lets invoke the `Join` method which causes the server to return a single result *after* the client has streamed the request values to the server. gRPC API expects the client application to provide @@ -211,24 +226,24 @@ public class ProtoBasedStringServiceClient { public void invokeClientStreamingWithIterable() throws Exception { String sentence = "A simple invocation of a client streaming method"; - Collection input = Arrays.stream(sentence.split(" ")) // (1) + Collection input = Arrays.stream(sentence.split(" ")) // <1> .map(w -> StringMessage.newBuilder().setText(w).build()) .collect(Collectors.toList()); CompletableFuture result = - grpcClient.clientStreaming("Join", input); // (2) + grpcClient.clientStreaming("Join", input); // <2> } public void invokeClientStreaming() throws Exception { String sentence = "A simple invocation of a client streaming method"; StringMessageStream responseStream = new StringMessageStream(); StreamObserver clientStream = - grpcClient.clientStreaming("Join", responseStream); // (3) + grpcClient.clientStreaming("Join", responseStream); // <3> for (String word : sentence.split(" ")) { - clientStream.onNext(StringMessage.newBuilder().setText(word).build()); // (4) + clientStream.onNext(StringMessage.newBuilder().setText(word).build()); // <4> } - clientStream.onCompleted(); // (5) + clientStream.onCompleted(); // <5> } public static class StringMessageStream { /* code imitted */ } @@ -236,21 +251,17 @@ public class ProtoBasedStringServiceClient { } ---- -1. We prepare the collection that contains the values to be streamed. - -2. We call the first variant of the `clientStreaming()` method that takes the +<1> We prepare the collection that contains the values to be streamed. +<2> We call the first variant of the `clientStreaming()` method that takes the method name and the collection of values to be streamed from the client. Note: The above helper method is useful if the values to be streamed is fixed and small in number. - -3. If the number of values to be streamed is large (or unknown), then it is better to use this +<3> If the number of values to be streamed is large (or unknown), then it is better to use this variant of the `clientStreaming()` method that takes a `io.grpc.StreamObserver` as an argument. This method returns a client stream through which the client can stream (potentially a large number of) value to the server. - -4. Once the client stream is obtained, the client streams the values using the `onNext()` method on the +<4> Once the client stream is obtained, the client streams the values using the `onNext()` method on the stream. - -5. When all values have been stream, the client invokes the `onCompleted()` method signal that all values +<5> When all values have been stream, the client invokes the `onCompleted()` method signal that all values have been streamed from the client. === Invoking a server streaming method on the StringService (generated from `protoc`) @@ -267,10 +278,10 @@ public class ProtoBasedStringServiceClient { public void invokeServerStreaming() throws Exception { String sentence = "This sentence will be split into words and sent back to client"; - StringMessage input = StringMessage.newBuilder().setText(sentence).build(); // (1) + StringMessage input = StringMessage.newBuilder().setText(sentence).build(); // <1> - StringMessageStream observer = new StringMessageStream<>(); // (2) - grpcClient.serverStreaming("Split", input, observer); // (3) + StringMessageStream observer = new StringMessageStream<>(); // <2> + grpcClient.serverStreaming("Split", input, observer); // <3> } public static class StringMessageStream { /* code imitted */ } @@ -278,11 +289,9 @@ public class ProtoBasedStringServiceClient { } ---- -1. We prepare the input `StringMessage` that needs to be split. - -2. We create a `StringMessageStream` which will receive the results streamed from the server. - -3. We call the `serverStreaming()` passing the input and the `StringMessageStream` as arguments. +<1> We prepare the input `StringMessage` that needs to be split. +<2> We create a `StringMessageStream` which will receive the results streamed from the server. +<3> We call the `serverStreaming()` passing the input and the `StringMessageStream` as arguments. The server sends a stream of words by calling the `onNext()` method on the `StringMessageStream` for each word. @@ -301,15 +310,15 @@ public class ProtoBasedStringServiceClient { public void invokeBidiStreaming() throws Exception { - StringMessageStream observer = new StringMessageStream<>(); // (1) + StringMessageStream observer = new StringMessageStream<>(); // <1> StringMessageStream clientStream = grpcClient - .bidiStreaming("Echo", observer); // (2) + .bidiStreaming("Echo", observer); // <2> String sentence = "Each word will be echoed back to the client by the server"; for (String word : sentence.split(" ")) { - clientStream.onNext(StringMessage.newBuilder().setText(word).build()); // (3) + clientStream.onNext(StringMessage.newBuilder().setText(word).build()); // <3> } - clientStream.onCompleted(); // (4) + clientStream.onCompleted(); // <4> } public static class StringMessageStream { /* code imitted */ } @@ -317,20 +326,17 @@ public class ProtoBasedStringServiceClient { } ---- -1. We create a `StringMessageStream` which will receive the results streamed from the server. - -2. We call the `bidiStreaming()` passing the `observer` as argument. The server will +<1> We create a `StringMessageStream` which will receive the results streamed from the server. +<2> We call the `bidiStreaming()` passing the `observer` as argument. The server will send its results through this stream (basically by calling the `onNext()` on the `observer`). The method returns a (client) stream which should be used by the client to stream values to the server. - -3. We stream each word in our sentence to the server by calling the `onNext()` method on the +<3> We stream each word in our sentence to the server by calling the `onNext()` method on the `clientStream`. - -4. We call the `onCompleted()` method on the `clientStream` to signal that the client has +<4> We call the `onCompleted()` method on the `clientStream` to signal that the client has streamed all its values. -== Programmatically creating ClientServiceDescriptor for StringService +=== Programmatically creating ClientServiceDescriptor for StringService Assuming that the service is still running on port 1408, lets see how to create our Client without using the `StringService` 's proto `ServiceDescriptor`. @@ -351,30 +357,30 @@ public class StringServiceClient { public static void main(String[] args) { ClientMethodDescriptor lower = ClientMethodDescriptor - .unary("StringService", "Lower") // (1) - .requestType(StringMessage.class) // (2) - .responseType(StringMessage.class) // (3) - .build(); // (4) + .unary("StringService", "Lower") // <1> + .requestType(StringMessage.class) // <2> + .responseType(StringMessage.class) // <3> + .build(); // <4> ClientMethodDescriptor join = ClientMethodDescriptor - .clientStreaming("StringService", "Join") // (5) + .clientStreaming("StringService", "Join") // <5> .requestType(StringMessage.class) .responseType(StringMessage.class) .build(); ClientMethodDescriptor split = ClientMethodDescriptor - .serverStreaming("StringService", "Split") // (6) + .serverStreaming("StringService", "Split") // <6> .requestType(StringMessage.class) .responseType(StringMessage.class) .build(); ClientMethodDescriptor echo = ClientMethodDescriptor - .bidirectional("StringService", "Echo") // (7) + .bidirectional("StringService", "Echo") // <7> .requestType(StringMessage.class) .responseType(StringMessage.class) .build(); - ClientServiceDescriptor serviceDesc = ClientServiceDescriptor // (8) + ClientServiceDescriptor serviceDesc = ClientServiceDescriptor // <8> .builder(StringService.class) .unary(lower) .clientStreaming(join) @@ -383,65 +389,56 @@ public class StringServiceClient { .build(); - Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408) // (9) + Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408) // <9> .usePlaintext().build(); - GrpcServiceClient client = GrpcServiceClient.create(channel, serviceDesc); // (10) + GrpcServiceClient client = GrpcServiceClient.create(channel, serviceDesc); // (<10> } } ---- -1. Use the `unary()` method on `ClientMethodDescriptor` to create a builder for a gRPC unary method. +<1> Use the `unary()` method on `ClientMethodDescriptor` to create a builder for a gRPC unary method. The service name and the method name ("Lower") are specified. - -2. Set the request type of the method to be `StringMessage` (since the `Lower` method takes `StringMessage` as a parameter). - -3. Set the response type of the method to be `StringMessage` (since the `Lower` method returns a `StringMessage` as a parameter). - -4. Build the `ClientMethodDescriptor`. Note that the return value is a `ClientMethodDescriptor` that contains +<2> Set the request type of the method to be `StringMessage` (since the `Lower` method takes `StringMessage` as a parameter). +<3> Set the response type of the method to be `StringMessage` (since the `Lower` method returns a `StringMessage` as a parameter). +<4> Build the `ClientMethodDescriptor`. Note that the return value is a `ClientMethodDescriptor` that contains the correct Marshallers for the request & response types. - -5. Use the `clientStreaming()` method on `ClientMethodDescriptor` to create a builder for a gRPC client streaming method. +<5> Use the `clientStreaming()` method on `ClientMethodDescriptor` to create a builder for a gRPC client streaming method. The service name and the method name ("Join") are specified. - -6. Use the `serverStreaming()` method on `ClientMethodDescriptor` to create a builder for a gRPC server streaming method. +<6> Use the `serverStreaming()` method on `ClientMethodDescriptor` to create a builder for a gRPC server streaming method. The service name and the method name ("Split") are specified. - -7. Use the `bidirectional()` method on `ClientMethodDescriptor` to create a builder for a gRPC Bidi streaming method. +<7> Use the `bidirectional()` method on `ClientMethodDescriptor` to create a builder for a gRPC Bidi streaming method. The service name and the method name ("Echo") are specified. - -8. Create a `ClientServiceDescriptor` for service named `StringService` and add all our `ClientMethodDescriptor` s. - -9. We create a `Channel` to the service that is running on `localhost:1408`. - -10. Finally, we create our `GrpcServiceClient` by using the above mentioned `ClientServiceDescriptor` +<8> Create a `ClientServiceDescriptor` for service named `StringService` and add all our `ClientMethodDescriptor` s. +<9> We create a `Channel` to the service that is running on `localhost:1408`. +<10> Finally, we create our `GrpcServiceClient` by using the above mentioned `ClientServiceDescriptor` and `Channel`. At this point the `client` object can be used to invoke any of the four types of methods we have seen in the -earlier sections!! +earlier sections. -== Creating gRPC clients for non protobuf services +=== Creating gRPC clients for non protobuf services If your service is *not* using protobuf for serialization, then the Client framework allows you to programmatically initialize `ClientMethodDescriptor` and create clients to invoke methods on the service. -All you have to do is create the set of `ClientMethodDescriptor` s and the `ClientServiceDescriptor` as -described in the previous section, but with one change. Just *do not* to set the request and response types +All you have to do is create the set of `ClientMethodDescriptor's` and the `ClientServiceDescriptor` as +described in the previous section, but with one change. Just *do not* set the request and response types in the `ClientMethodDescriptor`. That's all!! In fact, there is an API in the `ClientServiceDescriptor` that makes this even simpler. You can simply pass the method name. For example, to create a client streaming -method called "JoinString" that uses java serialization simply call the `clientStreamin("JoinString")`. +method called `"JoinString"` that uses java serialization, simply call the `clientStreaming("JoinString")`. Lets see an example of creating a client for a service that uses Java serialization. [source,java] ---- public static void main(String[] args) throws Exception { - ClientServiceDescriptor descriptor = ClientServiceDescriptor.builder(HelloService.class) // (1) - .clientStreaming("JoinString") // (2) + ClientServiceDescriptor descriptor = ClientServiceDescriptor.builder(HelloService.class) // <1> + .clientStreaming("JoinString") // <2> .build(); Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408) @@ -459,12 +456,84 @@ public static void main(String[] args) throws Exception { } ---- -1. Create a `ClientServiceDescriptor` for the `HelloService`. -2. Add the "JoinString" client streaming method to the `ClientServiceDescriptor`. Since we didn't set +<1> Create a `ClientServiceDescriptor` for the `HelloService`. +<2> Add the "JoinString" client streaming method to the `ClientServiceDescriptor`. Since we didn't set the request or response type (like we did in the previous sections), Java serialization will be used for Marshalling and Unmarshalling the request and response values. Note that whether a `ClientServiceDescriptor` is built using protobuf artifacts or is built programmatically, the same set of APIs provided by the Client Framework can be used to invoke gRPC methods. +include::marshalling.adoc[leveloffset=2] + +== Configuration +Configure the gRPC client using the Helidon configuration framework, either programmatically or via a configuration file. +As mentioned earlier, creating a `GrpcServiceClient` involves: + +1. Creating a `ClientServiceDescriptor` which describes the methods in the service that this client can invoke. +2. Creating a gRPC `Channel` through which the client communicates with the server. + +=== Configuring the ClientServiceDescriptor + +The only way to configure the `ClientServiceDescriptor` is in your application code. + +[source,java] +---- +ClientServiceDescriptor descriptor = ClientServiceDescriptor + .builder(HelloService.class) // <1> + .unary("SayHello") // <2> + .build(); // <3> +---- + +<1> Create a builder for a `ClientServiceDescriptor` for the `HelloService`. +<2> Specify that the `HelloService` has a unary method named `SayHello`. There are many other methods in this class that allow you +to define `ClientStreaming`, `ServerStreaming` and `Bidirectional` methods. +<3> Build the `ClientServiceDescriptor`. + +=== Configuring the gRPC Channel + +gRPC allows various channel configurations (deadlines, retries, interceptors etc.) + +Please refer to gRPC documentation: https://grpc.io/grpc-java/javadoc/io/grpc/ManagedChannelBuilder.html. + +== Examples +=== Quick Start + +First, create and run a minimalist `HelloService` gRPC server application as described in the +xref:server.adoc#_quick_start[gRPC Server quick start example]. + +Assuming that the server is running on port 1408, create a client as follows: + +[source,java] +---- +public static void main(String[] args) throws Exception { + ClientServiceDescriptor descriptor = ClientServiceDescriptor.builder(HelloService.class) // <1> + .marshallerSupplier(new JavaMarshall.Supplier()) // <2> + .unary("SayHello") // <3> + .build(); + + Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408) // <4> + .usePlaintext() + .build(); + + GrpcServiceClient client = GrpcServiceClient.create(channel, descriptor); // <5> + + CompletionStage future = client.unary("SayHello", "Helidon gRPC!!"); // <6> + System.out.println(future.get()); // <7> + +} +---- +<1> Create a `ClientServiceDescriptor` for the `HelloService`. +<2> Specify custom marshaller using Java serialization to marshall requests and responses. +<3> Add the `SayHello` unary method to the `ClientServiceDescriptor`. This method, by default, uses Java serialization for +marshalling and unmarshalling the request and response values. +<4> Create a gRPC `Channel` that will communicate with the server running in localhost and on port 1408 (using plaintext). +<5> Create the `GrpcServiceClient` that uses the above `Channel` and `ClientServiceDescriptor`. `GrpcClientService` represents +a client that can be used to define the set of methods described by the specified `ClientServiceDescriptor`. In our case, the +`ClientServiceDescriptor` defines one unary method called `SayHello`. +<6> Invoke the `SayHello` method which returns a `CompletionStage`. +<7> Print the result. + +The example above creates a very simple client to the gRPC server that by default uses Java serialization to marshall +requests and responses. diff --git a/docs/se/grpc/configuration.adoc b/docs/se/grpc/configuration.adoc deleted file mode 100644 index 0f0687691c6..00000000000 --- a/docs/se/grpc/configuration.adoc +++ /dev/null @@ -1,69 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// - - Copyright (c) 2019, 2022 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. - -/////////////////////////////////////////////////////////////////////////////// - -= gRPC Server Configuration -:description: Helidon gRPC Server Configuration -:keywords: helidon, grpc, java, configuration -:rootdir: {docdir}/../.. - -include::{rootdir}/includes/se.adoc[] - -Configure the gRPC Server using the Helidon configuration framework, either programmatically -or via a configuration file. - -== Configuring the gRPC Server in your code - -The easiest way to configure the gRPC Server is in your application code. - -[source,java] ----- -GrpcServerConfiguration configuration = GrpcServerConfiguration.builder() - .port(8080) - .build(); -GrpcServer grpcServer = GrpcServer.create(configuration, routing); ----- - -== Configuring the gRPC Server in a configuration file - -You can also define the configuration in a file. - -[source,hocon] -.GrpcServer configuration file `application.yaml` ----- -grpcserver: - port: 3333 ----- - -Then, in your application code, load the configuration from that file. - -[source,java] -.GrpcServer initialization using the `application.conf` file located on the classpath ----- -GrpcServerConfiguration configuration = GrpcServerConfiguration.create( - Config.builder() - .sources(classpath("application.conf")) - .build()); - -GrpcServer grpcServer = GrpcServer.create(configuration, routing); ----- - -== Configuration options - -See all configuration options - link:{javadoc-base-url}/io.helidon.grpc/GrpcServerConfiguration.html[here]. - diff --git a/docs/se/grpc/health-checks.adoc b/docs/se/grpc/health-checks.adoc deleted file mode 100644 index 9ca934fd07b..00000000000 --- a/docs/se/grpc/health-checks.adoc +++ /dev/null @@ -1,119 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// - - Copyright (c) 2019, 2022 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. - -/////////////////////////////////////////////////////////////////////////////// - -= gRPC Service Health Checks -:description: Helidon gRPC Service Health Checks -:keywords: helidon, grpc, java -:rootdir: {docdir}/../.. - -include::{rootdir}/includes/se.adoc[] - -== Service Health Checks - -Helidon gRPC services provide a built-in support for Helidon Health Checks. - -Unless a custom health check is implemented by the service developer, each service -deployed to the gRPC server will be provisioned with a default health check, which -always returns status of `UP`. - -This allows all services, including the ones that don't have a meaningful health check, -to show up in the health report (or to be queried for health) without service developer -having to do anything. - -However, services that do need custom health checks can easily define one, -directly within `GrpcService` implementation: - -[source,java] ----- -public class MyService implements GrpcService { - - @Override - public void update(ServiceDescriptor.Rules rules) { - rules.unary("MyMethod", this::myMethod) - .healthCheck(this::healthCheck); // <1> - } - - private HealthCheckResponse healthCheck() { - boolean fUp = isMyServiceUp(); // <2> - return HealthCheckResponse - .named(name()) // <3> - .state(fUp) // <4> - .withData("ts", System.currentTimeMillis()) // <5> - .build(); - } - - private void myMethod(ReqT request, StreamObserver observer) { - // do something - } -} ----- - -<1> Configure a custom health check for the service -<2> Determine service status -<3> Use service name as a health check name for consistency -<4> Use determined service status -<5> Optionally, provide additional metadata - -You can also define custom health check for an existing service, including plain -`io.grpc.BindableService` implementations, using service configurer inside the -`GrpcRouting` deefinition: - -[source,java] ----- -private static GrpcRouting createRouting() { - return GrpcRouting.builder() - .register(new EchoService(), cfg -> cfg.healthCheck(MyCustomHealthChecks::echoHealthCheck)) // <1> - .build(); -} ----- - -<1> Configure custom health check for an existing or legacy service - -== Exposing Health Checks - -All gRPC service health checks are managed by the Helidon gRPC Server, and are -automatically exposed to the gRPC clients using custom implementation of the -standard gRPC `HealthService` API. - -However, they can also be exposed to REST clients via standard Helidon/Microprofile -`/health` endpoint: - -[source,java] ----- - GrpcServer grpcServer = GrpcServer.create(grpcServerConfig(), createRouting(config)); // <1> - grpcServer.start(); // <2> - - HealthSupport health = HealthSupport.builder() - .add(grpcServer.healthChecks()) // <3> - .build(); - - Routing routing = Routing.builder() - .register(health) // <4> - .build(); - - WebServer.create(webServerConfig(), routing).start(); // <5> ----- - -<1> Create `GrpcServer` instance -<2> Start gRPC server, which will deploy all services and register default and custom health checks -<3> Add gRPC server managed health checks to `HealthSupport` instance -<4> Add `HealthSupport` to the web server routing definition -<5> Create and start web server - -All gRPC health checks will now be available via `/health` REST endpoint, in -addition to the standard gRPC `HealthService` diff --git a/docs/se/grpc/interceptors.adoc b/docs/se/grpc/interceptors.adoc deleted file mode 100644 index 3d97c096247..00000000000 --- a/docs/se/grpc/interceptors.adoc +++ /dev/null @@ -1,126 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// - - Copyright (c) 2019, 2022 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. - -/////////////////////////////////////////////////////////////////////////////// - -= gRPC Interceptors -:description: Helidon gRPC Service Interceptors -:keywords: helidon, grpc, java -:rootdir: {docdir}/../.. - -include::{rootdir}/includes/se.adoc[] - -== Interceptors - -Helidon gRPC allows you to configure standard `io.grpc.ServerInterceptor`s. - -For example, you could implement an interceptor that logs each RPC call: - -[source,java] ----- -class LoggingInterceptor implements ServerInterceptor { // <1> - - private static final Logger LOG = Logger.getLogger(LoggingInterceptor.class.getName()); - - @Override - public ServerCall.Listener interceptCall(ServerCall call, - Metadata metadata, - ServerCallHandler handler) { - - LOG.info(() -> "CALL: " + call.getMethodDescriptor()); // <2> - return handler.startCall(call, metadata); // <3> - } -} ----- - -<1> Implement `io.grpc.ServerInterceptor` -<2> Implement the logging logic -<3> Start intercepted call - -== Registering Interceptors - -You can register interceptors globally, in which case they will be applied to all -methods of all services, by simply adding them to the `GrpcRouting` instance: - -[source,java] ----- -private static GrpcRouting createRouting(Config config) { - return GrpcRouting.builder() - .intercept(new LoggingInterceptor()) // <1> - .register(new GreetService(config)) - .register(new EchoService()) - .build(); -} ----- - -<1> Adds `LoggingInterceptor` to all methods of `GreetService` and `EchoService` - -You can also register an interceptor for a specific service, either by implementing -`GrpcService.update` method: - -[source,java] ----- -public class MyService implements GrpcService { - - @Override - public void update(ServiceDescriptor.Rules rules) { - rules.intercept(new LoggingInterceptor()) // <1> - .unary("MyMethod", this::myMethod); - } - - private void myMethod(ReqT request, StreamObserver observer) { - // do something - } -} ----- - -<1> Adds `LoggingInterceptor` to all methods of `MyService` - -Or by configuring `ServiceDescriptor` externally, when creating `GrpcRouting`, which -allows you to add interceptors to plain `io.grpc.BindableService` services as well: - -[source,java] ----- -private static GrpcRouting createRouting(Config config) { - return GrpcRouting.builder() - .register(new GreetService(config), cfg -> cfg.intercept(new LoggingInterceptor())) // <1> - .register(new EchoService()) - .build(); -} ----- - -<1> Adds `LoggingInterceptor` to all methods of `GreetService` only - -Finally, you can also register an interceptor at the method level: - -[source,java] ----- -public class MyService implements GrpcService { - - @Override - public void update(ServiceDescriptor.Rules rules) { - rules.unary("MyMethod", - this::myMethod, - cfg -> cfg.intercept(new LoggingInterceptor())); // <1> - } - - private void myMethod(ReqT request, StreamObserver observer) { - // do something - } -} ----- - -<1> Adds `LoggingInterceptor` to `MyService::MyMethod` only diff --git a/docs/se/grpc/introduction.adoc b/docs/se/grpc/introduction.adoc deleted file mode 100644 index 2e18bee5bf5..00000000000 --- a/docs/se/grpc/introduction.adoc +++ /dev/null @@ -1,79 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// - - Copyright (c) 2019, 2022 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. - -/////////////////////////////////////////////////////////////////////////////// - -= gRPC Server Introduction -:description: Helidon gRPC Server Introduction -:keywords: helidon, grpc, java -:feature-name: gRPC -:rootdir: {docdir}/../.. - -include::{rootdir}/includes/se.adoc[] - -Helidon gRPC Server provides a framework for creating link:http://grpc.io/[gRPC] applications. - -== Experimental - -WARNING: The Helidon gRPC feature is currently experimental and the APIs are - subject to changes until gRPC support is stabilized. - -include::{rootdir}/includes/dependencies.adoc[] - -[source,xml] ----- - - io.helidon.grpc - helidon-grpc-server - ----- - -== Quick Start - -Here is the code for a minimalist gRPC application that runs on a default port (1408): - -[source,java] ----- -public static void main(String[] args) throws Exception { - GrpcServer grpcServer = GrpcServer - .create(GrpcRouting.builder() - .register(new HelloService()) // <1> - .build()) - .start() // <2> - .toCompletableFuture() - .get(10, TimeUnit.SECONDS); // Implement the simplest possible gRPC service. // <3> - - System.out.println("gRPC Server started at: http://localhost:" + grpcServer.port()); // <4> -} - -static class HelloService implements GrpcService { <5> - @Override - public void update(ServiceDescriptor.Rules rules) { - rules.unary("SayHello", ((request, responseObserver) -> complete(responseObserver, "Hello " + request))); // <6> - } -} ----- - -<1> Register gRPC service. -<2> Start the server. -<3> Wait for the server to start while throwing possible errors as exceptions. -<4> The server is bound to a default port (1408). -<5> Implement the simplest possible gRPC service. -<6> Add unary method `HelloService/SayHello` to the service definition. - -The example above deploys a very simple service to the gRPC server that by default uses Java serialization to marshall -requests and responses. We will look into deployment of "standard" gRPC services that use Protobuf for request and -response marshalling, as well as how you can configure custom marshallers, later in this document. diff --git a/docs/se/grpc/marshalling.adoc b/docs/se/grpc/marshalling.adoc index 5065da16074..79c6ad254e9 100644 --- a/docs/se/grpc/marshalling.adoc +++ b/docs/se/grpc/marshalling.adoc @@ -18,7 +18,7 @@ = Marshalling :description: Helidon gRPC Marshalling -:keywords: helidon, grpc, java +:keywords: helidon, grpc, java, marshalling :rootdir: {docdir}/../.. include::{rootdir}/includes/se.adoc[] diff --git a/docs/se/grpc/metrics.adoc b/docs/se/grpc/metrics.adoc deleted file mode 100644 index 8cb3af1ca9b..00000000000 --- a/docs/se/grpc/metrics.adoc +++ /dev/null @@ -1,196 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// - - Copyright (c) 2019, 2022 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. - -/////////////////////////////////////////////////////////////////////////////// - -= gRPC Service Metrics -:description: Helidon gRPC Service Metrics -:keywords: helidon, grpc, java -:rootdir: {docdir}/../.. - -include::{rootdir}/includes/se.adoc[] - -Helidon gRPC Server has built-in support for metrics capture, which allows -service developers to easily enable application-level metrics for their services. - -== Enabling Metrics Capture - -By default, gRPC Server only captures two vendor-level metrics: `grpc.request.count` -and `grpc.request.meter`. These metrics provide aggregate view of requests across -all services, and serve as an indication of the overall server load. - -However, users can enable more fine grained metrics by simply configuring a built-in -`GrpcMetrics` interceptor within the routing: - -[source,java] ----- -private static GrpcRouting createRouting(Config config) { - return GrpcRouting.builder() - .intercept(GrpcMetrics.timed()) // <1> - .register(new GreetService(config)) - .register(new EchoService()) - .build(); -} ----- - -<1> Capture metrics for all methods of all services as a `timer` - -In the example above we have chosen to create and keep a `timer` metric type for -each method of each service. Alternatively, we could've chosen to use a -`counter`, `meter` or a `histogram` instead. - -== Overriding Metrics Capture - -While global metrics capture is certainly useful, it is not always sufficient. -Keeping a separate `timer` for each gRPC method may be an overkill, so the user -could decide to use a lighter-weight metric type, such as `counter` or a `meter`. - -However, she may still want to enable `histogram` or a `timer` for some services, -or even only some methods of some services. - -This can be easily accomplished by overriding the type of the captured metric at -either service or the method level: - -[source,java] ----- -private static GrpcRouting createRouting(Config config) { - return GrpcRouting.builder() - .intercept(GrpcMetrics.counted()) // <1> - .register(new MyService()) - .build(); -} - -public static class MyService implements GrpcService { - - @Override - public void update(ServiceDescriptor.Rules rules) { - rules - .intercept(GrpcMetrics.metered()) // <2> - .unary("MyMethod", this::myMethod, - cfg -> cfg.intercept(GrpcMetrics.timer())) // <3> - } - - private void myMethod(ReqT request, StreamObserver observer) { - // do something - } -} ----- - -<1> Use `counter` for all methods of all services, unless overridden -<2> Use `meter` for all methods of `MyService` -<3> Use `timer` for `MyService::MyMethod` - -== Exposing Metrics Externally - -Collected metrics are stored in the standard Helidon Metric Registries, such as vendor and -application registry, and can be exposed via standard `/metrics` REST API. - -[source,java] ----- -Routing routing = Routing.builder() - .register(MetricsSupport.create()) // <1> - .build(); - -WebServer.create(webServerConfig(), routing) // <2> - .start() ----- -<1> Add `MetricsSupport` instance to web server routing -<2> Create and start Helidon web server - -See xref:../metrics/metrics.adoc[Helidon Metrics] documentation for more details. - -== Specifying Metric Meta-data - -Helidon metrics contain meta-data such as tags, a description, units etc. It is possible to -add this additional meta-data when specifying the metrics. - -=== Adding Tags - -To add tags to a metric a `Map` of key/value tags can be supplied. -For example: -[source,java] ----- -Map tagMap = new HashMap<>(); -tagMap.put("keyOne", "valueOne"); -tagMap.put("keyTwo", "valueTwo"); - -GrpcRouting routing = GrpcRouting.builder() - .intercept(GrpcMetrics.counted().tags(tagMap)) // <1> - .register(new MyService()) - .build(); ----- -<1> the `tags()` method is used to add the `Map` of tags to the metric. - -=== Adding a Description - -A meaningful description can be added to a metric: -For example: -[source,java] ----- -GrpcRouting routing = GrpcRouting.builder() - .intercept(GrpcMetrics.counted().description("Something useful")) // <1> - .register(new MyService()) - .build(); ----- - -<1> the `description()` method is used to add the description to the metric. - -=== Adding Metric Units - -A units value can be added to the Metric: -For example: -[source,java] ----- -GrpcRouting routing = GrpcRouting.builder() - .intercept(GrpcMetrics.timed().units(MetricUnits.SECONDS)) // <1> - .register(new MyService()) - .build(); ----- -<1> the `units()` method is used to add the metric units to the metric. -Typically the units value is one of the constants from `org.eclipse.microprofile.metrics.MetricUnits` class. - -== Overriding the Metric Name - -By default the metric name is the gRPC service name followed by a dot ('.') followed by the method name. -It is possible to supply a function that can be used to override the default behaviour. - -The function should implement the `io.helidon.grpc.metrics.GrpcMetrics.NamingFunction` interface -[source,java] ----- -@FunctionalInterface -public interface NamingFunction { - /** - * Create a metric name. - * - * @param service the service descriptor - * @param methodName the method name - * @param metricType the metric type - * @return the metric name - */ - String createName(ServiceDescriptor service, String methodName, MetricType metricType); -} ----- -This is a functional interface so lambda can be used too. - -For example: -[source,java] ----- -GrpcRouting routing = GrpcRouting.builder() - .intercept(GrpcMetrics.counted() - .nameFunction((svc, method, metric) -> "grpc." + service.name() + '.' + method) // <1> ----- -<1> the `NamingFunction` is just a lambda that returns the concatenated service name and method name -with the prefix `grpc.` So for a service "Foo", method "bar" the above example would produce a name "grpc.Foo.bar". diff --git a/docs/se/grpc/routing.adoc b/docs/se/grpc/routing.adoc deleted file mode 100644 index 4d64f3255ab..00000000000 --- a/docs/se/grpc/routing.adoc +++ /dev/null @@ -1,104 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// - - Copyright (c) 2019, 2022 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. - -/////////////////////////////////////////////////////////////////////////////// - -= gRPC Server Routing -:description: Helidon gRPC Server Routing -:keywords: helidon, grpc, java -:rootdir: {docdir}/../.. - -include::{rootdir}/includes/se.adoc[] - -== gRPC Server Routing - -Unlike Webserver, which allows you to route requests based on path expression -and the HTTP verb, gRPC server always routes requests based on the service and -method name. This makes routing configuration somewhat simpler -- all you need -to do is register your services: - -[source,java] ----- -private static GrpcRouting createRouting(Config config) { - return GrpcRouting.builder() - .register(new GreetService(config)) // <1> - .register(new EchoService()) // <2> - .register(new MathService()) // <3> - .build(); -} ----- - -<1> Register `GreetService` instance. -<2> Register `EchoService` instance. -<3> Register `MathService` instance. - -Both "standard" gRPC services that implement `io.grpc.BindableService` interface -(typically implemented by extending generated server-side stub and overriding -its methods), and Helidon gRPC services that implement -`io.helidon.grpc.server.GrpcService` interface can be registered. - -The difference is that Helidon gRPC services allow you to customize behavior -down to the method level, and provide a number of useful helper methods that -make service implementation easier, as we'll see in a moment. - -== Customizing Service Definitions - -When registering a service, regardless of its type, you can customize its -descriptor by providing configuration consumer as a second argument to the -`register` method. - -This is particularly useful when registering standard `BindableService` -instances, as it allows you to add certain Helidon-specific behaviors, such as -xref:health-checks.adoc[health checks] and xref:metrics.adoc[metrics] to them: - -[source,java] ----- -private static GrpcRouting createRouting(Config config) { - return GrpcRouting.builder() - .register(new GreetService(config)) - .register(new EchoService(), service -> { - service.healthCheck(CustomHealthChecks::echoHealthCheck) // <1> - .metered(); // <2> - }) - .build(); -} ----- - -<1> Add custom health check to the service. -<2> Specify that all the calls to service methods should be metered. - -== Specifying Global Interceptors - -`GrpcRouting` also allows you to specify xref:interceptors.adoc[custom interceptors] -that will be applied to all registered services. - -This is useful to configure features such as tracing, security and metrics collection, -and we provide built-in interceptors for those purposes that you can simply register -with the routing definition: - -[source,java] ----- -private static GrpcRouting createRouting(Config config) { - return GrpcRouting.builder() - .intercept(GrpcMetrics.timed()) // <1> - .register(new GreetService(config)) - .register(new EchoService()) - .register(new MathService()) - .build(); -} ----- - -<1> Register `GrpcMetrics` interceptor that will collect timers for all methods of all services (but can be overridden at the individual service or even method level). diff --git a/docs/se/grpc/security.adoc b/docs/se/grpc/security.adoc deleted file mode 100644 index 1b1cb24c96a..00000000000 --- a/docs/se/grpc/security.adoc +++ /dev/null @@ -1,233 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// - - Copyright (c) 2019, 2022 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. - -/////////////////////////////////////////////////////////////////////////////// - -= gRPC Server Security -:description: Helidon Security gRPC integration -:keywords: helidon, grpc, security -:feature-name: gRPC Server Security -:rootdir: {docdir}/../.. - -include::{rootdir}/includes/se.adoc[] - -Security integration of the xref:introduction.adoc[gRPC server] - -include::{rootdir}/includes/dependencies.adoc[] - -[source,xml] ----- - - io.helidon.security.integration - helidon-security-integration-grpc - ----- - -== Bootstrapping - -There are two steps to configure security with gRPC server: - -1. Create security instance and register it with server -2. Protect gRPC services of server with various security features - -[source,java] -.Example using builders ----- -// gRPC server's routing -GrpcRouting.builder() - // This is step 1 - register security instance with gRPC server processing - // security - instance of security either from config or from a builder - // securityDefaults - default enforcement for each service that has a security definition - .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate())) - // this is step 2 - protect a service - // register and protect this service with authentication (from defaults) and role "user" - .register(greetService, GrpcSecurity.rolesAllowed("user")) - .build(); ----- - -[source,java] -.Example using builders for more fine grained method level security ----- -// create the service descriptor -ServiceDescriptor greetService = ServiceDescriptor.builder(new GreetService()) - // Add an instance of gRPC security that will apply to all methods of - // the service - in this case require the "user" role - .intercept(GrpcSecurity.rolesAllowed("user")) - // Add an instance of gRPC security that will apply to the "SetGreeting" - // method of the service - in this case require the "admin" role - .intercept("SetGreeting", GrpcSecurity.rolesAllowed("admin")) - .build(); - -// Create the gRPC server's routing -GrpcRouting.builder() - // This is step 1 - register security instance with gRPC server processing - // security - instance of security either from config or from a builder - // securityDefaults - default enforcement for each service that has a security definition - .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate())) - // this is step 2 - add the service descriptor - .register(greetService) - .build(); ----- - -[source,java] -.Example using configuration ----- -GrpcRouting.builder() - // helper method to load both security and gRPC server security from configuration - .intercept(GrpcSecurity.create(config)) - // continue with gRPC server route configuration... - .register(new GreetService()) - .build(); ----- - -[source,conf] -.Example using configuration - configuration (HOCON) ----- -# This may change in the future - to align with gRPC server configuration, -# once it is supported -security - grpc-server: - # Configuration of integration with gRPC server - defaults: - authenticate: true - # Configuration security for individual services - services: - - name: "GreetService" - defaults: - roles-allowed: ["user"] - # Configuration security for individual methods of the service - methods: - - name: "SetGreeting" - roles-allowed: ["admin"] ----- - -=== Client security -When using the Helidon SE gRPC client API security can be configured for a gRPC service -or at the individual method level. The client API has a custom `CallCredentials` implementation that -integrates with the Helidon security APIs. - -[source,java] -.Example configuring client security for a service ----- -Security security = Security.builder() // <1> - .addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth"))) - .build(); - -GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client")) // <2> - .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, user) - .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, password) - .build(); - -ClientServiceDescriptor descriptor = ClientServiceDescriptor // <3> - .builder(StringService.class) - .unary("Lower") - .callCredentials(clientSecurity) // <4> - .build(); - -GrpcServiceClient client = GrpcServiceClient.create(channel, descriptor); // <5> - -String response = client.blockingUnary("Lower", "ABCD"); // <6> ----- -<1> Create the Helidon `Security` instance (in this case using the basic auth provider) -<2> Create the `GrpcClientSecurity` gRPC `CallCredentials` adding the user and password -property expected by the basic auth provider. -<3> Create the gRPC `ClientServiceDescriptor` for the `StringService` gRPC service. -<4> Set the `GrpcClientSecurity` instance as the call credentials for all methods of the service -<5> Create a `GrpcServiceClient` that will allow methods to be called on the service -<6> Call the "Lower" method which will use the configured basic auth credentials - - -[source,java] -.Example configuring client security for a specific method ----- -GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client")) // <1> - .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, user) - .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, password) - .build(); - -ClientServiceDescriptor descriptor = ClientServiceDescriptor // <2> - .builder(StringService.class) - .unary("Lower") - .unary("Upper", rules -> rules.callCredentials(clientSecurity)) // <3> - .build(); ----- -<1> Create the `GrpcClientSecurity` call credentials in the same way as above. -<2> Create the `ClientServiceDescriptor`, this time with two unary methods, "Lower" and "Upper". -<3> The "Upper" method is configured to use the `GrpcClientSecurity` call credentials, the "Lower" method -will be called without any credentials. - - -=== Outbound security -Outbound security covers three scenarios: - -* Calling a secure gRPC service from inside a gRPC service method handler -* Calling a secure gRPC service from inside a web server method handler -* Calling a secure web endpoint from inside a gRPC service method handler - -Within each scenario credentials can be propagated if the gRPC/http method -handler is executing within a security context or credentials can be overridden -to provide a different set of credentials to use to call the outbound endpoint. - -[source,java] -.Example calling a secure gRPC service from inside a gRPC service method handler ----- -// Obtain the SecurityContext from the current gRPC call Context -SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get(); - -// Create a gRPC CallCredentials that will use the current request's -// security context to configure outbound credentials -GrpcClientSecurity clientSecurity = GrpcClientSecurity.create(securityContext); - -// Create the gRPC stub using the CallCredentials -EchoServiceGrpc.EchoServiceBlockingStub stub = noCredsEchoStub.withCallCredentials(clientSecurity); ----- - -[source,java] -.Example calling a secure gRPC service from inside a web server method handler ----- -private static void propagateCredentialsWebRequest(ServerRequest req, ServerResponse res) { - try { - // Create a gRPC CallCredentials that will use the current request's - // security context to configure outbound credentials - GrpcClientSecurity clientSecurity = GrpcClientSecurity.create(req); - - // Create the gRPC stub using the CallCredentials - EchoServiceGrpc.EchoServiceBlockingStub stub = noCredsEchoStub.withCallCredentials(clientSecurity); - - String message = req.queryParams().first("message").orElse(null); - Echo.EchoResponse echoResponse = stub.echo(Echo.EchoRequest.newBuilder().setMessage(message).build()); - res.send(echoResponse.getMessage()); - } catch (StatusRuntimeException e) { - res.status(GrpcHelper.toHttpResponseStatus(e)).send(); - } catch (Throwable thrown) { - res.status(Http.ResponseStatus.create(500, thrown.getMessage())).send(); - } -} ----- - -[source,java] -.Example calling a secure web endpoint from inside a gRPC service method handler ----- -// Obtain the SecurityContext from the gRPC call Context -SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get(); - -// Use the SecurityContext as normal to make a http request -Response webResponse = client.target(url) - .path("/test") - .request() - .property(ClientSecurity.PROPERTY_CONTEXT, securityContext) - .get(); ----- diff --git a/docs/se/grpc/server.adoc b/docs/se/grpc/server.adoc new file mode 100644 index 00000000000..c149e37ffae --- /dev/null +++ b/docs/se/grpc/server.adoc @@ -0,0 +1,933 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2019, 2022 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. + +/////////////////////////////////////////////////////////////////////////////// + += gRPC Server +:description: Helidon gRPC Server +:keywords: helidon, grpc, java, se +:feature-name: gRPC Server +:rootdir: {docdir}/../.. + +include::{rootdir}/includes/se.adoc[] + +== Contents + +- <> +- <> +- <> +- <> +- <> + +== Overview +Helidon gRPC Server provides a framework for creating link:http://grpc.io/[gRPC] applications. +While it allows you to deploy any standard gRPC service that +implements `io.grpc.BindableService` interface, including services generated +from the Protobuf IDL files (and even allows you to customize them to a certain +extent), using Helidon gRPC framework to implement your services has a number of +benefits: + +* It allows you to define both HTTP and gRPC services using similar programming +model, simplifying learning curve for developers. + +* It provides a number of helper methods that make service implementation +significantly simpler. + +* It allows you to configure some of the Helidon value-added features, such +as <> and <> +down to the method level. + +* It allows you to easily specify custom marshaller for requests and +responses if Protobuf does not satisfy your needs. + +* It provides built in support for <>. + + +=== Experimental + +WARNING: The Helidon gRPC feature is currently experimental and the APIs are +subject to changes until gRPC support is stabilized. + +include::{rootdir}/includes/dependencies.adoc[] + +[source,xml] +---- + + io.helidon.grpc + helidon-grpc-server + +---- + +[[security_maven_coordinartes]] +If `gRPC server security` is required as described in this <> , add the following dependency to your project’s pom.xml: +[source,xml] +---- + + io.helidon.security.integration + helidon-security-integration-grpc + +---- + +== Usage +=== gRPC Server Routing + +Unlike Webserver, which allows you to route requests based on path expression +and the HTTP verb, gRPC server always routes requests based on the service and +method name. This makes routing configuration somewhat simpler -- all you need +to do is register your services: + +[source,java] +---- +private static GrpcRouting createRouting(Config config) { + return GrpcRouting.builder() + .register(new GreetService(config)) // <1> + .register(new EchoService()) // <2> + .register(new MathService()) // <3> + .build(); +} +---- + +<1> Register `GreetService` instance. +<2> Register `EchoService` instance. +<3> Register `MathService` instance. + +Both "standard" gRPC services that implement `io.grpc.BindableService` interface +(typically implemented by extending generated server-side stub and overriding +its methods), and Helidon gRPC services that implement +`io.helidon.grpc.server.GrpcService` interface can be registered. + +The difference is that Helidon gRPC services allow you to customize behavior +down to the method level, and provide a number of useful helper methods that +make service implementation easier, as we'll see in a moment. + +==== Customizing Service Definitions + +When registering a service, regardless of its type, you can customize its +descriptor by providing configuration consumer as a second argument to the +`register` method. + +This is particularly useful when registering standard `BindableService` +instances, as it allows you to add certain Helidon-specific behaviors, such as +<> and <> to them: + +[source,java] +---- +private static GrpcRouting createRouting(Config config) { + return GrpcRouting.builder() + .register(new GreetService(config)) + .register(new EchoService(), service -> { + service.healthCheck(CustomHealthChecks::echoHealthCheck) // <1> + .metered(); // <2> + }) + .build(); +} +---- + +<1> Add custom health check to the service. +<2> Specify that all the calls to service methods should be metered. + +==== Specifying Global Interceptors + +`GrpcRouting` also allows you to specify <> that will be applied to all registered services. + +This is useful to configure features such as tracing, security and metrics collection, +and we provide built-in interceptors for those purposes that you can simply register +with the routing definition: + +[source,java] +---- +private static GrpcRouting createRouting(Config config) { + return GrpcRouting.builder() + .intercept(GrpcMetrics.timed()) // <1> + .register(new GreetService(config)) + .register(new EchoService()) + .register(new MathService()) + .build(); +} +---- + +<1> Register `GrpcMetrics` interceptor that will collect timers for all methods of all services (but can be overridden at the individual service or even method level). + +=== Service Implementation + +At the very basic level, all you need to do in order to implement a Helidon +gRPC service is create a class that implements `io.helidon.grpc.server.GrpcService` +interface and define one or more methods for the service: + +[source,java] +---- +class EchoService implements GrpcService { + + @Override + public void update(ServiceDescriptor.Rules rules) { + rules.unary("Echo", this::echo); // <1> + } + + /** + * Echo the message back to the caller. + * + * @param request the echo request containing the message to echo + * @param observer the response observer + */ + public void echo(String request, StreamObserver observer) { // <2> + complete(observer, request); // <3> + } +} +---- + +<1> Define unary method `Echo` and map it to the `this::echo` handler. +<2> Create a handler for the `Echo` method. +<3> Send the request string back to the client by completing response observer. + +NOTE: The `complete` method shown in the example above is just one of many helper +methods available in the `GrpcService` class. See the full list +link:{grpc-server-javadoc-base-url}/io/helidon/grpc/server/GrpcService.html[here]. + +The example above implements a service with a single unary method, which will be +exposed at the `EchoService/Echo' endpoint. The service does not explicitly define +a marshaller for requests and responses, so Java serialization will be used as a +default. + +Unfortunately, this implies that you will have to implement clients by hand and +configure them to use the same marshaller as the server. Obviously, one of the +major selling points of gRPC is that it makes it easy to generate clients for a +number of languages (as long as you use Protobuf for marshalling), so let's see +how we would implement Protobuf enabled Helidon gRPC service. + +==== Implementing Protobuf Services + +In order to implement Protobuf-based service, you would follow the official +link:https://grpc.io/docs/quickstart/java.html[instructions] on the gRPC +web site, which boil down to the following: + +===== Define the Service IDL + +For this example, we will re-implement the `EchoService` above as a Protobuf +service in `echo.proto` file. + +[source, proto] +---- +syntax = "proto3"; +option java_package = "org.example.services.echo"; + +service EchoService { + rpc Echo (EchoRequest) returns (EchoResponse) {} +} + +message EchoRequest { + string message = 1; +} + +message EchoResponse { + string message = 1; +} +---- + +Based on this IDL, the gRPC compiler will generate message classes (`EchoRequest` +and `EchoResponse`), client stubs that can be used to make RPC calls to the server, +as well as the base class for the server-side service implementation. + +We can ignore the last one, and implement the service using Helidon gRPC framework +instead. + +===== Implement the Service + +The service implementation will be very similar to our original implementation: + +[source,java] +---- +class EchoService implements GrpcService { + + @Override + public void update(ServiceDescriptor.Rules rules) { + rules.proto(Echo.getDescriptor()) // <1> + .unary("Echo", this::echo); // <2> + } + + /** + * Echo the message back to the caller. + * + * @param request the echo request containing the message to echo + * @param observer the response observer + */ + public void echo(Echo.EchoRequest request, StreamObserver observer) { // <3> + String message = request.getMessage(); // <4> + Echo.EchoResponse response = Echo.EchoResponse.newBuilder().setMessage(message).build(); // <5> + complete(observer, response); // <6> + } +} +---- + +<1> Specify proto descriptor in order to provide necessary type information and +enable Protobuf marshalling. +<2> Define unary method `Echo` and map it to the `this::echo` handler. +<3> Create a handler for the `Echo` method, using Protobuf message types for request and response. +<4> Extract message string from the request. +<5> Create the response containing extracted message. +<6> Send the response back to the client by completing response observer. + +=== Interceptors + +Helidon gRPC allows you to configure standard interceptors using `io.grpc.ServerInterceptor`. + +For example, you could implement an interceptor that logs each RPC call: + +[source,java] +---- +class LoggingInterceptor implements ServerInterceptor { // <1> + + private static final Logger LOG = Logger.getLogger(LoggingInterceptor.class.getName()); + + @Override + public ServerCall.Listener interceptCall(ServerCall call, + Metadata metadata, + ServerCallHandler handler) { + + LOG.info(() -> "CALL: " + call.getMethodDescriptor()); // <2> + return handler.startCall(call, metadata); // <3> + } +} +---- + +<1> Implement `io.grpc.ServerInterceptor` +<2> Implement the logging logic +<3> Start intercepted call + +==== Registering Interceptors + +You can register interceptors globally, in which case they will be applied to all +methods of all services, by simply adding them to the `GrpcRouting` instance: + +[source,java] +---- +private static GrpcRouting createRouting(Config config) { + return GrpcRouting.builder() + .intercept(new LoggingInterceptor()) // <1> + .register(new GreetService(config)) + .register(new EchoService()) + .build(); +} +---- + +<1> Adds `LoggingInterceptor` to all methods of `GreetService` and `EchoService` + +You can also register an interceptor for a specific service, either by implementing +`GrpcService.update` method: + +[source,java] +---- +public class MyService implements GrpcService { + + @Override + public void update(ServiceDescriptor.Rules rules) { + rules.intercept(new LoggingInterceptor()) // <1> + .unary("MyMethod", this::myMethod); + } + + private void myMethod(ReqT request, StreamObserver observer) { + // do something + } +} +---- + +<1> Adds `LoggingInterceptor` to all methods of `MyService` + +Or by configuring `ServiceDescriptor` externally, when creating `GrpcRouting`, which +allows you to add interceptors to plain `io.grpc.BindableService` services as well: + +[source,java] +---- +private static GrpcRouting createRouting(Config config) { + return GrpcRouting.builder() + .register(new GreetService(config), cfg -> cfg.intercept(new LoggingInterceptor())) // <1> + .register(new EchoService()) + .build(); +} +---- + +<1> Adds `LoggingInterceptor` to all methods of `GreetService` only + +Finally, you can also register an interceptor at the method level: + +[source,java] +---- +public class MyService implements GrpcService { + + @Override + public void update(ServiceDescriptor.Rules rules) { + rules.unary("MyMethod", + this::myMethod, + cfg -> cfg.intercept(new LoggingInterceptor())); // <1> + } + + private void myMethod(ReqT request, StreamObserver observer) { + // do something + } +} +---- + +<1> Adds `LoggingInterceptor` to `MyService::MyMethod` only + +=== Service Health Checks + +Helidon gRPC services provide a built-in support for Helidon Health Checks. + +Unless a custom health check is implemented by the service developer, each service +deployed to the gRPC server will be provisioned with a default health check, which +always returns status of `UP`. + +This allows all services, including the ones that don't have a meaningful health check, +to show up in the health report (or to be queried for health) without service developer +having to do anything. + +However, services that do need custom health checks can easily define one, +directly within `GrpcService` implementation: + +[source,java] +---- +public class MyService implements GrpcService { + + @Override + public void update(ServiceDescriptor.Rules rules) { + rules.unary("MyMethod", this::myMethod) + .healthCheck(this::healthCheck); // <1> + } + + private HealthC +heckResponse healthCheck() { + boolean fUp = isMyServiceUp(); // <2> + return HealthCheckResponse + .named(name()) // <3> + .state(fUp) // <4> + .withData("ts", System.currentTimeMillis()) // <5> + .build(); + } + + private void myMethod(ReqT request, StreamObserver observer) { + // do something + } +} +---- + +<1> Configure a custom health check for the service +<2> Determine service status +<3> Use service name as a health check name for consistency +<4> Use determined service status +<5> Optionally, provide additional metadata + +You can also define custom health check for an existing service, including plain +`io.grpc.BindableService` implementations, using service configurer inside the +`GrpcRouting` deefinition: + +[source,java] +---- +private static GrpcRouting createRouting() { + return GrpcRouting.builder() + .register(new EchoService(), cfg -> cfg.healthCheck(MyCustomHealthChecks::echoHealthCheck)) // <1> + .build(); +} +---- + +<1> Configure custom health check for an existing or legacy service + +==== Exposing Health Checks + +All gRPC service health checks are managed by the Helidon gRPC Server, and are +automatically exposed to the gRPC clients using custom implementation of the +standard gRPC `HealthService` API. + +However, they can also be exposed to REST clients via standard Helidon/Microprofile +`/health` endpoint: + +[source,java] +---- + GrpcServer grpcServer = GrpcServer.create(grpcServerConfig(), createRouting(config)); // <1> + grpcServer.start(); // <2> + + HealthSupport health = HealthSupport.builder() + .add(grpcServer.healthChecks()) // <3> + .build(); + + Routing routing = Routing.builder() + .register(health) // <4> + .build(); + + WebServer.create(webServerConfig(), routing).start(); // <5> +---- + +<1> Create `GrpcServer` instance +<2> Start gRPC server, which will deploy all services and register default and custom health checks +<3> Add gRPC server managed health checks to `HealthSupport` instance +<4> Add `HealthSupport` to the web server routing definition +<5> Create and start web server + +All gRPC health checks will now be available via `/health` REST endpoint, in +addition to the standard gRPC `HealthService` + +=== Service Metrics +Helidon gRPC Server has built-in support for metrics capture, which allows +service developers to easily enable application-level metrics for their services. + +==== Enabling Metrics Capture + +By default, gRPC Server only captures two vendor-level metrics: `grpc.request.count` +and `grpc.request.meter`.These metrics provide aggregate view of requests across +all services, and serve as an indication of the overall server load. + +However, users can enable more fine grained metrics by simply configuring a built-in +`GrpcMetrics` interceptor within the routing: + +[source,java] +---- +private static GrpcRouting createRouting(Config config) { + return GrpcRouting.builder() + .intercept(GrpcMetrics.timed()) // <1> + .register(new GreetService(config)) + .register(new EchoService()) + .build(); +} +---- + +<1> Capture metrics for all methods of all services as a `timer` + +In the example above we have chosen to create and keep a `timer` metric type for +each method of each service. Alternatively, we could've chosen to use a +`counter`, `meter` or a `histogram` instead. + +==== Overriding Metrics Capture + +While global metrics capture is certainly useful, it is not always sufficient. +Keeping a separate `timer` for each gRPC method may be an overkill, so the user +could decide to use a lighter-weight metric type, such as `counter` or a `meter`. + +However, she may still want to enable `histogram` or a `timer` for some services, +or even only some methods of some services. + +This can be easily accomplished by overriding the type of the captured metric at +either service or the method level: + +[source,java] +---- +private static GrpcRouting createRouting(Config config) { + return GrpcRouting.builder() + .intercept(GrpcMetrics.counted()) // <1> + .register(new MyService()) + .build(); +} + +public static class MyService implements GrpcService { + + @Override + public void update(ServiceDescriptor.Rules rules) { + rules + .intercept(GrpcMetrics.metered()) // <2> + .unary("MyMethod", this::myMethod, + cfg -> cfg.intercept(GrpcMetrics.timer())) // <3> + } + + private void myMethod(ReqT request, StreamObserver observer) { + // do something + } +} +---- + +<1> Use `counter` for all methods of all services, unless overridden +<2> Use `meter` for all methods of `MyService` +<3> Use `timer` for `MyService::MyMethod` + +==== Exposing Metrics Externally + +Collected metrics are stored in the standard Helidon Metric Registries, such as vendor and +application registry, and can be exposed via standard `/metrics` REST API. + +[source,java] +---- +Routing routing = Routing.builder() + .register(MetricsSupport.create()) // <1> + .build(); + +WebServer.create(webServerConfig(), routing) // <2> + .start() +---- +<1> Add `MetricsSupport` instance to web server routing +<2> Create and start Helidon web server + +See xref:../metrics/metrics.adoc[Helidon Metrics] documentation for more details. + +==== Specifying Metric Meta-data + +Helidon metrics contain meta-data such as tags, a description, units etc. It is possible to +add this additional meta-data when specifying the metrics. + +===== Adding Tags + +To add tags to a metric a `Map` of key/value tags can be supplied. +For example: +[source,java] +---- +Map tagMap = new HashMap<>(); +tagMap.put("keyOne", "valueOne"); +tagMap.put("keyTwo", "valueTwo"); + +GrpcRouting routing = GrpcRouting.builder() + .intercept(GrpcMetrics.counted().tags(tagMap)) // <1> + .register(new MyService()) + .build(); +---- +<1> the `tags()` method is used to add the `Map` of tags to the metric. + +===== Adding a Description + +A meaningful description can be added to a metric: +For example: +[source,java] +---- +GrpcRouting routing = GrpcRouting.builder() + .intercept(GrpcMetrics.counted().description("Something useful")) // <1> + .register(new MyService()) + .build(); +---- + +<1> the `description()` method is used to add the description to the metric. + +===== Adding Metric Units + +A units value can be added to the Metric: +For example: +[source,java] +---- +GrpcRouting routing = GrpcRouting.builder() + .intercept(GrpcMetrics.timed().units(MetricUnits.SECONDS)) // <1> + .register(new MyService()) + .build(); +---- +<1> the `units()` method is used to add the metric units to the metric. +Typically the units value is one of the constants from `org.eclipse.microprofile.metrics.MetricUnits` class. + +==== Overriding the Metric Name + +By default the metric name is the gRPC service name followed by a dot ('.') followed by the method name. +It is possible to supply a function that can be used to override the default behaviour. + +The function should implement the `io.helidon.grpc.metrics.GrpcMetrics.NamingFunction` interface +[source,java] +---- +@FunctionalInterface +public interface NamingFunction { + /** + * Create a metric name. + * + * @param service the service descriptor + * @param methodName the method name + * @param metricType the metric type + * @return the metric name + */ + String createName(ServiceDescriptor service, String methodName, MetricType metricType); +} +---- +This is a functional interface so lambda can be used too. + +For example: +[source,java] +---- +GrpcRouting routing = GrpcRouting.builder() + .intercept(GrpcMetrics.counted() + .nameFunction((svc, method, metric) -> "grpc." + service.name() + '.' + method) // <1> +---- +<1> the `NamingFunction` is just a lambda that returns the concatenated service name and method name +with the prefix `grpc.` So for a service "Foo", method "bar" the above example would produce a name "grpc.Foo.bar". + +=== Security +To enable Server Security, refer to earlier section about <> for guidance on what dependency to add in the project's pom.xml. + +==== Bootstrapping + +There are two steps to configure security with gRPC server: + +1. Create security instance and register it with server +2. Protect gRPC services of server with various security features + +[source,java] +.Example using builders +---- +// gRPC server's routing +GrpcRouting.builder() + // This is step 1 - register security instance with gRPC server processing + // security - instance of security either from config or from a builder + // securityDefaults - default enforcement for each service that has a security definition + .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate())) + // this is step 2 - protect a service + // register and protect this service with authentication (from defaults) and role "user" + .register(greetService, GrpcSecurity.rolesAllowed("user")) + .build(); +---- + +[source,java] +.Example using builders for more fine grained method level security +---- +// create the service descriptor +ServiceDescriptor greetService = ServiceDescriptor.builder(new GreetService()) + // Add an instance of gRPC security that will apply to all methods of + // the service - in this case require the "user" role + .intercept(GrpcSecurity.rolesAllowed("user")) + // Add an instance of gRPC security that will apply to the "SetGreeting" + // method of the service - in this case require the "admin" role + .intercept("SetGreeting", GrpcSecurity.rolesAllowed("admin")) + .build(); + +// Create the gRPC server's routing +GrpcRouting.builder() + // This is step 1 - register security instance with gRPC server processing + // security - instance of security either from config or from a builder + // securityDefaults - default enforcement for each service that has a security definition + .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate())) + // this is step 2 - add the service descriptor + .register(greetService) + .build(); +---- + +[source,java] +.Example using configuration +---- +GrpcRouting.builder() + // helper method to load both security and gRPC server security from configuration + .intercept(GrpcSecurity.create(config)) + // continue with gRPC server route configuration... + .register(new GreetService()) + .build(); +---- + +[source,conf] +.Example using configuration - configuration (HOCON) +---- +# This may change in the future - to align with gRPC server configuration, +# once it is supported +security + grpc-server: + # Configuration of integration with gRPC server + defaults: + authenticate: true + # Configuration security for individual services + services: + - name: "GreetService" + defaults: + roles-allowed: ["user"] + # Configuration security for individual methods of the service + methods: + - name: "SetGreeting" + roles-allowed: ["admin"] +---- + +===== Client security +When using the Helidon SE gRPC client API security can be configured for a gRPC service +or at the individual method level. The client API has a custom `CallCredentials` implementation that +integrates with the Helidon security APIs. + +[source,java] +.Example configuring client security for a service +---- +Security security = Security.builder() // <1> + .addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth"))) + .build(); + +GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client")) // <2> + .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, user) + .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, password) + .build(); + +ClientServiceDescriptor descriptor = ClientServiceDescriptor // <3> + .builder(StringService.class) + .unary("Lower") + .callCredentials(clientSecurity) // <4> + .build(); + +GrpcServiceClient client = GrpcServiceClient.create(channel, descriptor); // <5> + +String response = client.blockingUnary("Lower", "ABCD"); // <6> +---- +<1> Create the Helidon `Security` instance (in this case using the basic auth provider) +<2> Create the `GrpcClientSecurity` gRPC `CallCredentials` adding the user and password +property expected by the basic auth provider. +<3> Create the gRPC `ClientServiceDescriptor` for the `StringService` gRPC service. +<4> Set the `GrpcClientSecurity` instance as the call credentials for all methods of the service +<5> Create a `GrpcServiceClient` that will allow methods to be called on the service +<6> Call the "Lower" method which will use the configured basic auth credentials + + +[source,java] +.Example configuring client security for a specific method +---- +GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client")) // <1> + .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, user) + .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, password) + .build(); + +ClientServiceDescriptor descriptor = ClientServiceDescriptor // <2> + .builder(StringService.class) + .unary("Lower") + .unary("Upper", rules -> rules.callCredentials(clientSecurity)) // <3> + .build(); +---- +<1> Create the `GrpcClientSecurity` call credentials in the same way as above. +<2> Create the `ClientServiceDescriptor`, this time with two unary methods, "Lower" and "Upper". +<3> The "Upper" method is configured to use the `GrpcClientSecurity` call credentials, the "Lower" method +will be called without any credentials. + + +===== Outbound security +Outbound security covers three scenarios: + +* Calling a secure gRPC service from inside a gRPC service method handler +* Calling a secure gRPC service from inside a web server method handler +* Calling a secure web endpoint from inside a gRPC service method handler + +Within each scenario credentials can be propagated if the gRPC/http method +handler is executing within a security context or credentials can be overridden +to provide a different set of credentials to use to call the outbound endpoint. + +[source,java] +.Example calling a secure gRPC service from inside a gRPC service method handler +---- +// Obtain the SecurityContext from the current gRPC call Context +SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get(); + +// Create a gRPC CallCredentials that will use the current request's +// security context to configure outbound credentials +GrpcClientSecurity clientSecurity = GrpcClientSecurity.create(securityContext); + +// Create the gRPC stub using the CallCredentials +EchoServiceGrpc.EchoServiceBlockingStub stub = noCredsEchoStub.withCallCredentials(clientSecurity); +---- + +[source,java] +.Example calling a secure gRPC service from inside a web server method handler +---- +private static void propagateCredentialsWebRequest(ServerRequest req, ServerResponse res) { + try { + // Create a gRPC CallCredentials that will use the current request's + // security context to configure outbound credentials + GrpcClientSecurity clientSecurity = GrpcClientSecurity.create(req); + + // Create the gRPC stub using the CallCredentials + EchoServiceGrpc.EchoServiceBlockingStub stub = noCredsEchoStub.withCallCredentials(clientSecurity); + + String message = req.queryParams().first("message").orElse(null); + Echo.EchoResponse echoResponse = stub.echo(Echo.EchoRequest.newBuilder().setMessage(message).build()); + res.send(echoResponse.getMessage()); + } catch (StatusRuntimeException e) { + res.status(GrpcHelper.toHttpResponseStatus(e)).send(); + } catch (Throwable thrown) { + res.status(Http.ResponseStatus.create(500, thrown.getMessage())).send(); + } +} +---- + +[source,java] +.Example calling a secure web endpoint from inside a gRPC service method handler +---- +// Obtain the SecurityContext from the gRPC call Context +SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get(); + +// Use the SecurityContext as normal to make a http request +Response webResponse = client.target(url) + .path("/test") + .request() + .property(ClientSecurity.PROPERTY_CONTEXT, securityContext) + .get(); +---- + +include::marshalling.adoc[leveloffset=2] + +== Configuration +Configure the gRPC Server using the Helidon configuration framework, either programmatically +or via a configuration file. + +=== Configuring the gRPC Server in your code + +The easiest way to configure the gRPC Server is in your application code. + +[source,java] +---- +GrpcServerConfiguration configuration = GrpcServerConfiguration.builder() + .port(8080) + .build(); +GrpcServer grpcServer = GrpcServer.create(configuration, routing); +---- + +See all configuration options +link:{javadoc-base-url}/io.helidon.grpc/GrpcServerConfiguration.html[here]. + +=== Configuring the gRPC Server in a configuration file + +You can also define the gRPC server configuration in a file. + +include::{rootdir}/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc[leveloffset=1, tag=config] + +.GrpcServer configuration file example using `application.yaml` +[source,yaml] +---- +grpcserver: + port: 3333 +---- + +Then, in your application code, load the configuration from that file. + +[source,java] +.GrpcServer initialization using the `application.conf` file located on the classpath +---- +GrpcServerConfiguration configuration = GrpcServerConfiguration.create( + Config.builder() + .sources(classpath("application.conf")) + .build()); + +GrpcServer grpcServer = GrpcServer.create(configuration, routing); +---- + +== Examples +=== Quick Start + +Here is the code for a minimalist gRPC application that runs on a default port (1408): + +[source,java] +---- +public static void main(String[] args) throws Exception { + GrpcServer grpcServer = GrpcServer + .create(GrpcRouting.builder() + .register(new HelloService()) // <1> + .build()) + .start() // <2> + .toCompletableFuture() + .get(10, TimeUnit.SECONDS); // Implement the simplest possible gRPC service. // <3> + + System.out.println("gRPC Server started at: http://localhost:" + grpcServer.port()); // <4> +} + +static class HelloService implements GrpcService { // <5> + @Override + public void update(ServiceDescriptor.Rules rules) { + rules.unary("SayHello", ((request, responseObserver) -> complete(responseObserver, "Hello " + request))); // <6> + rules.marshallerSupplier(new JavaMarshaller.Supplier()); // <7> + } +} +---- + +<1> Register gRPC service. +<2> Start the server. +<3> Wait for the server to start while throwing possible errors as exceptions. +<4> The server is bound to a default port (1408). +<5> Implement the simplest possible gRPC service. +<6> Add unary method `HelloService/SayHello` to the service definition. +<7> Specify custom marshaller using Java serialization to marshall requests and responses. diff --git a/docs/se/grpc/service-implementation.adoc b/docs/se/grpc/service-implementation.adoc deleted file mode 100644 index a7a7c57c00b..00000000000 --- a/docs/se/grpc/service-implementation.adoc +++ /dev/null @@ -1,165 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// - - Copyright (c) 2019, 2022 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. - -/////////////////////////////////////////////////////////////////////////////// - -= gRPC Service Implementation -:description: Helidon gRPC Service Implementation -:keywords: helidon, grpc, java -:rootdir: {docdir}/../.. - -include::{rootdir}/includes/se.adoc[] - -== Service Implementation - -While Helidon gRPC Server allows you to deploy any standard gRPC service that -implements `io.grpc.BindableService` interface, including services generated -from the Protobuf IDL files (and even allows you to customize them to a certain -extent), using Helidon gRPC framework to implement your services has a number of -benefits: - -* It allows you to define both HTTP and gRPC services using similar programming - model, simplifying learning curve for developers. - -* It provides a number of helper methods that make service implementation - significantly simpler. - -* It allows you to configure some of the Helidon value-added features, such - as xref:security.adoc[security] and xref:metrics.adoc[metrics collection] - down to the method level. - -* It allows you to easily specify custom marshaller for requests and - responses if Protobuf does not satisfy your needs. - -* It provides built in support for xref:health-checks.adoc[health checks]. - -== Service Implementation Basics - -At the very basic level, all you need to do in order to implement a Helidon -gRPC service is create a class that implements `io.helidon.grpc.server.GrpcService` -interface and define one or more methods for the service: - -[source,java] ----- -class EchoService implements GrpcService { - - @Override - public void update(ServiceDescriptor.Rules rules) { - rules.unary("Echo", this::echo); // <1> - } - - /** - * Echo the message back to the caller. - * - * @param request the echo request containing the message to echo - * @param observer the response observer - */ - public void echo(String request, StreamObserver observer) { // <2> - complete(observer, request); // <3> - } -} ----- - -<1> Define unary method `Echo` and map it to the `this::echo` handler. -<2> Create a handler for the `Echo` method. -<3> Send the request string back to the client by completing response observer. - -NOTE: The `complete` method shown in the example above is just one of many helper - methods available in the `GrpcService` class. See the full list - link:{grpc-server-javadoc-base-url}/io/helidon/grpc/server/GrpcService.html[here]. - -The example above implements a service with a single unary method, which will be -exposed at the `EchoService/Echo' endpoint. The service does not explicitly define -a marshaller for requests and responses, so Java serialization will be used as a -default. - -Unfortunately, this implies that you will have to implement clients by hand and -configure them to use the same marshaller as the server. Obviously, one of the -major selling points of gRPC is that it makes it easy to generate clients for a -number of languages (as long as you use Protobuf for marshalling), so let's see -how we would implement Protobuf enabled Helidon gRPC service. - -== Implementing Protobuf Services - -In order to implement Protobuf-based service, you would follow the official -link:https://grpc.io/docs/quickstart/java.html[instructions] on the gRPC -web site, which boil down to the following: - -=== Define the Service IDL - -For this example, we will re-implement the `EchoService` above as a Protobuf -service in `echo.proto` file. - -[source, proto] ----- -syntax = "proto3"; -option java_package = "org.example.services.echo"; - -service EchoService { - rpc Echo (EchoRequest) returns (EchoResponse) {} -} - -message EchoRequest { - string message = 1; -} - -message EchoResponse { - string message = 1; -} ----- - -Based on this IDL, the gRPC compiler will generate message classes (`EchoRequest` -and `EchoResponse`), client stubs that can be used to make RPC calls to the server, -as well as the base class for the server-side service implementation. - -We can ignore the last one, and implement the service using Helidon gRPC framework -instead. - -=== Implement the Service - -The service implementation will be very similar to our original implementation: - -[source,java] ----- -class EchoService implements GrpcService { - - @Override - public void update(ServiceDescriptor.Rules rules) { - rules.proto(Echo.getDescriptor()) // <1> - .unary("Echo", this::echo); // <2> - } - - /** - * Echo the message back to the caller. - * - * @param request the echo request containing the message to echo - * @param observer the response observer - */ - public void echo(Echo.EchoRequest request, StreamObserver observer) { // <3> - String message = request.getMessage(); // <4> - Echo.EchoResponse response = Echo.EchoResponse.newBuilder().setMessage(message).build(); // <5> - complete(observer, response); // <6> - } -} ----- - -<1> Specify proto descriptor in order to provide necessary type information and - enable Protobuf marshalling. -<2> Define unary method `Echo` and map it to the `this::echo` handler. -<3> Create a handler for the `Echo` method, using Protobuf message types for request and response. -<4> Extract message string from the request. -<5> Create the response containing extracted message. -<6> Send the response back to the client by completing response observer. diff --git a/docs/se/introduction.adoc b/docs/se/introduction.adoc index 00c771677c5..e90b057be69 100644 --- a/docs/se/introduction.adoc +++ b/docs/se/introduction.adoc @@ -76,7 +76,7 @@ Build GraphQL servers. //gRPC [CARD] .gRPC -[icon=swap_horiz,link=grpc/introduction.adoc] +[icon=swap_horiz,link=grpc/server.adoc] -- Build gRPC servers and clients. -- diff --git a/docs/sitegen.yaml b/docs/sitegen.yaml index 48fd3894b6b..5ba40a239b6 100644 --- a/docs/sitegen.yaml +++ b/docs/sitegen.yaml @@ -132,24 +132,14 @@ backend: type: "icon" value: "storage" - type: "MENU" - title: "gRPC server" + title: "gRPC" dir: "grpc" glyph: type: "icon" value: "swap_horiz" sources: - - "introduction.adoc" - - "configuration.adoc" - - "routing.adoc" - - "service-implementation.adoc" - - "interceptors.adoc" - - "health-checks.adoc" - - "metrics.adoc" - - "security.adoc" - - "marshalling.adoc" - - "client-introduction.adoc" - - "client-configuration.adoc" - - "client-implementation.adoc" + - "server.adoc" + - "client.adoc" - type: "PAGE" title: "GraphQL server" source: "graphql.adoc" @@ -354,14 +344,14 @@ backend: type: "icon" value: "warning" - type: "MENU" - title: "gRPC server" + title: "gRPC" dir: "grpc" glyph: type: "icon" value: "swap_horiz" sources: - - "mp-server-side-services.adoc" - - "mp-clients.adoc" + - "server-side-services.adoc" + - "client.adoc" - type: "PAGE" title: "GraphQL" source: "graphql.adoc" diff --git a/grpc/client/pom.xml b/grpc/client/pom.xml index 7458f197507..2f7d614a224 100644 --- a/grpc/client/pom.xml +++ b/grpc/client/pom.xml @@ -100,6 +100,18 @@ provided true + + io.helidon.config + helidon-config-metadata + provided + true + + + io.helidon.config + helidon-config-metadata-processor + provided + true + diff --git a/grpc/client/src/main/java/io/helidon/grpc/client/GrpcChannelDescriptor.java b/grpc/client/src/main/java/io/helidon/grpc/client/GrpcChannelDescriptor.java index abdf3b7b575..be059ed7ed9 100644 --- a/grpc/client/src/main/java/io/helidon/grpc/client/GrpcChannelDescriptor.java +++ b/grpc/client/src/main/java/io/helidon/grpc/client/GrpcChannelDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 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. @@ -18,6 +18,8 @@ import java.util.Optional; +import io.helidon.config.metadata.Configured; +import io.helidon.config.metadata.ConfiguredOption; import io.helidon.config.objectmapping.Value; import io.helidon.grpc.core.GrpcTlsDescriptor; @@ -127,6 +129,7 @@ public Optional tlsDescriptor() { /** * Builder builds a GrpcChannelDescriptor. */ + @Configured public static class Builder implements io.helidon.common.Builder { private boolean inProcessChannel; private String host = GrpcChannelsProvider.DEFAULT_HOST; @@ -157,6 +160,7 @@ public Builder inProcess() { * * @see io.grpc.ManagedChannelBuilder#forTarget(String) */ + @ConfiguredOption() @Value public Builder target(String target) { this.target = target; @@ -169,6 +173,7 @@ public Builder target(String target) { * * @return this instance for fluent API */ + @ConfiguredOption(value = GrpcChannelsProvider.DEFAULT_HOST) @Value(withDefault = GrpcChannelsProvider.DEFAULT_HOST) public Builder host(String host) { this.host = host; @@ -181,6 +186,7 @@ public Builder host(String host) { * * @return this instance for fluent API */ + @ConfiguredOption(value = "" + GrpcChannelsProvider.DEFAULT_PORT) @Value(withDefault = "" + GrpcChannelsProvider.DEFAULT_PORT) public Builder port(int port) { this.port = port; @@ -194,6 +200,7 @@ public Builder port(int port) { * * @return this instance for fluent API */ + @ConfiguredOption(key = "tls") @Value(key = "tls") public Builder sslDescriptor(GrpcTlsDescriptor tlsDescriptor) { this.tlsDescriptor = tlsDescriptor; @@ -208,6 +215,7 @@ public Builder sslDescriptor(GrpcTlsDescriptor tlsDescriptor) { * * @see io.grpc.ManagedChannelBuilder#defaultLoadBalancingPolicy(String) */ + // @ConfiguredOption public Builder loadBalancerPolicy(String policy) { loadBalancerPolicy = policy; return this; @@ -221,6 +229,7 @@ public Builder loadBalancerPolicy(String policy) { * * @see io.grpc.ManagedChannelBuilder#nameResolverFactory(io.grpc.NameResolver.Factory) */ + // @ConfiguredOption public Builder nameResolverFactory(NameResolver.Factory factory) { this.nameResolver = factory; return this; diff --git a/grpc/client/src/main/java/module-info.java b/grpc/client/src/main/java/module-info.java index 6c3a1ba0629..892ea0dbaf4 100644 --- a/grpc/client/src/main/java/module-info.java +++ b/grpc/client/src/main/java/module-info.java @@ -23,4 +23,6 @@ requires transitive io.helidon.grpc.core; requires io.helidon.tracing; + + requires static io.helidon.config.metadata; } diff --git a/grpc/core/pom.xml b/grpc/core/pom.xml index a23701f1087..8c8343e6f6a 100644 --- a/grpc/core/pom.xml +++ b/grpc/core/pom.xml @@ -150,5 +150,17 @@ mockito-core test + + io.helidon.config + helidon-config-metadata + provided + true + + + io.helidon.config + helidon-config-metadata-processor + provided + true + diff --git a/grpc/core/src/main/java/io/helidon/grpc/core/GrpcTlsDescriptor.java b/grpc/core/src/main/java/io/helidon/grpc/core/GrpcTlsDescriptor.java index c6bd361b291..b08e73e5aa9 100644 --- a/grpc/core/src/main/java/io/helidon/grpc/core/GrpcTlsDescriptor.java +++ b/grpc/core/src/main/java/io/helidon/grpc/core/GrpcTlsDescriptor.java @@ -18,6 +18,8 @@ import io.helidon.common.configurable.Resource; import io.helidon.config.Config; +import io.helidon.config.metadata.Configured; +import io.helidon.config.metadata.ConfiguredOption; import io.helidon.config.objectmapping.Value; /** @@ -109,6 +111,7 @@ public Resource tlsCaCert() { /** * Builder to build a new instance of {@link GrpcTlsDescriptor}. */ + @Configured public static class Builder implements io.helidon.common.Builder { private boolean enabled = true; @@ -139,6 +142,7 @@ private Builder(Config config) { * @param enabled true to enable, false otherwise * @return this instance for fluent API */ + @ConfiguredOption(value = "true") @Value(withDefault = "true") public Builder enabled(boolean enabled) { this.enabled = enabled; @@ -150,6 +154,7 @@ public Builder enabled(boolean enabled) { * @param jdkSSL true to use JDK based SSL, false otherwise * @return this instance for fluent API */ + @ConfiguredOption(key = "jdk-ssl") @Value(key = "jdk-ssl") public Builder jdkSSL(boolean jdkSSL) { this.jdkSSL = jdkSSL; @@ -161,6 +166,7 @@ public Builder jdkSSL(boolean jdkSSL) { * @param tlsCert the path to client's certificate * @return this instance for fluent API */ + @ConfiguredOption(key = "tls-cert") @Value(key = "tls-cert") public Builder tlsCert(Resource tlsCert) { this.tlsCert = tlsCert; @@ -172,6 +178,7 @@ public Builder tlsCert(Resource tlsCert) { * @param tlsKey the 's TLS private key * @return this instance for fluent API */ + @ConfiguredOption(key = "tls-key") @Value(key = "tls-key") public Builder tlsKey(Resource tlsKey) { this.tlsKey = tlsKey; @@ -183,6 +190,7 @@ public Builder tlsKey(Resource tlsKey) { * @param caCert the path to CA certificate * @return this instance for fluent API */ + @ConfiguredOption(key = "tls-ca-cert") @Value(key = "tls-ca-cert") public Builder tlsCaCert(Resource caCert) { this.tlsCaCert = caCert; diff --git a/grpc/core/src/main/java/module-info.java b/grpc/core/src/main/java/module-info.java index 0ff20524262..b6aa0a60d31 100644 --- a/grpc/core/src/main/java/module-info.java +++ b/grpc/core/src/main/java/module-info.java @@ -47,6 +47,8 @@ requires jakarta.inject; + requires static io.helidon.config.metadata; + provides MarshallerSupplier with MarshallerSupplier.DefaultMarshallerSupplier, MarshallerSupplier.ProtoMarshallerSupplier, diff --git a/grpc/server/pom.xml b/grpc/server/pom.xml index 6b4563dad26..ff1baeaa4c7 100644 --- a/grpc/server/pom.xml +++ b/grpc/server/pom.xml @@ -141,6 +141,18 @@ provided true + + io.helidon.config + helidon-config-metadata + provided + true + + + io.helidon.config + helidon-config-metadata-processor + provided + true + diff --git a/grpc/server/src/main/java/io/helidon/grpc/server/GrpcServerConfiguration.java b/grpc/server/src/main/java/io/helidon/grpc/server/GrpcServerConfiguration.java index 93291ebf180..41f950385f5 100644 --- a/grpc/server/src/main/java/io/helidon/grpc/server/GrpcServerConfiguration.java +++ b/grpc/server/src/main/java/io/helidon/grpc/server/GrpcServerConfiguration.java @@ -21,6 +21,8 @@ import io.helidon.common.context.Context; import io.helidon.config.Config; +import io.helidon.config.metadata.Configured; +import io.helidon.config.metadata.ConfiguredOption; import io.helidon.grpc.core.GrpcTlsDescriptor; import io.helidon.tracing.Tracer; @@ -146,6 +148,7 @@ static Builder builder(Config config) { /** * A {@link GrpcServerConfiguration} builder. */ + @Configured final class Builder implements io.helidon.common.Builder { private static final AtomicInteger GRPC_SERVER_COUNTER = new AtomicInteger(1); @@ -174,6 +177,22 @@ private Builder() { * @param config configuration instance * @return updated builder */ + @ConfiguredOption(key = "name", + type = String.class, + value = DEFAULT_NAME, + description = "Name of the gRPC server") + @ConfiguredOption(key = "port", + type = Integer.class, + value = "" + DEFAULT_PORT, + description = "Specify the gRPC server port") + @ConfiguredOption(key = "native", + type = Boolean.class, + value = "false", + description = "Specify if native transport should be used.") + @ConfiguredOption(key = "workers", + type = String.class, + value = "Number of processors available to the JVM", + description = "Specify the count of threads in pool used to process HTTP requests.") public Builder config(Config config) { if (config == null) { return this; diff --git a/grpc/server/src/main/java/module-info.java b/grpc/server/src/main/java/module-info.java index 8d8e6a246e4..c92ae5c618c 100644 --- a/grpc/server/src/main/java/module-info.java +++ b/grpc/server/src/main/java/module-info.java @@ -34,5 +34,7 @@ requires jakarta.annotation; requires java.logging; + requires static io.helidon.config.metadata; + requires jakarta.inject; } From 30d09dc2e701d7618c1f62f7a84fe8218d90b35c Mon Sep 17 00:00:00 2001 From: Keith Lustria Date: Tue, 19 Jul 2022 09:34:47 -0700 Subject: [PATCH 2/2] Update @ConfiguredOption(s) and grpcserver paramter to grpc in the SE gRPC config document --- .../io_helidon_grpc_core_GrpcTlsDescriptor.adoc | 2 +- ...lidon_grpc_server_GrpcServerConfiguration.adoc | 13 ++++++++++--- docs/se/grpc/server.adoc | 2 +- .../grpc/client/GrpcChannelDescriptor.java | 2 -- .../io/helidon/grpc/core/GrpcTlsDescriptor.java | 7 +++---- .../grpc/server/GrpcServerConfiguration.java | 15 +++------------ 6 files changed, 18 insertions(+), 23 deletions(-) diff --git a/docs/config/io_helidon_grpc_core_GrpcTlsDescriptor.adoc b/docs/config/io_helidon_grpc_core_GrpcTlsDescriptor.adoc index ac508a5292b..a652686862d 100644 --- a/docs/config/io_helidon_grpc_core_GrpcTlsDescriptor.adoc +++ b/docs/config/io_helidon_grpc_core_GrpcTlsDescriptor.adoc @@ -43,7 +43,7 @@ Optional configuration options: |key |type |default value |description |`enabled` |boolean |`true` |Enable or disable TLS. If enabled is false then the rest of the TLS configuration properties are ignored. -|`jdk-ssl` |boolean |{nbsp} |Sets the type of SSL implementation to be used. +|`jdk-ssl` |boolean |`false` |Sets the type of SSL implementation to be used. |`tls-ca-cert` |xref:{rootdir}/config/io_helidon_common_configurable_Resource.adoc[Resource] |{nbsp} |Set the CA (certificate authority) certificate path. |`tls-cert` |xref:{rootdir}/config/io_helidon_common_configurable_Resource.adoc[Resource] |{nbsp} |Set the client tlsCert path. Required only if mutual auth is desired. |`tls-key` |xref:{rootdir}/config/io_helidon_common_configurable_Resource.adoc[Resource] |{nbsp} |Set the client private key path. Required only if mutual auth is desired. diff --git a/docs/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc b/docs/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc index 9c02067f5e7..53ed5837be1 100644 --- a/docs/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc +++ b/docs/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc @@ -42,10 +42,17 @@ Optional configuration options: |=== |key |type |default value |description -|`name` |string |`grpc.server` |Name of the gRPC server +|`name` |string |`grpc.server` |Set the name of the gRPC server. + + Configuration key: `name` |`native` |boolean |`false` |Specify if native transport should be used. -|`port` |int |`1408` |Specify the gRPC server port -|`workers` |string |`Number of processors available to the JVM` |Specify the count of threads in pool used to process HTTP requests. +|`port` |int |`1408` |Sets server port. If port is `0` or less then any available ephemeral port will be used. + + Configuration key: `port` +|`workers` |int |`Number of processors available to the JVM` |Sets a count of threads in pool used to process HTTP requests. + Default value is `CPU_COUNT * 2`. + + Configuration key: `workers` |=== diff --git a/docs/se/grpc/server.adoc b/docs/se/grpc/server.adoc index c149e37ffae..df6e4a9b89b 100644 --- a/docs/se/grpc/server.adoc +++ b/docs/se/grpc/server.adoc @@ -879,7 +879,7 @@ include::{rootdir}/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc[le .GrpcServer configuration file example using `application.yaml` [source,yaml] ---- -grpcserver: +grpc: port: 3333 ---- diff --git a/grpc/client/src/main/java/io/helidon/grpc/client/GrpcChannelDescriptor.java b/grpc/client/src/main/java/io/helidon/grpc/client/GrpcChannelDescriptor.java index be059ed7ed9..25e5324e91e 100644 --- a/grpc/client/src/main/java/io/helidon/grpc/client/GrpcChannelDescriptor.java +++ b/grpc/client/src/main/java/io/helidon/grpc/client/GrpcChannelDescriptor.java @@ -215,7 +215,6 @@ public Builder sslDescriptor(GrpcTlsDescriptor tlsDescriptor) { * * @see io.grpc.ManagedChannelBuilder#defaultLoadBalancingPolicy(String) */ - // @ConfiguredOption public Builder loadBalancerPolicy(String policy) { loadBalancerPolicy = policy; return this; @@ -229,7 +228,6 @@ public Builder loadBalancerPolicy(String policy) { * * @see io.grpc.ManagedChannelBuilder#nameResolverFactory(io.grpc.NameResolver.Factory) */ - // @ConfiguredOption public Builder nameResolverFactory(NameResolver.Factory factory) { this.nameResolver = factory; return this; diff --git a/grpc/core/src/main/java/io/helidon/grpc/core/GrpcTlsDescriptor.java b/grpc/core/src/main/java/io/helidon/grpc/core/GrpcTlsDescriptor.java index b08e73e5aa9..09cb9c12968 100644 --- a/grpc/core/src/main/java/io/helidon/grpc/core/GrpcTlsDescriptor.java +++ b/grpc/core/src/main/java/io/helidon/grpc/core/GrpcTlsDescriptor.java @@ -154,8 +154,7 @@ public Builder enabled(boolean enabled) { * @param jdkSSL true to use JDK based SSL, false otherwise * @return this instance for fluent API */ - @ConfiguredOption(key = "jdk-ssl") - @Value(key = "jdk-ssl") + @ConfiguredOption(key = "jdk-ssl", value = "false") public Builder jdkSSL(boolean jdkSSL) { this.jdkSSL = jdkSSL; return this; @@ -166,7 +165,7 @@ public Builder jdkSSL(boolean jdkSSL) { * @param tlsCert the path to client's certificate * @return this instance for fluent API */ - @ConfiguredOption(key = "tls-cert") + @ConfiguredOption @Value(key = "tls-cert") public Builder tlsCert(Resource tlsCert) { this.tlsCert = tlsCert; @@ -178,7 +177,7 @@ public Builder tlsCert(Resource tlsCert) { * @param tlsKey the 's TLS private key * @return this instance for fluent API */ - @ConfiguredOption(key = "tls-key") + @ConfiguredOption @Value(key = "tls-key") public Builder tlsKey(Resource tlsKey) { this.tlsKey = tlsKey; diff --git a/grpc/server/src/main/java/io/helidon/grpc/server/GrpcServerConfiguration.java b/grpc/server/src/main/java/io/helidon/grpc/server/GrpcServerConfiguration.java index 41f950385f5..ac13a4ae89e 100644 --- a/grpc/server/src/main/java/io/helidon/grpc/server/GrpcServerConfiguration.java +++ b/grpc/server/src/main/java/io/helidon/grpc/server/GrpcServerConfiguration.java @@ -177,22 +177,10 @@ private Builder() { * @param config configuration instance * @return updated builder */ - @ConfiguredOption(key = "name", - type = String.class, - value = DEFAULT_NAME, - description = "Name of the gRPC server") - @ConfiguredOption(key = "port", - type = Integer.class, - value = "" + DEFAULT_PORT, - description = "Specify the gRPC server port") @ConfiguredOption(key = "native", type = Boolean.class, value = "false", description = "Specify if native transport should be used.") - @ConfiguredOption(key = "workers", - type = String.class, - value = "Number of processors available to the JVM", - description = "Specify the count of threads in pool used to process HTTP requests.") public Builder config(Config config) { if (config == null) { return this; @@ -215,6 +203,7 @@ public Builder config(Config config) { * * @return an updated builder */ + @ConfiguredOption(key = "name", value = DEFAULT_NAME) public Builder name(String name) { this.name = name == null ? null : name.trim(); return this; @@ -228,6 +217,7 @@ public Builder name(String name) { * @param port the server port * @return an updated builder */ + @ConfiguredOption(value = "" + DEFAULT_PORT) public Builder port(int port) { this.port = port < 0 ? 0 : port; return this; @@ -287,6 +277,7 @@ public Builder tracingConfig(GrpcTracingConfig tracingConfig) { * @param workers a workers count * @return an updated builder */ + @ConfiguredOption(key = "workers", value = "Number of processors available to the JVM") public Builder workersCount(int workers) { this.workers = workers; return this;