diff --git a/docs/includes/attributes.adoc b/docs/includes/attributes.adoc index d00c524218d..9c1a8cf83d7 100644 --- a/docs/includes/attributes.adoc +++ b/docs/includes/attributes.adoc @@ -197,7 +197,7 @@ endif::[] :config-mapping-javadoc-base-url: {javadoc-base-url}/io.helidon.config.objectmapping :configurable-javadoc-base-url: {javadoc-base-url}/io.helidon.common.configurable :faulttolerance-javadoc-base-url: {javadoc-base-url}/io.helidon.faulttolerance -:grpc-server-javadoc-base-url: {javadoc-base-url}/io.helidon.grpc.server +:grpc-server-javadoc-base-url: {javadoc-base-url}/io.helidon.webserver.grpc :health-javadoc-base-url: {javadoc-base-url}/io.helidon.health.checks :integration-oci-sdk-cdi-javadoc-base-url: {javadoc-base-url}/io.helidon.integrations.oci.sdk.cdi :media-jsonp-javadoc-base-url: {javadoc-base-url}/io.helidon.http.media.jsonp diff --git a/docs/includes/grpc-marshalling.adoc b/docs/includes/grpc-marshalling.adoc deleted file mode 100644 index 1010f46fdb1..00000000000 --- a/docs/includes/grpc-marshalling.adoc +++ /dev/null @@ -1,107 +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. - -/////////////////////////////////////////////////////////////////////////////// - -ifndef::rootdir[:rootdir: {docdir}/..] -:description: Helidon gRPC Marshalling -:keywords: helidon, grpc, java, marshalling - -= Marshalling - -== Default Marshalling Support - -Helidon gRPC supports Protobuf out of the box. The Protobuf marshaller will be used by default for any request and response classes that extend `com.google.protobuf.MessageLite`, which is the case for all classes generated from a `proto` file using `protoc` compiler. - -That means that you don't need any special handling or configuration in order to support Protobuf serialization of requests and responses. - -== Custom Marshalling - -Helidon makes the use of custom marshallers trivial and provides one custom implementation, link:{helidon-github-tree-url}/grpc/core/src/main/java/io/helidon/grpc/core/JsonbMarshaller.java[JsonbMarshaller], out of the box. - -You can also easily implement your own marshaller to support serialization formats that are not supported natively -by Helidon, by implementing `Marshaller` and `MarshallerSupplier` interfaces. As an example, check out -the source code of the built-in marshaller: -link:{helidon-github-tree-url}/grpc/core/src/main/java/io/helidon/grpc/core/JsonbMarshaller.java[JsonbMarshaller.java]. - -Furthermore, https://coherence.community/[Oracle Coherence CE] provides a marshaller for a highly optimized, binary, platform independent Portable Object Format (POF). You can find more information about POF in https://coherence.community/20.12/docs/#/docs/core/04_portable_types[Coherence documentation] - -=== Setting the custom marshaller -ifeval::["{flavor-lc}" == "mp"] - -You can annotate your service's class or interface with @GrpcMarshaller: - -.Sample code with `@GrpcMarshaller` annotation -[source,java] ----- -@Grpc -@ApplicationScoped -@GrpcMarshaller("jsonb") //<1> -public class AsyncStringService { - // code is omitted -} ----- -<1> Set the named marshaller supplier via the @GrpcMarshaller annotation. - -endif::[] - -ifeval::["{flavor-lc}" == "se"] - -ifeval::["{feature-name}" == "gRPC Server"] -You can implement the `update` method on your service's class and set the custom marshaller supplier via the -`ServiceDescriptor.Rules.marshallerSupplier()` method: - -.Sample code for setting the marshaller on the gRPC service -[source,java] ----- -public class GreetServiceJava - implements GrpcService { - private String greeting; - - - public GreetServiceJava(Config config) { - this.greeting = config.get("app.greeting").asString().orElse("Ciao"); - } - - @Override - public void update(ServiceDescriptor.Rules rules) { - rules.marshallerSupplier(new JsonbMarshaller.Supplier()) // <1> - .unary("Greet", this::greet) - .unary("SetGreeting", this::setGreeting); - } - - // Implement Service methods -} ----- -<1> Specify the custom marshaller to use. -endif::[] - -ifeval::["{feature-name}" == "gRPC Client"] -You can set the custom marshaller supplier via the `ClientServiceDescriptor.builder.marshallerSupplier()` method: - -.Sample code for setting the marshaller on the ClientServiceDescriptor -[source,java] ----- -ClientServiceDescriptor descriptor = ClientServiceDescriptor - .builder(HelloService.class) - .marshallerSupplier(new JsonbMarshaller.Supplier()) // <1> - .clientStreaming("JoinString") - .build(); ----- -<1> Specify the custom marshaller to use. -endif::[] - -endif::[] diff --git a/docs/mp/aot.adoc b/docs/mp/aot.adoc index fe9c1307899..be0afdab800 100644 --- a/docs/mp/aot.adoc +++ b/docs/mp/aot.adoc @@ -104,9 +104,9 @@ for native image. |✅ |{nbsp} |Security |{nbsp} |✅ |{nbsp} |Tracing |{nbsp} |✅ |{nbsp} |Websocket |Server only. -|✅ |gRPC Server |gRPC Server |Since GraalVM 21.0.0 +|✅ |gRPC Server |gRPC Server |{nbsp} |✅ |{nbsp} |Metrics |{nbsp} -|✅ |gRPC Client |gRPC Client |Since GraalVM 21.0.0 +|✅ |gRPC Client |gRPC Client |{nbsp} |✅ |{nbsp} |Metrics |{nbsp} |✅ |Scheduling |Scheduling |{nbsp} |✅ |OCI |OCI Integration |Modules with group id `io.helidon.integrations.oci` diff --git a/docs/mp/grpc/client.adoc b/docs/mp/grpc/client.adoc index 4a7daeadfcf..b34861eb60b 100644 --- a/docs/mp/grpc/client.adoc +++ b/docs/mp/grpc/client.adoc @@ -28,261 +28,10 @@ include::{rootdir}/includes/mp.adoc[] == Contents - <> -- <> -- <> -- <> -- <> -- <> == Overview -Building Java-based gRPC clients using the Helidon MP gRPC APIs is very simple and removes a lot of the boilerplate code typically -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[] - -[source,xml] ----- - - io.helidon.microprofile.grpc - helidon-microprofile-grpc-client - ----- - -== API - -The following annotations are used to work with Helidon MP gRPC clients: - -* `@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. - -== 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. - -// grpc client is temporarily removed from Helidon -// 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:{rootdir}/mp/config/introduction.adoc[configuration in Helidon] - -.General form of a gRPC channels configuration -[source,yaml] ----- -grpc: - channels: # <1> - test-server: # <2> - host: localhost # <3> - port: 1408 # <4> ----- -<1> Channels are configured in the `channels` section. -<2> Each subsection is the channel name that is then used to refer to this channel in the application code. -<3> Each channel contains a host name. -<4> It also contains a port. - -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] ----- -grpc: - channels: - london: - host: london.foo.com - port: 1408 - new-york: - host: ny.foo.com - port: 1408 ----- -The above example shows two channel configurations, one named `london` and the other `new-york`. - -=== Configuring TLS -It is also possible to configure a Channel to use TLS if the server is using TLS. - -// grpc client is temporarily removed from Helidon -// include::{rootdir}/config/io_helidon_grpc_core_GrpcTlsDescriptor.adoc[leveloffset=3] - -.TLS on gRPC Channels configuration example -[source,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> ----- -<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. -<3> The `tls-cert` value is the location of the TLS certificate file. -<4> The `tls-key` value is the location of the TLS key file. -<5> The `tls-ca-cert` value is the location of the TLS CA certificate file. - -The SSL configuration uses the Helidon `Resource` class to locate configured keys and certificates. -In the example above the `tls-cert-path` config key has the `-path` suffix which tells the configuration to load `/certs/foo.cert` -as a file. If `/certs/foo.cert` was a resource on the classpath, the configuration key could have been changed to -`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, then they can be used by the 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: - -.gRPC Channel Injection -[source,java] ----- - @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 examples provided in the <>. - -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 the 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. - -.gRPC in-Process Channel Injection -[source,java] ----- - @Inject // <1> - @InProcessGrpcChannel // <2> - private Channel channel; ----- -<1> The `@Inject` annotation tells CDI to identify the injectable qualifiers. -<2> The `@InProcessGrpcChannel` is the qualifier that is used to tell the Helidon MP gRPC API to inject an in-process channel. - - -=== Using the Client Interface in an Application -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. - -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] -.Simple gRPC Service ----- -@ApplicationScoped -public class Client { - - @Inject // <1> - @GrpcProxy // <2> - @GrpcChannel(name = "test-server") // <3> - private StringService stringService; -} ----- - -<1> The `@Inject` annotation tells the CDI to inject the client implementation. -<2> The `@GrpcProxy` annotation is used by the CDI container to match the injection point to the gRPC MP APIs provider. -<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. - -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 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.adoc#_defining_service_methods[Defining Service methods] section of the xref:server.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 types, 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); -} ----- - -== Examples -link:{helidon-github-tree-url}/examples/grpc/microprofile/basic-client[Basic gRPC Client] example demonstrates a -simple gRPC client that invokes services from deployed gRPC server applications provided in the -link:{helidon-github-tree-url}/examples/grpc/microprofile/basic-server-implicit[Basic gRPC Server] and -link:{helidon-github-tree-url}/examples/grpc/microprofile/metrics[gRPC Server metrics] examples. +gRPC is temporarily removed in Helidon, please follow issue +https://github.com/helidon-io/helidon/issues/5418 +If you require gRPC in Helidon MP, kindly stay with Helidon MP 3.x, until the issue is resolved. diff --git a/docs/mp/grpc/server.adoc b/docs/mp/grpc/server.adoc index db44fa04cbc..12e67136794 100644 --- a/docs/mp/grpc/server.adoc +++ b/docs/mp/grpc/server.adoc @@ -28,327 +28,10 @@ include::{rootdir}/includes/mp.adoc[] == Contents - <> -- <> -- <> -- <> -- <> -- <> == Overview -The gRPC Microprofile APIs are an extension to xref:{rootdir}/mp/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 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. -Building gRPC services using Helidon gRPC MP is very simple and allows the developer to concentrate on their -application logic without needing to write a lot of boilerplate gRPC code. +gRPC is temporarily removed in Helidon, please follow issue +https://github.com/helidon-io/helidon/issues/5418 -include::{rootdir}/includes/dependencies.adoc[] - -[source,xml] ----- - - io.helidon.microprofile.grpc - helidon-microprofile-grpc-server - ----- - - -== 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. -* `@GrpcMarshaller` - an annotation used to annotate a type or method to specify the named marshaller supplier to use for rpc method calls. - -gRPC method types: - -* <> - 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. -Using Helidon gRPC MP, all you need to do is write an annotated service implementation class that is just a normal POJO. - -For example: - -[source,java] -.Simple gRPC Service ----- -@ApplicationScoped -@io.helidon.microprofile.grpc.core.Grpc -public class StringService { - - @io.helidon.microprofile.grpc.core.Unary - public String upper(String s) { - return s == null ? null : s.toUpperCase(); - } -} ----- - -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, -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 as 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 -annotations. - -[source,java] ----- -@ApplicationScoped // <1> -@io.helidon.microprofile.grpc.core.Grpc // <2> -public class StringService { - /* code is omitted */ -} ----- - -<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 -then deployed by the gRPC MP server. - -=== Service Name -By default when a class is annotated with `Grpc`, the class name will be used as the gRPC service name. So in the example -above, the service name will be `StringService`. This can be changed by supplying a name to the annotation. - -[source,java] ----- -@ApplicationScoped -@io.helidon.microprofile.grpc.core.Grpc(name="Strings") // <1> -public class StringService { ----- -<1> 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 -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. - -The Helidon gRPC MP API determines a method type by its annotation, which should be one of the following: -[source,java] ----- -@io.helidon.microprofile.grpc.core.Unary -@io.helidon.microprofile.grpc.core.ServerStreaming -@io.helidon.microprofile.grpc.core.ClientStreaming -@io.helidon.microprofile.grpc.core.Bidirectional ----- - -==== 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 -Protobuf serializable classes but this is not the case with Helidon gRPC. Helidon supports -<> and by default will support 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 -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 -return no response. - -All of the signatures below are valid unary methods in Helidon gRPC MP. -[source,java] ----- -// A unary method with a simple request and response -@io.helidon.microprofile.grpc.core.Unary -public ResponseType invoke(RequestType req) - -// A unary method that just returns a response -@io.helidon.microprofile.grpc.core.Unary -public ResponseType invoke() - -// A unary method that takes a request but returns no response -@io.helidon.microprofile.grpc.core.Unary -public void invoke(RequestType req) - -// A unary method that takes no request and returns no response -@io.helidon.microprofile.grpc.core.Unary -public void invoke() - -// An async unary request that takes a request and returns a future -// that will complete when the response is ready -@io.helidon.microprofile.grpc.core.Unary -public CompletableFuture invoke(RequestType req) - -// An async unary request that takes no request and returns a future -// that will complete when the response is ready -@io.helidon.microprofile.grpc.core.Unary -public CompletableFuture invoke() - -// The standard gRPC Java unary method signature -@io.helidon.microprofile.grpc.core.Unary -public void invoke(RequestType req, StreamObserver observer) - -// The standard gRPC Java unary method signature but without a request type -@io.helidon.microprofile.grpc.core.Unary -public void invoke(StreamObserver observer) - -// A unary method that takes a request type and a future to complete -// with the response type -@io.helidon.microprofile.grpc.core.Unary -public void invoke(RequestType req, CompletableFuture observer) - -// A unary method that takes no request type but just takes a future -// to complete with the response type -@io.helidon.microprofile.grpc.core.Unary -public void invoke(CompletableFuture observer) ----- - -The various signatures supported above allow the service developer to choose the method signature that best fits their -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 -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, -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. -[source,java] ----- -// The standard gRPC Java server streaming method signature -@io.helidon.microprofile.grpc.core.ServerStreaming -public void invoke(RequestType req, StreamObserver observer) - -// A server streaming method that uses a Stream to send the responses to the client -@io.helidon.microprofile.grpc.core.ServerStreaming -public Stream invoke(RequestType req) - -// The server streaming method without a request parameter -@io.helidon.microprofile.grpc.core.ServerStreaming -public void invoke(StreamObserver observer) - -// A server streaming method without a request parameter -// that uses a Stream to send the responses to the client -@io.helidon.microprofile.grpc.core.ServerStreaming -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 -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. - -All of the signatures below are valid client streaming methods in Helidon gRPC MP. -[source,java] ----- -// The standard gRPC Java client streaming method signature -@io.helidon.microprofile.grpc.core.ClientStreaming -public StreamObserver invoke(StreamObserver observer) - -// The gRPC Java client streaming method with an asynchronous response -@io.helidon.microprofile.grpc.core.ClientStreaming -public StreamObserver invoke(CompletableFuture observer) - ----- - - -==== 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 -different method signatures for a bidirectional method so the only supported signature is the standard gRPC Java method. - -[source,java] ----- -@io.helidon.microprofile.grpc.core.Bidirectional -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 -requirement to deploy services built the traditional way using gRPC Java generated classes or built as -xref:{rootdir}/se/grpc/server.adoc#_service_implementation[non-microprofile Helidon gRPC 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 -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. - -[source,java] ----- -@ApplicationScoped -public class StringService - extends StringServiceGrpc.StringServiceImplBase { ----- - -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] ----- -@ApplicationScoped -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 non-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: -[source,java] ----- -public class MyExtension - implements GrpcMpExtension { - @Override - public void configure(GrpcMpContext context) { <1> - context.routing() - .register(new ServiceService()); <2> - } -} ----- - -<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:{rootdir}/se/grpc/server.adoc#_grpc_server_routing[gRPC server routing] documentation). - -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. - -include::{rootdir}/includes/grpc-marshalling.adoc[leveloffset=2] - -== Configuration -Configure the gRPC server using the Helidon microprofile configuration framework by which the ConfigSource defaults to -`microprofile-config.properties`. Alternatively, you can also use other ConfigSources such as `application.yaml`. -Refer to xref:{rootdir}/mp/config/introduction.adoc[MicroProfile Config] for more details about the different options for -ConfigSources. - -// grpc now shares the same server instance -// include::{rootdir}/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc[leveloffset=1, tag=config] - -.GrpcServer configuration file example using `application.yaml` -[source,yaml] ----- -grpc: - name: test.server # <1> - port: 3333 # <2> ----- -<1> Specifies the name of the gRPC server. -<2> Sets the server port. - -== Examples -Helidon MP includes some examples that demonstrate the gRPC server usage: - -* link:{helidon-github-tree-url}/examples/grpc/microprofile/basic-server-implicit[Basic gRPC Server example] provides -a simple gRPC application that deploys a gRPC service that will be discovered by CDI. Two additional services are -included that are not normally CDI managed beans, but are manually added as CDI managed beans so that they can also be -discovered by Helidon MP. -* link:{helidon-github-tree-url}/examples/grpc/microprofile/metrics[gRPC Server Metrics example] demonstrates a -Helidon MP application that enables `metrics` and `tracing` on a gRPC Service. +If you require gRPC in Helidon MP, kindly stay with Helidon MP 3.x, until the issue is resolved. diff --git a/docs/mp/guides/tracing.adoc b/docs/mp/guides/tracing.adoc index a430cf55e2e..6bcf8aee93a 100644 --- a/docs/mp/guides/tracing.adoc +++ b/docs/mp/guides/tracing.adoc @@ -38,7 +38,7 @@ include::{rootdir}/includes/prerequisites.adoc[tag=prerequisites] Distributed tracing is a critical feature of micro-service based applications, since it traces workflow both within a service and across multiple services. This provides insight to sequence and timing data for specific blocks of work, which helps you identify performance and operational issues. Helidon MP includes support for distributed tracing -through the https://opentracing.io[OpenTracing API]. Tracing is integrated with WebServer, gRPC Server, +through the https://opentracing.io[OpenTracing API]. Tracing is integrated with WebServer, and Security using either the https://zipkin.io[Zipkin] or https://www.jaegertracing.io[Jaeger] tracers. === Tracing Concepts diff --git a/docs/mp/tracing.adoc b/docs/mp/tracing.adoc index 7a140e8a81a..30178dd9773 100644 --- a/docs/mp/tracing.adoc +++ b/docs/mp/tracing.adoc @@ -45,7 +45,7 @@ The MP OpenTracing specification is no longer required by MicroProfile. This fea Distributed tracing is a critical feature of micro-service based applications, since it traces workflow both within a service and across multiple services. This provides insight to sequence and timing data for specific blocks of work, which helps you identify performance and operational issues. Helidon MP includes support for distributed tracing -through the https://opentracing.io[OpenTracing API]. Tracing is integrated with WebServer, gRPC Server, +through the https://opentracing.io[OpenTracing API]. Tracing is integrated with WebServer, and Security. include::{rootdir}/includes/dependencies.adoc[] diff --git a/docs/se/aot.adoc b/docs/se/aot.adoc index fc69829d46a..c4341c2294a 100644 --- a/docs/se/aot.adoc +++ b/docs/se/aot.adoc @@ -111,9 +111,9 @@ for native image. |✅ |{nbsp} |Multi-part |{nbsp} |❓ |{nbsp} |Prometheus |Not yet tested. |✅ |{nbsp} |Websocket |Server only. -|✅ |gRPC Server |gRPC Server |Since GraalVM 21.0.0 +|✅ |gRPC Server |gRPC Server |{nbsp} |✅ |{nbsp} |Metrics |{nbsp} -|✅ |gRPC Client |gRPC Client |Since GraalVM 21.0.0 +|✅ |gRPC Client |gRPC Client |{nbsp} |✅ |{nbsp} |Metrics |{nbsp} |✅ |Scheduling |Scheduling |{nbsp} |✅ |OCI |OCI Integration |Modules with group id `io.helidon.integrations.oci` diff --git a/docs/se/grpc/client.adoc b/docs/se/grpc/client.adoc index fce4d216c01..b60237daba4 100644 --- a/docs/se/grpc/client.adoc +++ b/docs/se/grpc/client.adoc @@ -27,536 +27,10 @@ include::{rootdir}/includes/se.adoc[] == Contents - <> -- <> -- <> -** <> -** <> -** <> -** <> -** <> -** <> -** <> - - <> -** <> -** <> - - <> -** <> -** <> == 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 marshallers for requests and -responses if `protobuf` does not satisfy your needs. - -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 - ----- - -== 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 several 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 other 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 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, we create an instance of `GrpcServiceClient` passing the `ClientMethodDescriptor` and the `Channel` instances. - -=== 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 -from protobuf IDL file. - -For this section we will assume the following proto file: - -[source, proto] ----- -syntax = "proto3"; -option java_package = "io.helidon.grpc.client.test"; - -service StringService { - rpc Upper (StringMessage) returns (StringMessage) {} // (Unary) - rpc Lower (StringMessage) returns (StringMessage) {} // (Unary) - rpc Split (StringMessage) returns (stream StringMessage) {} // (Server Streaming) - rpc Join (stream StringMessage) returns (StringMessage) {} // (Client Streaming) - rpc Echo (stream StringMessage) returns (stream StringMessage) {} // (Bidirectional Streaming) -} - -message StringMessage { - string text = 1; -} ----- - -If you run it through `protoc`, it will generate a class (among other things) called `StringService`. -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) - -Let's build a class called `ProtoBasedStringServiceClient` that invokes the various types of -gRPC methods that our `StringService` offers. - - -[source,java] ----- -public class ProtoBasedStringServiceClient { - - private GrpcServiceClient client; - - public ProtoBasedStringServiceClient() { - ClientServiceDescriptor desc = ClientServiceDescriptor - .builder(StringService.getServiceDescriptor()) // <1> - .build(); - - Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408) // <2> - .usePlaintext().build(); - - 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 implements StreamObserver { // <4> - @Override - public void onNext(T value) { - System.out.println("Received : " + value); - } - - @Override - public void onError(Throwable t) { - t.printStracktrace(); - } - - @Override - public void onCompleted() { - System.out.println("DONE"); - } - } -} ----- - -<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 -of this class can be used wherever a `io.grpc.StreamObserver` is required (like server streaming, -bi-directional streaming methods). - -==== Invoking a Unary Method on the StringService - -The Client Framework provides many helper methods to invoke gRPC unary methods. - -[source,java] ----- -public class ProtoBasedStringServiceClient { - - private GrpcServiceClient client; - - public ProtoBasedStringServiceClient() { /* code omitted */ } - - public void invokeUnaryMethod() throws Exception { - StringMessage input = StringMessage.newBuilder().setText("ABC").build(); - - CompletableFuture result = client.unary("Lower", input); // <1> - - String lcase = client.blockingUnary("Lower", input); // <2> - - StringMessageStream stream = new StringMessageStream(); - client.blockingUnary("Lower", stream); // <3> - } - - public static class StringMessageStream { /* code omitted */ } -} ----- - -<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 its response thus allowing the client to obtain the result asynchronously. -<2> This is simply a wrapper around the above method. This method blocks until the result is available. -<3> Here, we 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 - -Let's invoke the `Join` method which causes the server to return a single result *after* the client -has streamed the request values to the server. The gRPC API expects the client application to provide -an instance of `io.grpc.StreamObserver` as an argument during the invocation of the client -streaming method. - -In order to simplify the task of invoking Client Streaming methods, the Helidon Client Framework provides -two methods to invoke gRPC client Streaming methods. The first variant takes an `Iterable` as -argument which in turn is converted into a `io.grpc.StreamObserver`. The second variant takes a -`io.grpc.StreamObserver` as argument. The first variant can be used if the number of values to be -streamed in small and known a priori. - -[source,java] ----- -public class ProtoBasedStringServiceClient { - - private GrpcServiceClient client; - - public ProtoBasedStringServiceClient() { /* code omitted */ } - - public void invokeClientStreamingWithIterable() throws Exception { - - String sentence = "A simple invocation of a client streaming method"; - Collection input = Arrays.stream(sentence.split(" ")) // <1> - .map(w -> StringMessage.newBuilder().setText(w).build()) - .collect(Collectors.toList()); - - CompletableFuture result = - 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> - - for (String word : sentence.split(" ")) { - clientStream.onNext(StringMessage.newBuilder().setText(word).build()); // <4> - } - clientStream.onCompleted(); // <5> - } - - public static class StringMessageStream { /* code is omitted */ } - -} ----- - -<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 -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 -stream. -<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) - -Let's invoke the "Split" method which causes the server to stream the results back. - -[source,java] ----- -public class ProtoBasedStringServiceClient { - - private GrpcServiceClient client; - - public ProtoBasedStringServiceClient() { /* code omitted */ } - - 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> - - StringMessageStream observer = new StringMessageStream<>(); // <2> - grpcClient.serverStreaming("Split", input, observer); // <3> - } - - public static class StringMessageStream { /* code is omitted */ } - -} ----- - -<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. - -=== Invoking a Bi-Directional Streaming Method on the StringService (Generated from protoc) - -Now let's invoke the `Echo` method in which both the client and the server have to stream -the request and response. - -[source,java] ----- -public class ProtoBasedStringServiceClient { - - private GrpcServiceClient client; - - public ProtoBasedStringServiceClient() { /* code omitted */ } - - public void invokeBidiStreaming() throws Exception { - - StringMessageStream observer = new StringMessageStream<>(); // <1> - StringMessageStream clientStream = grpcClient - .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.onCompleted(); // <4> - } - - public static class StringMessageStream { /* code is omitted */ } - -} ----- - -<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 -`clientStream`. -<4> We call the `onCompleted()` method on the `clientStream` to signal that the client has -streamed all its values. - -=== Programmatically Creating ClientServiceDescriptor for StringService - -Assuming that the service is still running on port 1408, let's see how to create our Client -without using the ``StringService``'s proto `ServiceDescriptor`. - -Since we are *not* going to use the ``StringService``'s proto `ServiceDescriptor`, we need to -describe the methods that the client needs to invoke. The Helidon client framework provides -several methods to easily describe gRPC methods. - -For example, to register a unary method, we need to use the `unary` method and configure it to -specify the request and response types. - -Other than describing the methods that our client will invoke, the rest of the code should be -very similar to (or the same as) the previous section!! - -[source,java] ----- -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> - - ClientMethodDescriptor join = ClientMethodDescriptor - .clientStreaming("StringService", "Join") // <5> - .requestType(StringMessage.class) - .responseType(StringMessage.class) - .build(); - - ClientMethodDescriptor split = ClientMethodDescriptor - .serverStreaming("StringService", "Split") // <6> - .requestType(StringMessage.class) - .responseType(StringMessage.class) - .build(); - - ClientMethodDescriptor echo = ClientMethodDescriptor - .bidirectional("StringService", "Echo") // <7> - .requestType(StringMessage.class) - .responseType(StringMessage.class) - .build(); - - ClientServiceDescriptor serviceDesc = ClientServiceDescriptor // <8> - .builder(StringService.class) - .unary(lower) - .clientStreaming(join) - .serverStreaming(split) - .bidirectional(echo) - .build(); - - - Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408) // <9> - .usePlaintext().build(); - - GrpcServiceClient client = GrpcServiceClient.create(channel, serviceDesc); // (<10> - - } - -} ----- - -<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 -the correct marshaller for the request & response types. -<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. -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. -The service name and the method name ("Echo") are specified. -<8> Create a `ClientServiceDescriptor` for a service named `StringService` and add all the defined ``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. - - -=== 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. Just *do not* set the request and response types -in the `ClientMethodDescriptor` anymore. Furthermore, there is an API in the `ClientServiceDescriptor` -that makes this even simpler where you can simply pass the method name. For example, to create a client streaming -method called `"JoinString"` that uses some custom marshalling, simply call the `clientStreaming("JoinString")`. - -[source,java] ----- -public static void main(String[] args) throws Exception { - ClientServiceDescriptor descriptor = ClientServiceDescriptor.builder(HelloService.class) // <1> - .marshallerSupplier(new JsonbMarshaller.Supplier()) // <2> - .clientStreaming("JoinString") // <3> - .build(); - - Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408) - .usePlaintext() - .build(); - - GrpcServiceClient client = GrpcServiceClient.create(channel, descriptor); - - String sentence = "A simple invocation of a client streaming method"; - Collection input = Arrays.stream(sentence.split(" ")) - .map(w -> StringMessage.newBuilder().setText(w).build()) - .collect(Collectors.toList()); - - CompletableFuture result = grpcClient.clientStreaming("Join", input); -} ----- - -<1> Create a `ClientServiceDescriptor` for the `HelloService`. -<2> Specify a custom marshaller using the built-in JSON-B marshaller to serialize/deserialize requests and responses. -<3> 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), the custom marshaller 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::{rootdir}/includes/grpc-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 JsonbMarshaller.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 a custom marshaller using the built-in JSON-B marshaller to serialize/deserialize request and response values. -<3> Add the `SayHello` unary method to the `ClientServiceDescriptor` which will use the specified custom marshaller. -<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. - -=== Additional gRPC Client Examples -A set of gRPC client examples for Helidon SE can be found in the following links: - -* link:{helidon-github-tree-url}/examples/grpc/client-standalone[Basic gRPC Standalone Client] -* link:{helidon-github-tree-url}/examples/grpc/metrics[gRPC Server Metrics] -* link:{helidon-github-tree-url}/examples/grpc/opentracing[OpenTracing on a gRPC Server] -* link:{helidon-github-tree-url}/examples/grpc/security[Basic Auth Security on a gRPC Server] -* link:{helidon-github-tree-url}/examples/grpc/security-abac[Attribute-Based Access Control (ABAC) security on a gRPC Server] -* link:{helidon-github-tree-url}/examples/grpc/security-outbound[Outbound Security on a gRPC Server] +gRPC client is temporarily removed from Helidon, please follow issue +https://github.com/helidon-io/helidon/issues/5418 +If you require gRPC client, either stay with a previous version of Helidon, or allow us to finish fixing the issue above. diff --git a/docs/se/grpc/server.adoc b/docs/se/grpc/server.adoc index cefd36e573d..1524053802c 100644 --- a/docs/se/grpc/server.adoc +++ b/docs/se/grpc/server.adoc @@ -31,19 +31,17 @@ include::{rootdir}/includes/se.adoc[] - <> ** <> ** <> -** <> -** <> -** <> -** <> -** <> - <> ** <> -** <> - <> -** <> -** <> == Overview + +gRPC scope is temporarily smaller in Helidon, please follow issue +https://github.com/helidon-io/helidon/issues/5418 +As this is still work in progress, the WebServer gRPC module is release in preview mode, as we may introduce backward +incompatible changes to our APIs, so we can re-introduce features. + The 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 @@ -57,14 +55,7 @@ model, simplifying the 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 marshallers for requests and -responses if Protobuf does not satisfy your needs. - -* It provides built-in support for <>. +* It allows you to run gRPC and HTTP endpoints on the same WebServer, and even on the same port. include::{rootdir}/includes/dependencies.adoc[] @@ -72,45 +63,38 @@ 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 the <> section, add the following dependency to your project’s pom.xml: -[source,xml] ----- - - io.helidon.security.integration - helidon-security-integration-grpc + io.helidon.webserver + helidon-webserver-grpc ---- == Usage === gRPC Server Routing - <> -- <> -Unlike the webserver, which allows you to route requests based on path expression +Unlike the HTTP server, which allows you to route requests based on path expression and the HTTP verb, the 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) { +private static GrpcRouting.Builder createRouting(Config config) { return GrpcRouting.builder() - .register(new GreetService(config)) // <1> - .register(new EchoService()) // <2> - .register(new MathService()) // <3> - .build(); + .service(new GreetService(config)) // <1> + .service(new EchoService()) // <2> + .service(new MathService()) // <3> + .unary(Strings.getDescriptor(), // <4> + "StringService", + "Upper", + Main::grpcUpper); } ---- <1> Register `GreetService` instance. <2> Register `EchoService` instance. <3> Register `MathService` instance. +<4> Register a custom unary gRPC route Both "standard" gRPC services that implement `io.grpc.BindableService` interface (typically implemented by extending the generated server-side stub and overriding @@ -123,96 +107,10 @@ 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 a 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). +descriptor by providing an instance of `ServerServiceDefinition` to `service` method. === 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 the `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.marshallerSupplier(new JsonbMarshaller.Supplier()) // <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(String request, StreamObserver observer) { // <3> - complete(observer, request); // <4> - } -} ----- - -<1> Specify a custom marshaller to marshall requests and responses. -<2> Define unary method `Echo` and map it to the `this::echo` handler. -<3> Create a handler for the `Echo` method. -<4> 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 explicitly defines -a marshaller for requests and responses, so 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 @@ -256,11 +154,14 @@ The service implementation will be very similar to our original implementation: [source,java] ---- class EchoService implements GrpcService { + @Override + public Descriptors.FileDescriptor proto() { + return Echo.getDescriptor(); // <1> + } @Override public void update(ServiceDescriptor.Rules rules) { - rules.proto(Echo.getDescriptor()) // <1> - .unary("Echo", this::echo); // <2> + rules.unary("Echo", this::echo); // <2> } /** @@ -285,583 +186,9 @@ enable Protobuf marshalling. <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 the interceptor class using `io.grpc.ServerInterceptor`. -<2> Implement the logging logic. -<3> The intercepted call is started. - -==== 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 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 the service status. -<3> Use service name as a health check name for consistency. -<4> Set the determined service status. -<5> Optionally provide additional metadata. - -You can also define custom health checks for an existing service, including plain -`io.grpc.BindableService` implementations, using a service configurer inside the -`GrpcRouting` definition: - -[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 a custom implementation of the -standard gRPC `HealthService` API. - -However, they can also be exposed to REST clients via the 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 the `GrpcServer` instance. -<2> Start the gRPC server which will deploy all the 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 the web server. - -All gRPC health checks will now be available via the `/health` REST endpoint, in -addition to the standard gRPC `HealthService` - -=== Service Metrics -- <> -- <> -- <> -- <> -- <> - -The 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, the gRPC server only captures two vendor-level metrics: `grpc.request.count` -and `grpc.request.meter`. These metrics provide an 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 the 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 excessive, so the user -could decide to use a lighter-weight metric type, such as a `counter` or a `meter`. - -However, the user may still want to enable a `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 the 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 the vendor and -application registries, and can be exposed via the standard `/metrics` REST API. - -[source,java] ----- -Routing routing = Routing.builder() - .register(MetricsSupport.create()) // <1> - .build(); - -WebServer.create(webServerConfig(), routing) // <2> - .start() ----- -<1> Add the `MetricsSupport` instance to web server routing. -<2> Create and start the Helidon web server. - -See xref:{rootdir}/se/metrics/metrics.adoc[Helidon Metrics] documentation for more details. - -==== Specifying Metric Metadata - -Helidon metrics contain metadata such as tags, a description, units etc. It is possible to -add this additional metadata when specifying the metrics. - -===== Adding Tags - -To add tags to a metric, a `Map` of key/value tags can be supplied. - -[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. - -[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 a metric. - -[source,java] ----- -GrpcRouting routing = GrpcRouting.builder() - .intercept(GrpcMetrics.timed().units(MetricUnits.SECONDS)) // <1> - .register(new MyService()) - .build(); ----- -<1> The `units()` method is used to specify the metric units, -the value of which is one of the constants from the `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 a lambda expression can be used too. - -[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" and method "bar", the above example would produce a name "grpc.Foo.bar". - -=== Security -To enable server security, refer to the 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 the gRPC server: - -1. Create the security instance and register it the with server. -2. Protect the gRPC services of the 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(EndpointConfig.PROPERTY_OUTBOUND_ID, user) - .property(EndpointConfig.PROPERTY_OUTBOUND_SECRET, 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 which, in this case, will use 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(EndpointConfig.PROPERTY_OUTBOUND_ID, user) - .property(EndpointConfig.PROPERTY_OUTBOUND_SECRET, 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 for calling 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::{rootdir}/includes/grpc-marshalling.adoc[leveloffset=2] +NOTE: The `complete` method shown in the example above is just one of many helper +methods available in the `ResponseHelper` class. See the full list +link:{grpc-server-javadoc-base-url}/io/helidon/webserver/grpc/ResponseHelper.html[here]. == Configuration Configure the gRPC server using the Helidon configuration framework, either programmatically @@ -869,89 +196,30 @@ or via a configuration file. === Configuring the gRPC Server in the Code -The easiest way to configure the gRPC server is in the 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. +Currently we do not have any custom configuration options for gRPC protocol. -// grpc now shares the same server instance -// include::{rootdir}/config/io_helidon_grpc_server_GrpcServerConfiguration.adoc[leveloffset=1, tag=config] - -.GrpcServer configuration file example using `application.yaml` -[source,yaml] ----- -grpc: - port: 3333 ----- - -Then, in your application code, load the configuration from that file. +To register a routing with Helidon WebServer, simply add the routing to the listener +(WebServer configuration is itself the default listener configuration) [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); +WebServer.builder() + .port(8080) + .routing(httpRouting -> httpRouting.get("/greet", (req, res) -> res.send("Hi!"))) // <1> + .addRouting(GrpcRouting.builder() // <2> + .unary(String.getDescriptor(), + "StringService", + "Upper", + Main::grpcUpper)) + .build() + .start(); ---- -== Examples -=== Quick Start - -Here is the code for a minimalist gRPC application that runs on a default port (1408): +<1> Configure HTTP routing of the server +<2> Configure gRPC routing of the server -[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.marshallerSupplier(new JsonbMarshaller.Supplier()) // <6> - .unary("SayHello", ((request, responseObserver) -> complete(responseObserver, "Hello " + request))); // <7> - } -} ----- - -<1> Register the 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> Specify a custom marshaller using the built-in JsonB marshaller to marshall requests and responses. -<7> Add unary method `HelloService/SayHello` to the service definition. - -=== Additional gRPC Server Examples -A set of gRPC server examples for Helidon SE can be found in the following links: +== Examples -* link:{helidon-github-tree-url}/examples/grpc/basics[Basic gRPC Server] -* link:{helidon-github-tree-url}/examples/grpc/metrics[gRPC Server Metrics] -* link:{helidon-github-tree-url}/examples/grpc/opentracing[OpenTracing on a gRPC Server] -* link:{helidon-github-tree-url}/examples/grpc/security[Basic Auth Security on a gRPC Server] -* link:{helidon-github-tree-url}/examples/grpc/security-abac[Attribute-Based Access Control (ABAC) security on a gRPC Server] -* link:{helidon-github-tree-url}/examples/grpc/security-outbound[Outbound Security on a gRPC Server] +The following gRPC examples for Helidon SE are available: +* link:{helidon-github-tree-url}/examples/webserver/protocols[Multiple protocols on a single WebServer] diff --git a/docs/se/guides/tracing.adoc b/docs/se/guides/tracing.adoc index f9112545baa..2a8cf03852d 100644 --- a/docs/se/guides/tracing.adoc +++ b/docs/se/guides/tracing.adoc @@ -39,7 +39,7 @@ include::{rootdir}/includes/prerequisites.adoc[tag=prerequisites] Distributed tracing is a critical feature of micro-service based applications, since it traces workflow both within a service and across multiple services. This provides insight to sequence and timing data for specific blocks of work, which helps you identify performance and operational issues. Helidon SE includes support for distributed tracing -through the https://opentracing.io[OpenTracing API]. Tracing is integrated with WebServer, gRPC Server, +through the https://opentracing.io[OpenTracing API]. Tracing is integrated with WebServer, and Security using either the https://zipkin.io[Zipkin] or https://www.jaegertracing.io[Jaeger] tracers. === Tracing Concepts diff --git a/docs/se/guides/upgrade_3x.adoc b/docs/se/guides/upgrade_3x.adoc index d7a73125ebf..563ca9ba927 100644 --- a/docs/se/guides/upgrade_3x.adoc +++ b/docs/se/guides/upgrade_3x.adoc @@ -203,6 +203,8 @@ Deprecations: More information in the following link:https://github.com/oracle/helidon/issues/4367[Task]. +gRPC scope is temporarily smaller in Helidon, please follow issue +https://github.com/helidon-io/helidon/issues/5418 === LRA diff --git a/docs/se/tracing.adoc b/docs/se/tracing.adoc index 0a8d31ab90a..820fa03e1b9 100644 --- a/docs/se/tracing.adoc +++ b/docs/se/tracing.adoc @@ -43,8 +43,7 @@ within a service and across multiple services. This provides insight to sequence which helps you identify performance and operational issues. Helidon includes support for distributed tracing through its own API, backed by either through the https://opentelemetry.io/docs/instrumentation/js/api/tracing/[OpenTelemetry API], or by -https://opentracing.io[OpenTracing API]. Tracing is integrated with WebServer, gRPC Server, -and Security. +https://opentracing.io[OpenTracing API]. Tracing is integrated with WebServer, and Security. include::{rootdir}/includes/dependencies.adoc[]