Initial gRPC server/framework implementation (#543)

* Initial gRPC server/framework implementation

* Fixes for corrupted source files

* Checkstyle fix

* POM changes based on PR comments by @romain-grecourt

* Added missing gRPC documentation sections

* Change gRPC module to versions 1.0.4-SNAPSHOT

* Change netty tcnative to 2.0.22.Final

* Changes from Helidon review meeting (#84)

* Initial gRPC server/framework implementation

* Remove @author JavaDoc tags

* Rename gRPC server `ServiceDescriptor.Config` interface to `ServiceDescriptor.Rules` and `MethodDescriptor.Config` to `MethodDescriptor.Rules` based on pull-request feedback. Also changed corresponding variable names, parameter names and documentation usage.

* Split gRPC metrics out of the gRPC server module into its own module.

* Change configuration examples in gRPC documentation to yaml

* Fix gRPC security documentation error

* gRPC metrics clean-up based on review comments

* gRPC metrics docs change

* Change gRPC security example configuration to use yaml

* Remove the custom `BasicAuthCallCredentials` class from the security examples #90

* Change gRPC interceptor priority to be an `int` #91

* Fixing checkstyle errors

* Fix incorrect comment in gRPC examples basic `Server.java`
Change gRPC Open Tracing example to build the `Tracer` from config

* Checkstyle fix

* Support the Helidon Prioritized interface as a way to specify gRPC interceptor priority.

* Resolve merge conflict

* Update the gRPC introduction doc to make it clear that Helidon gRPC is currently an experimental feature
This commit is contained in:
Aleks Seovic
2019-05-05 16:12:32 -04:00
committed by Tomas Langer
parent 55f3ea5f7d
commit baefb32d24
164 changed files with 19903 additions and 18 deletions

View File

@@ -52,6 +52,17 @@
<artifactId>helidon-webserver-test-support</artifactId>
<version>${project.version}</version>
</dependency>
<!-- gRPC server -->
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-server</artifactId>
<version>${project.version}</version>
</dependency>
<!-- media -->
<dependency>
<groupId>io.helidon.media</groupId>
@@ -170,6 +181,11 @@
<artifactId>helidon-security-providers-http-sign</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.security.integration</groupId>
<artifactId>helidon-security-integration-grpc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.security.integration</groupId>
<artifactId>helidon-security-integration-jersey</artifactId>
@@ -186,10 +202,10 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.security.providers</groupId>
<artifactId>helidon-security-providers-abac</artifactId>
<version>${project.version}</version>
</dependency>
<groupId>io.helidon.security.providers</groupId>
<artifactId>helidon-security-providers-abac</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.security</groupId>
<artifactId>helidon-security-abac-time</artifactId>

View File

@@ -133,15 +133,6 @@
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<configuration>
<protocArtifact>
com.google.protobuf:protoc:${version.lib.protobuf.java}:exe:${os.detected.classifier}
</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>
io.grpc:protoc-gen-grpc-java:${version.lib.grpc}:exe:${os.detected.classifier}
</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>

View File

@@ -0,0 +1,80 @@
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
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.
///////////////////////////////////////////////////////////////////////////////
:pagename: grpc-server-introduction
:description: Helidon gRPC Server Introduction
:keywords: helidon, grpc, java
= gRPC Server Introduction
Helidon gRPC Server provides a framework for creating link:http://grpc.io/[gRPC] applications.
=== _Experimental Feature_
The Helidon gRPC feature is currently experimental and the APIs are subject to changes until gRPC support is stabilized.
== 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); // <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 World!"))); // <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.
== Maven Coordinates
The <<getting-started/03_managing-dependencies.adoc, Getting Started>> page describes how you
should declare dependency management for Helidon applications. Then declare the following dependency in your project:
[source,xml,subs="verbatim,attributes"]
----
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-server</artifactId> <!--1-->
</dependency>
----
<1> Dependency on gRPC Server.

View File

@@ -0,0 +1,69 @@
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
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.
///////////////////////////////////////////////////////////////////////////////
:javadoc-base-url-api: {javadoc-base-url}?io/helidon/grpc/server
:pagename: grpc-server-configuration
:description: Helidon gRPC Server Configuration
:keywords: helidon, grpc, java, configuration
= gRPC Server 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);
----
== 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-api}/GrpcServerConfiguration.html[here].

View File

@@ -0,0 +1,102 @@
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
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.
///////////////////////////////////////////////////////////////////////////////
:pagename: grpc-server-routing
:description: Helidon gRPC Server Routing
:keywords: helidon, grpc, java
= 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
<<06_health_checks.adoc, health checks>> and <<07_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 <<05_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).

View File

@@ -0,0 +1,163 @@
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
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.
///////////////////////////////////////////////////////////////////////////////
:javadoc-base-url-api: {javadoc-base-url}?io/helidon/grpc/server
:pagename: grpc-server-service-implementation
:description: Helidon gRPC Service Implementation
:keywords: helidon, grpc, java
= 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 <<08_security.adoc, security>> and <<07_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 <<06_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<String> 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:{javadoc-base-url-api}/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<Echo.EchoResponse> 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.

View File

@@ -0,0 +1,123 @@
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
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.
///////////////////////////////////////////////////////////////////////////////
:pagename: grpc-server-interceptors
:description: Helidon gRPC Service Interceptors
:keywords: helidon, grpc, java
= 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 <ReqT, ResT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, ResT> call,
Metadata metadata,
ServerCallHandler<ReqT, ResT> 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 <ReqT, ResT> void myMethod(ReqT request, StreamObserver<ResT> 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 <ReqT, ResT> void myMethod(ReqT request, StreamObserver<ResT> observer) {
// do something
}
}
----
<1> Adds `LoggingInterceptor` to `MyService::MyMethod` only

View File

@@ -0,0 +1,116 @@
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
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.
///////////////////////////////////////////////////////////////////////////////
:pagename: grpc-server-health-checks
:description: Helidon gRPC Service Health Checks
:keywords: helidon, grpc, java
= 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 <ReqT, ResT> void myMethod(ReqT request, StreamObserver<ResT> 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`

View File

@@ -0,0 +1,199 @@
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
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.
///////////////////////////////////////////////////////////////////////////////
:pagename: grpc-server-metrics
:description: Helidon gRPC Service Metrics
:keywords: helidon, grpc, java
= 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 <ReqT, ResT> void myMethod(ReqT request, StreamObserver<ResT> 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 <<metrics/01_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<String, String> 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".

View File

@@ -0,0 +1,172 @@
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
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.
///////////////////////////////////////////////////////////////////////////////
:description: Helidon Security gRPC integration
:keywords: helidon, grpc, security
= gRPC Server Security
Security integration of the <<grpc/01_introduction.adoc,gRPC server>>
[source,xml]
.Maven Dependency
----
<dependency>
<groupId>io.helidon.security.integration</groupId>
<artifactId>helidon-security-integration-grpc</artifactId>
</dependency>
----
==== 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"]
----
==== 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(ClientSecurityFeature.PROPERTY_CONTEXT, securityContext)
.get();
----

View File

@@ -0,0 +1,36 @@
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
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.
///////////////////////////////////////////////////////////////////////////////
:pagename: grpc-server-metrics
:description: Helidon gRPC Marshalling
:keywords: helidon, grpc, java
= Marshalling
== Default Marshalling Support
=== Protobuf Marshalling
=== Java Serialization Marshalling
== Custom Marshalling
=== Marshaller
=== Marshaller Supplier

View File

@@ -70,6 +70,14 @@ backend:
items:
- includes:
- "webserver/*.adoc"
- title: "gRPC server"
pathprefix: "/grpc"
glyph:
type: "icon"
value: "swap_horiz"
items:
- includes:
- "grpc/*.adoc"
- title: "Config"
pathprefix: "/config"
glyph:

View File

@@ -22,7 +22,7 @@
== Tracing Support
Helidon includes support for tracing through the `https://opentracing.io/[OpenTracing]` APIs.
Tracing is integrated with WebServer and Security.
Tracing is integrated with WebServer, gRPC Server, and Security.
Support for specific tracers is abstracted. Your application can depend on
the abstraction layer and provide a specific tracer implementation as a Java
@@ -42,6 +42,8 @@ Declare the following dependency in your project to use the tracer abstraction:
</dependency>
----
=== Configuring Tracing with WebServer
To configure tracer with WebServer:
[source,java]
@@ -56,6 +58,40 @@ ServerConfiguration.builder()
<1> The name of the application (service) to associate with the tracing events
<2> The endpoint for tracing events, specific to the tracer used, usually loaded from Config
=== Configuring Tracing with gRPC Server
[source,java]
.Configuring OpenTracing `Tracer`
----
Tracer tracer = (Tracer) TracerBuilder.create("Server")
.collectorUri(URI.create("http://10.0.0.18:9411")) // <1>
.build();
----
<3> If using zipkin tracing system, the endpoint would be:
----
http://10.0.0.18:9411/api/v2/spans
----
.Configuring Tracing Attributes
----
TracingConfiguration tracingConfig = new TracingConfiguration.Builder()
.withStreaming()
.withVerbosity()
.withTracedAttributes(ServerRequestAttribute.CALL_ATTRIBUTES,
ServerRequestAttribute.HEADERS,
ServerRequestAttribute.METHOD_NAME)
.build();
----
.Configuring gRPC Server
----
GrpcServerConfiguration serverConfig = GrpcServerConfiguration.builder().port(0)
.tracer(tracer)
.tracingConfig(tracingConfig)
.build();
----
=== Configuration using Helidon Config [[Tracing-config]]
There is a set of common configuration options that this section describes. In addition each tracer implementation
may have additional configuration options - please see the documentation of each of them.

4
examples/grpc/README.md Normal file
View File

@@ -0,0 +1,4 @@
# Helidon SE gRPC Server Examples

View File

@@ -0,0 +1,16 @@
# Helidon gRPC Example
A basic example gRPC server.
## Build
```
mvn package
```
## Run
```
mvn exec:java
```

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-project</artifactId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<artifactId>helidon-examples-grpc-basics</artifactId>
<name>Helidon gRPC Server Examples Basics</name>
<description>
Examples of elementary use of the gRPC Server
</description>
<properties>
<mainClass>io.helidon.grpc.examples.basics.Server</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.health</groupId>
<artifactId>helidon-health-checks</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.bundles</groupId>
<artifactId>helidon-bundles-config</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.basics;
import io.grpc.Channel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.health.v1.HealthCheckRequest;
import io.grpc.health.v1.HealthGrpc;
/**
* A simple gRPC health check client.
*/
public class HealthClient {
private HealthClient() {
}
/**
* The program entry point.
*
* @param args the program arguments
*
* @throws Exception if an error occurs
*/
public static void main(String[] args) {
Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408).usePlaintext().build();
HealthGrpc.HealthBlockingStub health = HealthGrpc.newBlockingStub(channel);
System.out.println(health.check(HealthCheckRequest.newBuilder().setService("GreetService").build()));
System.out.println(health.check(HealthCheckRequest.newBuilder().setService("FooService").build()));
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.basics;
import java.util.logging.LogManager;
import io.helidon.config.Config;
import io.helidon.grpc.examples.common.GreetService;
import io.helidon.grpc.examples.common.GreetServiceJava;
import io.helidon.grpc.examples.common.StringService;
import io.helidon.grpc.server.GrpcRouting;
import io.helidon.grpc.server.GrpcServer;
import io.helidon.grpc.server.GrpcServerConfiguration;
import io.helidon.health.HealthSupport;
import io.helidon.health.checks.HealthChecks;
import io.helidon.webserver.Routing;
import io.helidon.webserver.ServerConfiguration;
import io.helidon.webserver.WebServer;
/**
* A basic example of a Helidon gRPC server.
*/
public class Server {
private Server() {
}
/**
* The main program entry point.
*
* @param args the program arguments
*
* @throws Exception if an error occurs
*/
public static void main(String[] args) throws Exception {
// By default this will pick up application.yaml from the classpath
Config config = Config.create();
// load logging configuration
LogManager.getLogManager().readConfiguration(
Server.class.getResourceAsStream("/logging.properties"));
// Get gRPC server config from the "grpc" section of application.yaml
GrpcServerConfiguration serverConfig =
GrpcServerConfiguration.builder(config.get("grpc")).build();
GrpcServer grpcServer = GrpcServer.create(serverConfig, createRouting(config));
// Try to start the server. If successful, print some info and arrange to
// print a message at shutdown. If unsuccessful, print the exception.
grpcServer.start()
.thenAccept(s -> {
System.out.println("gRPC server is UP! http://localhost:" + s.port());
s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
})
.exceptionally(t -> {
System.err.println("Startup failed: " + t.getMessage());
t.printStackTrace(System.err);
return null;
});
// add support for standard and gRPC health checks
HealthSupport health = HealthSupport.builder()
.add(HealthChecks.healthChecks())
.add(grpcServer.healthChecks())
.build();
// start web server with health endpoint
Routing routing = Routing.builder()
.register(health)
.build();
ServerConfiguration webServerConfig = ServerConfiguration.builder(config.get("webserver")).build();
WebServer.create(webServerConfig, routing)
.start()
.thenAccept(s -> {
System.out.println("HTTP server is UP! http://localhost:" + s.port());
s.whenShutdown().thenRun(() -> System.out.println("HTTP server is DOWN. Good bye!"));
})
.exceptionally(t -> {
System.err.println("Startup failed: " + t.getMessage());
t.printStackTrace(System.err);
return null;
});
}
private static GrpcRouting createRouting(Config config) {
GreetService greetService = new GreetService(config);
GreetServiceJava greetServiceJava = new GreetServiceJava(config);
return GrpcRouting.builder()
.register(greetService)
.register(greetServiceJava)
.register(new StringService())
.build();
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
/**
* A set of small usage examples. Start with {@link io.helidon.grpc.examples.basics.Server Main} class.
*/
package io.helidon.grpc.examples.basics;

View File

@@ -0,0 +1,26 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
app:
greeting: "Hello"
grpc:
name: "test.server"
port: 1408
webserver:
port: 8080
bind-address: "0.0.0.0"

View File

@@ -0,0 +1,37 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
# Example Logging Configuration File
# For more information see $JAVA_HOME/jre/lib/logging.properties
# Send messages to the console
handlers=java.util.logging.ConsoleHandler
# Global default logging level. Can be overriden by specific handlers and loggers
.level=INFO
# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
# It replaces "!thread!" with the current thread name
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
#Component specific log levels
#io.helidon.webserver.level=INFO
#io.helidon.config.level=INFO
#io.helidon.security.level=INFO
#io.helidon.common.level=INFO
#io.netty.level=INFO

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-project</artifactId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<artifactId>helidon-examples-grpc-common</artifactId>
<name>Helidon gRPC Server Examples ProtoBuf Services</name>
<description>
ProtoBuf generated gRPC services used in gRPC examples
</description>
<properties>
<mainClass>io.helidon.grpc.examples.common.GreetClient</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-services</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.common</groupId>
<artifactId>helidon-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${version.plugin.os}</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.common;
import java.net.URI;
import io.helidon.grpc.client.ClientRequestAttribute;
import io.helidon.grpc.client.ClientTracingInterceptor;
import io.helidon.grpc.examples.common.Greet.GreetRequest;
import io.helidon.grpc.examples.common.Greet.SetGreetingRequest;
import io.helidon.tracing.TracerBuilder;
import io.grpc.Channel;
import io.grpc.ClientInterceptors;
import io.grpc.ManagedChannelBuilder;
import io.opentracing.Tracer;
/**
* A client for the {@link GreetService}.
*/
public class GreetClient {
private GreetClient() {
}
/**
* The program entry point.
* @param args the program arguments
*
* @throws Exception if an error occurs
*/
public static void main(String[] args) throws Exception {
Tracer tracer = (Tracer) TracerBuilder.create("Client")
.collectorUri(URI.create("http://localhost:9411/api/v2/spans"))
.build();
ClientTracingInterceptor tracingInterceptor = ClientTracingInterceptor.builder(tracer)
.withVerbosity().withTracedAttributes(ClientRequestAttribute.ALL_CALL_OPTIONS).build();
Channel channel = ClientInterceptors
.intercept(ManagedChannelBuilder.forAddress("localhost", 1408).usePlaintext().build(), tracingInterceptor);
GreetServiceGrpc.GreetServiceBlockingStub greetSvc = GreetServiceGrpc.newBlockingStub(channel);
System.out.println(greetSvc.greet(GreetRequest.newBuilder().setName("Aleks").build()));
System.out.println(greetSvc.setGreeting(SetGreetingRequest.newBuilder().setGreeting("Ciao").build()));
System.out.println(greetSvc.greet(GreetRequest.newBuilder().setName("Aleks").build()));
Thread.sleep(5000);
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.common;
import java.util.Optional;
import io.helidon.config.Config;
import io.helidon.grpc.examples.common.Greet.GreetRequest;
import io.helidon.grpc.examples.common.Greet.GreetResponse;
import io.helidon.grpc.examples.common.Greet.SetGreetingRequest;
import io.helidon.grpc.examples.common.Greet.SetGreetingResponse;
import io.helidon.grpc.server.GrpcService;
import io.helidon.grpc.server.ServiceDescriptor;
import io.grpc.stub.StreamObserver;
import org.eclipse.microprofile.health.HealthCheckResponse;
/**
* An implementation of the GreetService.
*/
public class GreetService implements GrpcService {
/**
* The config value for the key {@code greeting}.
*/
private String greeting;
/**
* Create a {@link GreetService}.
*
* @param config the service configuration
*/
public GreetService(Config config) {
this.greeting = config.get("app.greeting").asString().orElse("Ciao");
}
@Override
public void update(ServiceDescriptor.Rules rules) {
rules.proto(Greet.getDescriptor())
.unary("Greet", this::greet)
.unary("SetGreeting", this::setGreeting)
.healthCheck(this::healthCheck);
}
// ---- service methods -------------------------------------------------
private void greet(GreetRequest request, StreamObserver<GreetResponse> observer) {
String name = Optional.ofNullable(request.getName()).orElse("World");
String msg = String.format("%s %s!", greeting, name);
complete(observer, GreetResponse.newBuilder().setMessage(msg).build());
}
private void setGreeting(SetGreetingRequest request, StreamObserver<SetGreetingResponse> observer) {
greeting = request.getGreeting();
complete(observer, SetGreetingResponse.newBuilder().setGreeting(greeting).build());
}
private HealthCheckResponse healthCheck() {
return HealthCheckResponse
.named(name())
.up()
.withData("time", System.currentTimeMillis())
.withData("greeting", greeting)
.build();
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.common;
import java.util.Optional;
import io.helidon.config.Config;
import io.helidon.grpc.server.GrpcService;
import io.helidon.grpc.server.ServiceDescriptor;
import io.grpc.stub.StreamObserver;
/**
* A plain Java implementation of the GreetService.
*/
public class GreetServiceJava
implements GrpcService {
/**
* The config value for the key {@code greeting}.
*/
private String greeting;
/**
* Create a {@link GreetServiceJava}.
*
* @param config the service configuration
*/
public GreetServiceJava(Config config) {
this.greeting = config.get("app.greeting").asString().orElse("Ciao");
}
@Override
public void update(ServiceDescriptor.Rules rules) {
rules.unary("Greet", this::greet)
.unary("SetGreeting", this::setGreeting);
}
// ---- service methods -------------------------------------------------
private void greet(String name, StreamObserver<String> observer) {
name = Optional.ofNullable(name).orElse("World");
String msg = String.format("%s %s!", greeting, name);
complete(observer, msg);
}
private void setGreeting(String greeting, StreamObserver<String> observer) {
this.greeting = greeting;
complete(observer, greeting);
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.common;
import io.helidon.grpc.examples.common.Strings.StringMessage;
import io.grpc.Channel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;
/**
* A client to the {@link io.helidon.grpc.examples.common.StringService}.
*/
public class StringClient {
private StringClient() {
}
/**
* Program entry point.
*
* @param args the program arguments
*
* @throws Exception if an error occurs
*/
public static void main(String[] args) throws Exception {
Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408).usePlaintext().build();
StringServiceGrpc.StringServiceStub stub = StringServiceGrpc.newStub(channel);
stub.lower(stringMessage("Convert To Lowercase"), new PrintObserver<>());
Thread.sleep(500L);
stub.upper(stringMessage("Convert to Uppercase"), new PrintObserver<>());
Thread.sleep(500L);
stub.split(stringMessage("Let's split some text"), new PrintObserver<>());
Thread.sleep(500L);
StreamObserver<StringMessage> sender = stub.join(new PrintObserver<>());
sender.onNext(stringMessage("Let's"));
sender.onNext(stringMessage("join"));
sender.onNext(stringMessage("some"));
sender.onNext(stringMessage("text"));
sender.onCompleted();
Thread.sleep(500L);
sender = stub.echo(new PrintObserver<>());
sender.onNext(stringMessage("Let's"));
sender.onNext(stringMessage("echo"));
sender.onNext(stringMessage("some"));
sender.onNext(stringMessage("text"));
sender.onCompleted();
Thread.sleep(500L);
}
private static StringMessage stringMessage(String text) {
return StringMessage.newBuilder().setText(text).build();
}
static class PrintObserver<T> implements StreamObserver<T> {
public void onNext(T value) {
System.out.println(value);
}
public void onError(Throwable t) {
t.printStackTrace();
}
public void onCompleted() {
System.out.println("<completed>");
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.common;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.helidon.grpc.examples.common.Strings.StringMessage;
import io.helidon.grpc.server.CollectingObserver;
import io.helidon.grpc.server.GrpcService;
import io.helidon.grpc.server.ServiceDescriptor;
import io.grpc.stub.StreamObserver;
/**
* AN implementation of the StringService.
*/
public class StringService
implements GrpcService {
@Override
public void update(ServiceDescriptor.Rules rules) {
rules.proto(Strings.getDescriptor())
.unary("Upper", this::upper)
.unary("Lower", this::lower)
.serverStreaming("Split", this::split)
.clientStreaming("Join", this::join)
.bidirectional("Echo", this::echo);
}
// ---- service methods -------------------------------------------------
private void upper(StringMessage request, StreamObserver<StringMessage> observer) {
complete(observer, response(request.getText().toUpperCase()));
}
private void lower(StringMessage request, StreamObserver<StringMessage> observer) {
complete(observer, response(request.getText().toLowerCase()));
}
private void split(StringMessage request, StreamObserver<StringMessage> observer) {
String[] parts = request.getText().split(" ");
stream(observer, Stream.of(parts).map(this::response));
}
private StreamObserver<StringMessage> join(StreamObserver<StringMessage> observer) {
return new CollectingObserver<>(
Collectors.joining(" "),
observer,
StringMessage::getText,
this::response);
}
private StreamObserver<StringMessage> echo(StreamObserver<StringMessage> observer) {
return new StreamObserver<StringMessage>() {
public void onNext(StringMessage value) {
observer.onNext(value);
}
public void onError(Throwable t) {
t.printStackTrace();
}
public void onCompleted() {
observer.onCompleted();
}
};
}
// ---- helper methods --------------------------------------------------
private StringMessage response(String text) {
return StringMessage.newBuilder().setText(text).build();
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
/**
* Common classes and ProtoBuf generated gRPC servcies used in the Helidon gROC examples.
*/
package io.helidon.grpc.examples.common;

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
syntax = "proto3";
option java_package = "io.helidon.grpc.examples.common";
service GreetService {
rpc Greet (GreetRequest) returns (GreetResponse) {}
rpc SetGreeting (SetGreetingRequest) returns (SetGreetingResponse) {}
}
message GreetRequest {
string name = 1;
}
message GreetResponse {
string message = 1;
}
message SetGreetingRequest {
string greeting = 1;
}
message SetGreetingResponse {
string greeting = 1;
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
syntax = "proto3";
option java_package = "io.helidon.grpc.examples.common";
service StringService {
rpc Upper (StringMessage) returns (StringMessage) {}
rpc Lower (StringMessage) returns (StringMessage) {}
rpc Split (StringMessage) returns (stream StringMessage) {}
rpc Join (stream StringMessage) returns (StringMessage) {}
rpc Echo (stream StringMessage) returns (stream StringMessage) {}
}
message StringMessage {
string text = 1;
}

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-project</artifactId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<artifactId>helidon-examples-grpc-metrics</artifactId>
<name>Helidon gRPC Server Examples Metrics</name>
<description>
Examples of elementary use of the gRPC Server metrics
</description>
<properties>
<mainClass>io.helidon.grpc.examples.metrics.Server</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-metrics</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.bundles</groupId>
<artifactId>helidon-bundles-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-client</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,105 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.metrics;
import java.util.logging.LogManager;
import io.helidon.config.Config;
import io.helidon.grpc.examples.common.GreetService;
import io.helidon.grpc.examples.common.StringService;
import io.helidon.grpc.metrics.GrpcMetrics;
import io.helidon.grpc.server.GrpcRouting;
import io.helidon.grpc.server.GrpcServer;
import io.helidon.grpc.server.GrpcServerConfiguration;
import io.helidon.metrics.MetricsSupport;
import io.helidon.webserver.Routing;
import io.helidon.webserver.ServerConfiguration;
import io.helidon.webserver.WebServer;
/**
* A basic example of a Helidon gRPC server.
*/
public class Server {
private Server() {
}
/**
* The main program entry point.
*
* @param args the program arguments
*
* @throws Exception if an error occurs
*/
public static void main(String[] args) throws Exception {
// By default this will pick up application.yaml from the classpath
Config config = Config.create();
// load logging configuration
LogManager.getLogManager().readConfiguration(
Server.class.getResourceAsStream("/logging.properties"));
// Get gRPC server config from the "grpc" section of application.yaml
GrpcServerConfiguration serverConfig =
GrpcServerConfiguration.builder(config.get("grpc")).build();
GrpcRouting grpcRouting = GrpcRouting.builder()
.intercept(GrpcMetrics.counted()) // global metrics - all service methods counted
.register(new GreetService(config)) // GreetService uses global metrics so all methods are counted
.register(new StringService(), rules -> {
// service level metrics - StringService overrides global so that its methods are timed
rules.intercept(GrpcMetrics.timed())
// method level metrics - overrides service and global
.intercept("Upper", GrpcMetrics.histogram());
})
.build();
GrpcServer grpcServer = GrpcServer.create(serverConfig, grpcRouting);
// Try to start the server. If successful, print some info and arrange to
// print a message at shutdown. If unsuccessful, print the exception.
grpcServer.start()
.thenAccept(s -> {
System.out.println("gRPC server is UP! http://localhost:" + s.port());
s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
})
.exceptionally(t -> {
System.err.println("Startup failed: " + t.getMessage());
t.printStackTrace(System.err);
return null;
});
// start web server with the metrics endpoints
Routing routing = Routing.builder()
.register(MetricsSupport.create())
.build();
ServerConfiguration webServerConfig = ServerConfiguration.builder(config.get("webserver")).build();
WebServer.create(webServerConfig, routing)
.start()
.thenAccept(s -> {
System.out.println("HTTP server is UP! http://localhost:" + s.port());
s.whenShutdown().thenRun(() -> System.out.println("HTTP server is DOWN. Good bye!"));
})
.exceptionally(t -> {
System.err.println("Startup failed: " + t.getMessage());
t.printStackTrace(System.err);
return null;
});
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
/**
* An example of gRPC metrics.
* <p>
* Start with {@link io.helidon.grpc.examples.metrics.Server Main} class.
*/
package io.helidon.grpc.examples.metrics;

View File

@@ -0,0 +1,26 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
app:
greeting: "Hello"
grpc:
name: "test.server"
port: 1408
webserver:
port: 8080
bind-address: "0.0.0.0"

View File

@@ -0,0 +1,37 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
# Example Logging Configuration File
# For more information see $JAVA_HOME/jre/lib/logging.properties
# Send messages to the console
handlers=java.util.logging.ConsoleHandler
# Global default logging level. Can be overriden by specific handlers and loggers
.level=INFO
# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
# It replaces "!thread!" with the current thread name
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
#Component specific log levels
#io.helidon.webserver.level=INFO
#io.helidon.config.level=INFO
#io.helidon.security.level=INFO
#io.helidon.common.level=INFO
#io.netty.level=INFO

View File

@@ -0,0 +1,86 @@
Opentracing gRPC Server Example Application
===========================================
Running locally
---------------
Prerequisites:
1. Requirements: JDK9, Maven, Docker (optional)
2. Add following lines to `/etc/hosts`
```
127.0.0.1 zipkin
```
3. Run Zipkin: <br/>
In Docker:
```
docker run -d -p 9411:9411 openzipkin/zipkin
```
or with Java 8:
```
wget -O zipkin.jar 'https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec'
java -jar zipkin.jar
```
Build and run:
```
mvn clean install -pl examples/grpc/opentracing
mvn exec:java -pl examples/grpc/opentracing
curl "http://localhost:8080/test"
```
Check out the traces at: ```http://zipkin:9411```
Running in Minikube
-------------------
### Preparing the infrastructure ###
Starting Minikube
```
% minikube start
% kubectl version
Client Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.2", GitCommit:"477efc3cbe6a7effca06bd1452fa356e2201e1ee", GitTreeState:"clean", BuildDate:"2017-04-19T22:51:36Z", GoVersion:"go1.8.1", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.0", GitCommit:"fff5156092b56e6bd60fff75aad4dc9de6b6ef37", GitTreeState:"dirty", BuildDate:"2017-04-07T20:46:46Z", GoVersion:"go1.7.3", Compiler:"gc", Platform:"linux/amd64"}
% minikube dashboard
Waiting, endpoint for service is not ready yet...
Opening kubernetes dashboard in default browser...
```
Running Zipkin in K8S
```
% kubectl run zipkin --image=openzipkin/zipkin --port=9411
deployment "zipkin" created
% kubectl expose deployment zipkin --type=NodePort
service "zipkin" exposed
% kubectl get pod
NAME READY STATUS RESTARTS AGE
zipkin-2596933303-bccnw 0/1 ContainerCreating 0 14s
% kubectl get pod
NAME READY STATUS RESTARTS AGE
zipkin-2596933303-bccnw 1/1 Running 0 16s
% minikube service zipkin
Opening kubernetes service default/zipkin in default browser...
```
Running opentracing app
```
% eval $(minikube docker-env)
% mvn clean install -pl examples/grpc/opentracing docker:build
% kubectl run helidon-grpc-opentracing-example --image=mic.docker.oraclecorp.com/helidon-grpc-opentracing-example:1.0.1-SNAPSHOT --port=1408 --image-pull-policy=Never
deployment "helidon-grpc-opentracing-example" created
% kubectl expose deployment helidon-grpc-opentracing-example --type=NodePort
service "helidon-grpc-opentracing-example" exposed
% curl $(minikube service helidon-webserver-opentracing-example --url)/test
Hello World!%
```

View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-project</artifactId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<artifactId>helidon-examples-grpc-opentracing</artifactId>
<name>Helidon gRPC Server Examples OpenTracing</name>
<description>
Examples gRPC application using Open Tracing
</description>
<properties>
<mainClass>io.helidon.grpc.examples.opentracing.ZipkinExampleMain</mainClass>
<docker.image.name>${docker.registry}/helidon-grpc-opentracing-example:${project.version}</docker.image.name>
<docker.server.id>mic-docker-registry-automation</docker.server.id>
<docker.registry>mic.docker.oraclecorp.com</docker.registry>
<docker.registry.url>https://${docker.registry}/v1/</docker.registry.url>
<docker.maven.plugin.spotify.version>0.3.3</docker.maven.plugin.spotify.version>
<docker.image.version></docker.image.version>
</properties>
<dependencies>
<dependency>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.bundles</groupId>
<artifactId>helidon-bundles-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.tracing</groupId>
<artifactId>helidon-tracing-zipkin</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-docker-resources</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/distribution/container</outputDirectory>
<resources>
<resource>
<directory>src/main/docker</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<serverId>${docker.server.id}</serverId>
<registryUrl>${docker.registry.url}</registryUrl>
<imageName>${docker.image.name}</imageName>
<dockerDirectory>${project.build.directory}/distribution/container</dockerDirectory>
<imageTags>
<imageTag>${docker.image.version}</imageTag>
<imageTag>latest</imageTag>
</imageTags>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}-fat.jar</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,23 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
FROM java:9
EXPOSE 8080
COPY ./* /
RUN rm Dockerfile
WORKDIR /
CMD if [ ! -z "$MIC_CONTAINER_MEM_QUOTA" ]; then java -Xmx${MIC_CONTAINER_MEM_QUOTA}m ${JAVA_OPTS} -jar ${artifactId}-${version}-fat.jar ; else java ${JAVA_OPTS} -jar ${artifactId}-${version}-fat.jar ; fi

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.opentracing;
import java.util.logging.LogManager;
import io.helidon.config.Config;
import io.helidon.grpc.examples.common.GreetService;
import io.helidon.grpc.examples.common.StringService;
import io.helidon.grpc.server.GrpcRouting;
import io.helidon.grpc.server.GrpcServer;
import io.helidon.grpc.server.GrpcServerConfiguration;
import io.helidon.grpc.server.ServerRequestAttribute;
import io.helidon.grpc.server.TracingConfiguration;
import io.helidon.tracing.TracerBuilder;
import io.opentracing.Tracer;
/**
* An example gRPC server with Zipkin tracing enabled.
*/
public class ZipkinExampleMain {
private ZipkinExampleMain() {
}
/**
* Program entry point.
*
* @param args the program command line arguments
* @throws Exception if there is a program error
*/
public static void main(String[] args) throws Exception {
// By default this will pick up application.yaml from the classpath
Config config = Config.create();
// load logging configuration
LogManager.getLogManager().readConfiguration(
ZipkinExampleMain.class.getResourceAsStream("/logging.properties"));
Tracer tracer = TracerBuilder.create(config.get("tracing")).build();
TracingConfiguration tracingConfig = new TracingConfiguration.Builder()
.withStreaming()
.withVerbosity()
.withTracedAttributes(ServerRequestAttribute.CALL_ATTRIBUTES,
ServerRequestAttribute.HEADERS,
ServerRequestAttribute.METHOD_NAME)
.build();
// Get gRPC server config from the "grpc" section of application.yaml
GrpcServerConfiguration serverConfig =
GrpcServerConfiguration.builder(config.get("grpc")).tracer(tracer).tracingConfig(tracingConfig).build();
GrpcServer grpcServer = GrpcServer.create(serverConfig, createRouting(config));
// Try to start the server. If successful, print some info and arrange to
// print a message at shutdown. If unsuccessful, print the exception.
grpcServer.start()
.thenAccept(s -> {
System.out.println("gRPC server is UP! http://localhost:" + s.port());
s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
})
.exceptionally(t -> {
System.err.println("Startup failed: " + t.getMessage());
t.printStackTrace(System.err);
return null;
});
}
private static GrpcRouting createRouting(Config config) {
return GrpcRouting.builder()
.register(new GreetService(config))
.register(new StringService())
.build();
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
/**
* A set of small usage examples of running the Helidon gRPC server with Zipkin tracing enabled.
*/
package io.helidon.grpc.examples.opentracing;

View File

@@ -0,0 +1,29 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
app:
greeting: "Hello"
grpc:
name: "test.server"
port: 1408
webserver:
port: 8080
bind-address: "0.0.0.0"
tracing:
service: "grpc-server"

View File

@@ -0,0 +1,37 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
# Example Logging Configuration File
# For more information see $JAVA_HOME/jre/lib/logging.properties
# Send messages to the console
handlers=java.util.logging.ConsoleHandler
# Global default logging level. Can be overriden by specific handlers and loggers
.level=INFO
# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
# It replaces "!thread!" with the current thread name
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
#Component specific log levels
#io.helidon.webserver.level=INFO
#io.helidon.config.level=INFO
#io.helidon.security.level=INFO
#io.helidon.common.level=INFO
#io.netty.level=INFO

43
examples/grpc/pom.xml Normal file
View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.examples</groupId>
<artifactId>helidon-examples-project</artifactId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-project</artifactId>
<name>Helidon gRPC Examples</name>
<packaging>pom</packaging>
<modules>
<module>common</module>
<module>basics</module>
<module>metrics</module>
<module>opentracing</module>
<module>security</module>
<module>security-abac</module>
<module>security-outbound</module>
</modules>
</project>

View File

@@ -0,0 +1,16 @@
# Helidon gRPC Security ABAC Example
An example gRPC server for attribute based access control.
## Build
```
mvn package
```
## Run
```
mvn exec:java
```

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-project</artifactId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<artifactId>helidon-examples-grpc-security-abac</artifactId>
<name>Helidon gRPC Server Examples ABAC Security</name>
<description>
Examples of securing gRPC services using ABAC
</description>
<properties>
<mainClass>io.helidon.grpc.examples.security.abac.AbacServer</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.security.integration</groupId>
<artifactId>helidon-security-integration-grpc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.bundles</groupId>
<artifactId>helidon-bundles-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.bundles</groupId>
<artifactId>helidon-bundles-security</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.security.abac</groupId>
<artifactId>helidon-security-abac-policy-el</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-services</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,121 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.security.abac;
import java.time.DayOfWeek;
import java.time.LocalTime;
import java.util.logging.LogManager;
import io.helidon.grpc.examples.common.StringService;
import io.helidon.grpc.server.GrpcRouting;
import io.helidon.grpc.server.GrpcServer;
import io.helidon.grpc.server.GrpcServerConfiguration;
import io.helidon.grpc.server.ServiceDescriptor;
import io.helidon.security.Security;
import io.helidon.security.SubjectType;
import io.helidon.security.abac.policy.PolicyValidator;
import io.helidon.security.abac.scope.ScopeValidator;
import io.helidon.security.abac.time.TimeValidator;
import io.helidon.security.integration.grpc.GrpcSecurity;
import io.helidon.security.providers.abac.AbacProvider;
/**
* An example of a secure gRPC server that uses
* ABAC security configured in the code below.
* <p>
* This server configures in code the same rules that
* the {@link AbacServerFromConfig} class uses from
* its configuration.
*/
public class AbacServer {
private AbacServer() {
}
/**
* Main entry point.
*
* @param args the program arguments
*
* @throws Exception if an error occurs
*/
public static void main(String[] args) throws Exception {
LogManager.getLogManager().readConfiguration(
AbacServer.class.getResourceAsStream("/logging.properties"));
Security security = Security.builder()
.addProvider(AtnProvider.builder().build()) // add out custom provider
.addProvider(AbacProvider.builder().build()) // add the ABAC provider
.build();
// Create the time validator that will be used by the ABAC security provider
TimeValidator.TimeConfig validTimes = TimeValidator.TimeConfig.builder()
.addBetween(LocalTime.of(8, 15), LocalTime.of(12, 0))
.addBetween(LocalTime.of(12, 30), LocalTime.of(17, 30))
.addDaysOfWeek(DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY, DayOfWeek.FRIDAY)
.build();
// Create the policy validator that will be used by the ABAC security provider
PolicyValidator.PolicyConfig validPolicy = PolicyValidator.PolicyConfig.builder()
.statement("${env.time.year >= 2017}")
.build();
// Create the scope validator that will be used by the ABAC security provider
ScopeValidator.ScopesConfig validScopes = ScopeValidator.ScopesConfig.create("calendar_read", "calendar_edit");
// Create the Atn config that will be used by out custom security provider
AtnProvider.AtnConfig atnConfig = AtnProvider.AtnConfig.builder()
.addAuth(AtnProvider.Auth.builder("user")
.type(SubjectType.USER)
.roles("user_role")
.scopes("calendar_read", "calendar_edit")
.build())
.addAuth(AtnProvider.Auth.builder("service")
.type(SubjectType.SERVICE)
.roles("service_role")
.scopes("calendar_read", "calendar_edit")
.build())
.build();
ServiceDescriptor stringService = ServiceDescriptor.builder(new StringService())
.intercept("Upper", GrpcSecurity.secure()
.customObject(atnConfig)
.customObject(validScopes)
.customObject(validTimes)
.customObject(validPolicy))
.build();
GrpcRouting grpcRouting = GrpcRouting.builder()
.intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.secure()))
.register(stringService)
.build();
GrpcServerConfiguration serverConfig = GrpcServerConfiguration.builder().build();
GrpcServer grpcServer = GrpcServer.create(serverConfig, grpcRouting);
grpcServer.start()
.thenAccept(s -> {
System.out.println("gRPC server is UP! http://localhost:" + s.port());
s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
})
.exceptionally(t -> {
System.err.println("Startup failed: " + t.getMessage());
t.printStackTrace(System.err);
return null;
});
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.security.abac;
import java.util.logging.LogManager;
import io.helidon.config.Config;
import io.helidon.grpc.examples.common.StringService;
import io.helidon.grpc.server.GrpcRouting;
import io.helidon.grpc.server.GrpcServer;
import io.helidon.grpc.server.GrpcServerConfiguration;
import io.helidon.security.Security;
import io.helidon.security.integration.grpc.GrpcSecurity;
/**
* An example of a secure gRPC server that uses ABAC
* security configured from configuration the configuration
* file application.conf.
* <p>
* This server's configuration file configures security with
* same rules that the {@link AbacServer} class builds in
* code.
*/
public class AbacServerFromConfig {
private AbacServerFromConfig() {
}
/**
* Main entry point.
*
* @param args the program arguments
*
* @throws Exception if an error occurs
*/
public static void main(String[] args) throws Exception {
LogManager.getLogManager().readConfiguration(
AbacServerFromConfig.class.getResourceAsStream("/logging.properties"));
Config config = Config.create();
Security security = Security.create(config.get("security"));
GrpcRouting grpcRouting = GrpcRouting.builder()
.intercept(GrpcSecurity.create(security, config.get("security")))
.register(new StringService())
.build();
GrpcServerConfiguration serverConfig = GrpcServerConfiguration.create(config.get("grpc"));
GrpcServer grpcServer = GrpcServer.create(serverConfig, grpcRouting);
grpcServer.start()
.thenAccept(s -> {
System.out.println("gRPC server is UP! http://localhost:" + s.port());
s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
})
.exceptionally(t -> {
System.err.println("Startup failed: " + t.getMessage());
t.printStackTrace(System.err);
return null;
});
}
}

View File

@@ -0,0 +1,413 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.security.abac;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import io.helidon.common.CollectionsHelper;
import io.helidon.config.Config;
import io.helidon.security.AuthenticationResponse;
import io.helidon.security.EndpointConfig;
import io.helidon.security.Grant;
import io.helidon.security.Principal;
import io.helidon.security.ProviderRequest;
import io.helidon.security.Role;
import io.helidon.security.Subject;
import io.helidon.security.SubjectType;
import io.helidon.security.spi.AuthenticationProvider;
import io.helidon.security.spi.SynchronousProvider;
/**
* Example authentication provider that reads annotation to create a subject.
*/
public class AtnProvider extends SynchronousProvider implements AuthenticationProvider {
/**
* The configuration key for this provider.
*/
public static final String CONFIG_KEY = "atn";
private final Config config;
private AtnProvider(Config config) {
this.config = config;
}
@Override
protected AuthenticationResponse syncAuthenticate(ProviderRequest providerRequest) {
EndpointConfig endpointConfig = providerRequest.endpointConfig();
Config atnConfig = endpointConfig.config(CONFIG_KEY).orElse(null);
Subject user = null;
Subject service = null;
List<Auth> list;
Optional<AtnConfig> optional = providerRequest.endpointConfig().instance(AtnConfig.class);
if (optional.isPresent()) {
list = optional.get().auths();
} else if (atnConfig != null && !atnConfig.isLeaf()) {
list = atnConfig.asNodeList()
.map(this::fromConfig).orElse(Collections.emptyList());
} else {
list = fromAnnotations(endpointConfig);
}
for (Auth authentication : list) {
if (authentication.type() == SubjectType.USER) {
user = buildSubject(authentication);
} else {
service = buildSubject(authentication);
}
}
return AuthenticationResponse.success(user, service);
}
private List<Auth> fromConfig(List<Config> configList) {
return configList.stream()
.map(Auth::new)
.collect(Collectors.toList());
}
private List<Auth> fromAnnotations(EndpointConfig endpointConfig) {
return endpointConfig.combineAnnotations(Authentications.class, EndpointConfig.AnnotationScope.METHOD)
.stream()
.map(Authentications::value)
.flatMap(Arrays::stream)
.map(Auth::new)
.collect(Collectors.toList());
}
private Subject buildSubject(Auth authentication) {
Subject.Builder subjectBuilder = Subject.builder();
subjectBuilder.principal(Principal.create(authentication.principal()));
Arrays.stream(authentication.roles())
.map(Role::create)
.forEach(subjectBuilder::addGrant);
Arrays.stream(authentication.scopes())
.map(scope -> Grant.builder().name(scope).type("scope").build())
.forEach(subjectBuilder::addGrant);
return subjectBuilder.build();
}
@Override
public Collection<Class<? extends Annotation>> supportedAnnotations() {
return CollectionsHelper.setOf(Authentication.class);
}
/**
* Create a {@link AtnProvider}.
* @return a {@link AtnProvider}
*/
public static AtnProvider create() {
return builder().build();
}
/**
* Create a {@link AtnProvider}.
*
* @param config the configuration for the {@link AtnProvider}
*
* @return a {@link AtnProvider}
*/
public static AtnProvider create(Config config) {
return builder(config).build();
}
/**
* Create a {@link AtnProvider.Builder}.
* @return a {@link AtnProvider.Builder}
*/
public static Builder builder() {
return builder(null);
}
/**
* Create a {@link AtnProvider.Builder}.
*
* @param config the configuration for the {@link AtnProvider}
*
* @return a {@link AtnProvider.Builder}
*/
public static Builder builder(Config config) {
return new Builder(config);
}
/**
* A builder that builds {@link AtnProvider} instances.
*/
public static class Builder
implements io.helidon.common.Builder<AtnProvider> {
private Config config;
private Builder(Config config) {
this.config = config;
}
/**
* Set the configuration for the {@link AtnProvider}.
* @param config the configuration for the {@link AtnProvider}
* @return this builder
*/
public Builder config(Config config) {
this.config = config;
return this;
}
@Override
public AtnProvider build() {
return new AtnProvider(config);
}
}
/**
* Authentication annotation.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Inherited
@Repeatable(Authentications.class)
public @interface Authentication {
/**
* Name of the principal.
*
* @return principal name
*/
String value();
/**
* Type of the subject, defaults to user.
*
* @return type
*/
SubjectType type() default SubjectType.USER;
/**
* Granted roles.
* @return array of roles
*/
String[] roles() default "";
/**
* Granted scopes.
* @return array of scopes
*/
String[] scopes() default "";
}
/**
* Repeatable annotation for {@link Authentication}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Inherited
public @interface Authentications {
/**
* Repeating annotation.
* @return annotations
*/
Authentication[] value();
}
/**
* A holder for authentication settings.
*/
public static class Auth {
private String principal;
private SubjectType type = SubjectType.USER;
private String[] roles;
private String[] scopes;
private Auth(Authentication authentication) {
principal = authentication.value();
type = authentication.type();
roles = authentication.roles();
scopes = authentication.scopes();
}
private Auth(Config config) {
config.get("principal").ifExists(cfg -> principal = cfg.asString().get());
config.get("type").ifExists(cfg -> type = SubjectType.valueOf(cfg.asString().get()));
config.get("roles").ifExists(cfg -> roles = cfg.asList(String.class).get().toArray(new String[0]));
config.get("scopes").ifExists(cfg -> scopes = cfg.asList(String.class).get().toArray(new String[0]));
}
private Auth(String principal, SubjectType type, String[] roles, String[] scopes) {
this.principal = principal;
this.type = type;
this.roles = roles;
this.scopes = scopes;
}
private String principal() {
return principal;
}
private SubjectType type() {
return type;
}
private String[] roles() {
return roles;
}
private String[] scopes() {
return scopes;
}
/**
* Obtain a builder for building {@link Auth} instances.
*
* @param principal the principal name
*
* @return a builder for building {@link Auth} instances.
*/
public static Builder builder(String principal) {
return new Auth.Builder(principal);
}
/**
* A builder for building {@link Auth} instances.
*/
public static class Builder
implements io.helidon.common.Builder<Auth> {
private final String principal;
private SubjectType type = SubjectType.USER;
private String[] roles;
private String[] scopes;
private Builder(String principal) {
this.principal = principal;
}
/**
* Set the {@link SubjectType}.
* @param type the {@link SubjectType}
* @return this builder
*/
public Builder type(SubjectType type) {
this.type = type;
return this;
}
/**
* Set the roles.
* @param roles the role names
* @return this builder
*/
public Builder roles(String... roles) {
this.roles = roles;
return this;
}
/**
* Set the scopes.
* @param scopes the scopes names
* @return this builder
*/
public Builder scopes(String... scopes) {
this.scopes = scopes;
return this;
}
@Override
public Auth build() {
return new Auth(principal, type, roles, scopes);
}
}
}
/**
* The configuration for a {@link AtnProvider}.
*/
public static class AtnConfig {
private final List<Auth> authData;
private AtnConfig(List<Auth> list) {
this.authData = list;
}
/**
* Obtain the {@link List} of {@link Auth}s to use.
*
* @return the {@link List} of {@link Auth}s to use
*/
public List<Auth> auths() {
return Collections.unmodifiableList(authData);
}
/**
* Obtain a builder for building {@link AtnConfig} instances.
*
* @return a builder for building {@link AtnConfig} instances
*/
public static AtnConfig.Builder builder() {
return new Builder();
}
/**
* A builder for building {@link AtnConfig} instances.
*/
public static class Builder
implements io.helidon.common.Builder<AtnConfig> {
private final List<Auth> authData = new ArrayList<>();
/**
* Add an {@link Auth} instance.
*
* @param auth the {@link Auth} to add
*
* @return this builder
*
* @throws java.lang.NullPointerException if the {@link Auth} is null
*/
public Builder addAuth(Auth auth) {
authData.add(Objects.requireNonNull(auth));
return this;
}
@Override
public AtnConfig build() {
return new AtnConfig(authData);
}
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.security.abac;
import io.helidon.config.Config;
import io.helidon.security.spi.SecurityProvider;
import io.helidon.security.spi.SecurityProviderService;
/**
* A service provider for the {@link AtnProvider}.
*/
public class AtnProviderService
implements SecurityProviderService {
@Override
public String providerConfigKey() {
return AtnProvider.CONFIG_KEY;
}
@Override
public Class<? extends SecurityProvider> providerClass() {
return AtnProvider.class;
}
@Override
public SecurityProvider providerInstance(Config config) {
return AtnProvider.create(config);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.security.abac;
import io.helidon.grpc.examples.common.StringServiceGrpc;
import io.helidon.grpc.examples.common.Strings;
import io.grpc.Channel;
import io.grpc.ManagedChannelBuilder;
/**
* A {@link io.helidon.grpc.examples.common.StringService} client that optionally
* provides {@link io.grpc.CallCredentials} using basic auth.
*/
public class SecureStringClient {
private SecureStringClient() {
}
/**
* Program entry point.
*
* @param args program arguments
*/
public static void main(String[] args) {
Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408)
.usePlaintext()
.build();
StringServiceGrpc.StringServiceBlockingStub stub = StringServiceGrpc.newBlockingStub(channel);
String text = "abcde";
Strings.StringMessage request = Strings.StringMessage.newBuilder().setText(text).build();
Strings.StringMessage response = stub.upper(request);
System.out.println("Text '" + text + "' to upper is '" + response.getText() + "'");
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
/**
* A set of small usage examples. Start with {@link io.helidon.grpc.examples.security.SecureServer Main} class.
*/
package io.helidon.grpc.examples.security.abac;

View File

@@ -0,0 +1,19 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
io.helidon.security.abac.scope.ScopeValidator
io.helidon.security.abac.policy.PolicyValidator
io.helidon.security.abac.time.TimeValidator

View File

@@ -0,0 +1,17 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
io.helidon.grpc.examples.security.abac.AtnProviderService

View File

@@ -0,0 +1,86 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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:
port: 1408
security:
providers:
- abac:
# prepares environment
# executes attribute validations
# validates that attributes were processed
# grants/denies access to resource
#
####
# Combinations:
# # Will fail if any attribute is not validated and if any has failed validation
# fail-on-unvalidated: true
# fail-if-none-validated: true
#
# # Will fail if there is one or more attributes present and NONE of them is validated or if any has failed validation
# # Will NOT fail if there is at least one validated attribute and any number of not validated attributes (and NONE failed)
# fail-on-unvalidated: false
# fail-if-none-validated: true
#
# # Will fail if there is any attribute that failed validation
# # Will NOT fail if there are no failed validation or if there are NONE validated
# fail-on-unvalidated: false
# fail-if-none-validated: false
####
# fail if an attribute was not validated (e.g. we do not know, whether it is valid or not)
# defaults to true
fail-on-unvalidated: true
# fail if none of the attributes were validated
# defaults to true
fail-if-none-validated: true
- atn:
class: "io.helidon.grpc.examples.security.abac.AtnProvider"
grpc-server:
# Configuration of integration with grpc server
# The default configuration to apply to all services not explicitly configured below
defaults:
authenticate: true
authorize: true
services:
- name: "StringService"
methods:
- name: "Upper"
# Define our custom authenticator rules for the Upper method
atn:
- principal: "user"
type: "USER"
roles: ["user_role"]
scopes: ["calendar_read", "calendar_edit"]
- principal: "service"
type: "SERVICE"
roles: ["service_role"]
scopes: ["calendar_read", "calendar_edit"]
# Define ABAC rules for the Upper method
abac:
scopes: ["calendar_read", "calendar_edit"]
time:
time-of-day:
- from: "08:15:00"
to: "12:00:00"
- from: "12:30"
to: "17:30"
days-of-week: ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"]
policy:
statement: "${env.time.year >= 2017}"

View File

@@ -0,0 +1,21 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level=FINEST
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
.level=INFO
AUDIT.level=FINEST

View File

@@ -0,0 +1,17 @@
# Helidon gRPC Security ABAC Example
An example gRPC outbound security
## Build
```
mvn package
```
## Run
To start the server run:
```
mvn exec:java
```

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-project</artifactId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<artifactId>helidon-examples-grpc-security-outbound</artifactId>
<name>Helidon gRPC Server Examples Outbound Security</name>
<description>
Examples of outbound security when using gRPC services
</description>
<properties>
<mainClass>io.helidon.grpc.examples.security.outbound.SecureServer</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.security.integration</groupId>
<artifactId>helidon-security-integration-grpc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.bundles</groupId>
<artifactId>helidon-bundles-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.bundles</groupId>
<artifactId>helidon-bundles-webserver</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.security.integration</groupId>
<artifactId>helidon-security-integration-jersey</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.bundles</groupId>
<artifactId>helidon-bundles-security</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-client</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.security.outbound;
import io.helidon.config.Config;
import io.helidon.grpc.examples.common.Greet;
import io.helidon.grpc.examples.common.GreetServiceGrpc;
import io.helidon.security.Security;
import io.helidon.security.integration.grpc.GrpcClientSecurity;
import io.helidon.security.providers.httpauth.HttpBasicAuthProvider;
import io.grpc.Channel;
import io.grpc.ManagedChannelBuilder;
/**
* A GreetService client that uses {@link io.grpc.CallCredentials} using basic auth.
*/
public class SecureGreetClient {
private SecureGreetClient() {
}
/**
* Program entry point.
*
* @param args program arguments
*/
public static void main(String[] args) {
Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408)
.usePlaintext()
.build();
Config config = Config.create();
// configure Helidon security and add the basic auth provider
Security security = Security.builder()
.addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth")))
.build();
// create the gRPC client security call credentials
// setting the properties used by the basic auth provider for user name and password
GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client"))
.property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, "Bob")
.property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, "password")
.build();
// create the GreetService client stub and use the GrpcClientSecurity call credentials
GreetServiceGrpc.GreetServiceBlockingStub stub = GreetServiceGrpc.newBlockingStub(channel)
.withCallCredentials(clientSecurity);
Greet.GreetResponse greetResponse = stub.greet(Greet.GreetRequest.newBuilder().setName("Bob").build());
System.out.println(greetResponse.getMessage());
Greet.SetGreetingResponse setGreetingResponse =
stub.setGreeting(Greet.SetGreetingRequest.newBuilder().setGreeting("Merhaba").build());
System.out.println("Greeting set to: " + setGreetingResponse.getGreeting());
greetResponse = stub.greet(Greet.GreetRequest.newBuilder().setName("Bob").build());
System.out.println(greetResponse.getMessage());
}
}

View File

@@ -0,0 +1,331 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.security.outbound;
import java.util.Optional;
import java.util.logging.LogManager;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Response;
import io.helidon.config.Config;
import io.helidon.grpc.core.GrpcHelper;
import io.helidon.grpc.examples.common.Greet;
import io.helidon.grpc.examples.common.StringService;
import io.helidon.grpc.examples.common.StringServiceGrpc;
import io.helidon.grpc.examples.common.Strings;
import io.helidon.grpc.server.GrpcRouting;
import io.helidon.grpc.server.GrpcServer;
import io.helidon.grpc.server.GrpcServerConfiguration;
import io.helidon.grpc.server.GrpcService;
import io.helidon.grpc.server.ServiceDescriptor;
import io.helidon.security.Security;
import io.helidon.security.SecurityContext;
import io.helidon.security.integration.grpc.GrpcClientSecurity;
import io.helidon.security.integration.grpc.GrpcSecurity;
import io.helidon.security.integration.jersey.ClientSecurityFeature;
import io.helidon.security.integration.webserver.WebSecurity;
import io.helidon.security.providers.httpauth.HttpBasicAuthProvider;
import io.helidon.webserver.Routing;
import io.helidon.webserver.ServerConfiguration;
import io.helidon.webserver.ServerRequest;
import io.helidon.webserver.ServerResponse;
import io.helidon.webserver.Service;
import io.helidon.webserver.WebServer;
import io.grpc.Channel;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.stub.StreamObserver;
/**
* An example server that configures services with outbound security.
*/
public class SecureServer {
private static GrpcServer grpcServer;
private static WebServer webServer;
private SecureServer() {
}
/**
* Program entry point.
*
* @param args the program command line arguments
* @throws Exception if there is a program error
*/
public static void main(String[] args) throws Exception {
LogManager.getLogManager().readConfiguration(
SecureServer.class.getResourceAsStream("/logging.properties"));
Config config = Config.create();
Security security = Security.builder()
.addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth")))
.build();
grpcServer = createGrpcServer(config.get("grpc"), security);
webServer = createWebServer(config.get("webserver"), security);
}
/**
* Create the gRPC server.
*/
private static GrpcServer createGrpcServer(Config config, Security security) {
GrpcRouting grpcRouting = GrpcRouting.builder()
// Add the security interceptor with a default of allowing any authenticated user
.intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate()))
// add the StringService with required role "admin"
.register(new StringService(), GrpcSecurity.rolesAllowed("admin"))
// add the GreetService (picking up the default security of any authenticated user)
.register(new GreetService())
.build();
GrpcServer grpcServer = GrpcServer.create(GrpcServerConfiguration.create(config), grpcRouting);
grpcServer.start()
.thenAccept(s -> {
System.out.println("gRPC server is UP! http://localhost:" + s.port());
s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
})
.exceptionally(t -> {
System.err.println("gRPC server startup failed: " + t.getMessage());
t.printStackTrace(System.err);
return null;
});
return grpcServer;
}
/**
* Create the web server.
*/
private static WebServer createWebServer(Config config, Security security) {
Routing routing = Routing.builder()
.register(WebSecurity.create(security).securityDefaults(WebSecurity.authenticate()))
.register(new RestService())
.build();
WebServer webServer = WebServer.create(ServerConfiguration.create(config), routing);
webServer.start()
.thenAccept(s -> {
System.out.println("Web server is UP! http://localhost:" + s.port());
s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
})
.exceptionally(t -> {
System.err.println("Web server startup failed: " + t.getMessage());
t.printStackTrace(System.err);
return null;
});
return webServer;
}
/**
* A gRPC greet service that uses outbound security to
* access a ReST API.
*/
public static class GreetService
implements GrpcService {
/**
* The current greeting.
*/
private String greeting = "hello";
/**
* The JAX-RS client to use to make ReST calls.
*/
private Client client;
private GreetService() {
client = ClientBuilder.newBuilder()
.register(new ClientSecurityFeature())
.build();
}
@Override
public void update(ServiceDescriptor.Rules rules) {
rules.proto(Greet.getDescriptor())
.unary("Greet", this::greet)
.unary("SetGreeting", this::setGreeting);
}
/**
* This method calls a secure ReST endpoint using the caller's credentials.
*
* @param request the request
* @param observer the observer to send the response to
*/
private void greet(Greet.GreetRequest request, StreamObserver<Greet.GreetResponse> observer) {
// Obtain the greeting name from the request (default to "World".
String name = Optional.ofNullable(request.getName()).orElse("World");
// Obtain the security context from the current gRPC context
SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get();
// Use the current credentials call the "lower" ReST endpoint which will call
// the "Lower" method on the secure gRPC StringService.
Response response = client.target("http://127.0.0.1:" + webServer.port())
.path("lower")
.queryParam("value", name)
.request()
.property(ClientSecurityFeature.PROPERTY_CONTEXT, securityContext)
.get();
int status = response.getStatus();
if (status == 200) {
// Send the response to the caller of the current greeting and lower case name
String nameLower = response.readEntity(String.class);
String msg = String.format("%s %s!", greeting, nameLower);
complete(observer, Greet.GreetResponse.newBuilder().setMessage(msg).build());
} else {
completeWithError(response, observer);
}
}
/**
* This method calls a secure ReST endpoint overriding the caller's credentials and
* using the admin user's credentials.
*
* @param request the request
* @param observer the observer to send the response to
*/
private void setGreeting(Greet.SetGreetingRequest request, StreamObserver<Greet.SetGreetingResponse> observer) {
// Obtain the greeting name from the request (default to "hello".
String name = Optional.ofNullable(request.getGreeting()).orElse("hello");
// Obtain the security context from the current gRPC context
SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get();
// Use the admin user's credentials call the "upper" ReST endpoint which will call
// the "Upper" method on the secure gRPC StringService.
Response response = client.target("http://127.0.0.1:" + webServer.port())
.path("upper")
.queryParam("value", name)
.request()
.property(ClientSecurityFeature.PROPERTY_CONTEXT, securityContext)
.property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, "Ted")
.property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, "secret")
.get();
if (response.getStatus() == 200) {
greeting = response.readEntity(String.class);
complete(observer, Greet.SetGreetingResponse.newBuilder().setGreeting(greeting).build());
} else {
completeWithError(response, observer);
}
}
private void completeWithError(Response response, StreamObserver observer) {
int status = response.getStatus();
if (status == Response.Status.UNAUTHORIZED.getStatusCode()
|| status == Response.Status.FORBIDDEN.getStatusCode()){
observer.onError(Status.PERMISSION_DENIED.asRuntimeException());
} else {
observer.onError(Status.INTERNAL.withDescription(response.readEntity(String.class)).asRuntimeException());
}
}
@Override
public String name() {
return "GreetService";
}
}
/**
* A ReST service that calls the gRPC StringService to mutate String values.
*/
public static class RestService
implements Service {
private Channel channel;
@Override
public void update(Routing.Rules rules) {
rules.get("/lower", WebSecurity.rolesAllowed("user"), this::lower)
.get("/upper", WebSecurity.rolesAllowed("user"), this::upper);
}
/**
* Call the gRPC StringService Lower method overriding the caller's credentials and
* using the admin user's credentials.
*
* @param req the http request
* @param res the http response
*/
private void lower(ServerRequest req, ServerResponse res) {
try {
// Create the gRPC client security credentials from the current request
// overriding with the admin user's credentials
GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(req)
.property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, "Ted")
.property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, "secret")
.build();
StringServiceGrpc.StringServiceBlockingStub stub = StringServiceGrpc.newBlockingStub(ensureChannel())
.withCallCredentials(clientSecurity);
String value = req.queryParams().first("value").orElse(null);
Strings.StringMessage response = stub.lower(Strings.StringMessage.newBuilder().setText(value).build());
res.status(200).send(response.getText());
} catch (StatusRuntimeException e) {
res.status(GrpcHelper.toHttpResponseStatus(e.getStatus())).send();
}
}
/**
* Call the gRPC StringService Upper method using the current caller's credentials.
*
* @param req the http request
* @param res the http response
*/
private void upper(ServerRequest req, ServerResponse res) {
try {
// Create the gRPC client security credentials from the current request
GrpcClientSecurity clientSecurity = GrpcClientSecurity.create(req);
StringServiceGrpc.StringServiceBlockingStub stub = StringServiceGrpc.newBlockingStub(ensureChannel())
.withCallCredentials(clientSecurity);
String value = req.queryParams().first("value").orElse(null);
Strings.StringMessage response = stub.upper(Strings.StringMessage.newBuilder().setText(value).build());
res.status(200).send(response.getText());
} catch (StatusRuntimeException e) {
res.status(GrpcHelper.toHttpResponseStatus(e.getStatus())).send();
}
}
private synchronized Channel ensureChannel() {
if (channel == null) {
channel = InProcessChannelBuilder.forName(grpcServer.configuration().name()).build();
}
return channel;
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
/**
* Examples of using outbound security with gRPC services.
*/
package io.helidon.grpc.examples.security.outbound;

View File

@@ -0,0 +1,34 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
app:
greeting: "Hello"
grpc:
name: "test.server"
port: 1408
webserver:
port: 8080
http-basic-auth:
users:
- login: "Ted"
password: "secret"
roles: ["user", "admin"]
- login: "Bob"
password: "password"
roles: ["user"]

View File

@@ -0,0 +1,39 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
# Example Logging Configuration File
# For more information see $JAVA_HOME/jre/lib/logging.properties
# Send messages to the console
handlers=java.util.logging.ConsoleHandler
# Global default logging level. Can be overriden by specific handlers and loggers
.level=INFO
AUDIT.level=FINEST
# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
# It replaces "!thread!" with the current thread name
java.util.logging.ConsoleHandler.level=FINEST
java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
#Component specific log levels
#io.helidon.webserver.level=INFO
#io.helidon.config.level=INFO
#io.helidon.security.level=INFO
#io.helidon.common.level=INFO
#io.netty.level=INFO

View File

@@ -0,0 +1,16 @@
# Helidon gRPC Security Example
An example gRPC server using basic auth security.
## Build
```
mvn package
```
## Run
```
mvn exec:java
```

View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-project</artifactId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<artifactId>helidon-examples-grpc-security</artifactId>
<name>Helidon gRPC Server Examples Security</name>
<description>
Examples of securing gRPC services
</description>
<properties>
<mainClass>io.helidon.grpc.examples.security.SecureServer</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.helidon.examples.grpc</groupId>
<artifactId>helidon-examples-grpc-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.bundles</groupId>
<artifactId>helidon-bundles-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.security.integration</groupId>
<artifactId>helidon-security-integration-grpc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.security.providers</groupId>
<artifactId>helidon-security-providers-http-auth</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-services</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.security;
import io.helidon.config.Config;
import io.helidon.grpc.examples.common.Greet;
import io.helidon.grpc.examples.common.GreetServiceGrpc;
import io.helidon.security.Security;
import io.helidon.security.integration.grpc.GrpcClientSecurity;
import io.helidon.security.providers.httpauth.HttpBasicAuthProvider;
import io.grpc.CallCredentials;
import io.grpc.Channel;
import io.grpc.ManagedChannelBuilder;
/**
* A {@link io.helidon.grpc.examples.common.GreetService} client that optionally
* provides {@link CallCredentials} using basic auth.
*/
public class SecureGreetClient {
private SecureGreetClient() {
}
/**
* Main entry point.
*
* @param args the program arguments - {@code arg[0]} is the user name
* and {@code arg[1] is the password}
*/
public static void main(String[] args) {
Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408)
.usePlaintext()
.build();
// Obtain the user name and password from the program arguments
String user = args.length >= 2 ? args[0] : null;
String password = args.length >= 2 ? args[1] : null;
Config config = Config.create();
// configure Helidon security and add the basic auth provider
Security security = Security.builder()
.addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth")))
.build();
// create the gRPC client security call credentials
// setting the properties used by the basic auth provider for user name and password
GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client"))
.property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, user)
.property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, password)
.build();
// create the GreetService client stub and use the GrpcClientSecurity call credentials
GreetServiceGrpc.GreetServiceBlockingStub greetSvc = GreetServiceGrpc.newBlockingStub(channel)
.withCallCredentials(clientSecurity);
greet(greetSvc);
setGreeting(greetSvc);
greet(greetSvc);
}
private static void greet(GreetServiceGrpc.GreetServiceBlockingStub greetSvc) {
try {
Greet.GreetRequest request = Greet.GreetRequest.newBuilder().setName("Aleks").build();
Greet.GreetResponse response = greetSvc.greet(request);
System.out.println(response);
} catch (Exception e) {
System.err.println("Caught exception obtaining greeting: " + e.getMessage());
}
}
private static void setGreeting(GreetServiceGrpc.GreetServiceBlockingStub greetSvc) {
try {
Greet.SetGreetingRequest setRequest = Greet.SetGreetingRequest.newBuilder().setGreeting("Hey").build();
Greet.SetGreetingResponse setResponse = greetSvc.setGreeting(setRequest);
System.out.println(setResponse);
} catch (Exception e) {
System.err.println("Caught exception setting greeting: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.security;
import java.util.logging.LogManager;
import io.helidon.config.Config;
import io.helidon.grpc.examples.common.GreetService;
import io.helidon.grpc.examples.common.StringService;
import io.helidon.grpc.server.GrpcRouting;
import io.helidon.grpc.server.GrpcServer;
import io.helidon.grpc.server.GrpcServerConfiguration;
import io.helidon.grpc.server.ServiceDescriptor;
import io.helidon.security.Security;
import io.helidon.security.integration.grpc.GrpcSecurity;
import io.helidon.security.providers.httpauth.HttpBasicAuthProvider;
/**
* An example of a secure gRPC server.
*/
public class SecureServer {
private SecureServer() {
}
/**
* Main entry point.
*
* @param args the program arguments
*
* @throws Exception if an error occurs
*/
public static void main(String[] args) throws Exception {
LogManager.getLogManager().readConfiguration(
SecureServer.class.getResourceAsStream("/logging.properties"));
Config config = Config.create();
Security security = Security.builder()
.addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth")))
.build();
ServiceDescriptor greetService1 = ServiceDescriptor.builder(new GreetService(config))
.name("GreetService")
.intercept(GrpcSecurity.rolesAllowed("user"))
.intercept("SetGreeting", GrpcSecurity.rolesAllowed("admin"))
.build();
GrpcRouting grpcRouting = GrpcRouting.builder()
.intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate()))
.register(greetService1)
.register(new StringService())
.build();
GrpcServerConfiguration serverConfig = GrpcServerConfiguration.create(config.get("grpc"));
GrpcServer grpcServer = GrpcServer.create(serverConfig, grpcRouting);
grpcServer.start()
.thenAccept(s -> {
System.out.println("gRPC server is UP! http://localhost:" + s.port());
s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
})
.exceptionally(t -> {
System.err.println("Startup failed: " + t.getMessage());
t.printStackTrace(System.err);
return null;
});
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.examples.security;
import io.helidon.config.Config;
import io.helidon.grpc.examples.common.StringServiceGrpc;
import io.helidon.grpc.examples.common.Strings;
import io.helidon.security.Security;
import io.helidon.security.integration.grpc.GrpcClientSecurity;
import io.helidon.security.providers.httpauth.HttpBasicAuthProvider;
import io.grpc.CallCredentials;
import io.grpc.Channel;
import io.grpc.ManagedChannelBuilder;
/**
* A {@link io.helidon.grpc.examples.common.StringService} client that optionally
* provides {@link CallCredentials} using basic auth.
*/
public class SecureStringClient {
private SecureStringClient() {
}
/**
* Program entry point.
*
* @param args the program arguments - {@code arg[0]} is the user name
* and {@code arg[1] is the password}
*/
public static void main(String[] args) {
Channel channel = ManagedChannelBuilder.forAddress("localhost", 1408)
.usePlaintext()
.build();
// Obtain the user name and password from the program arguments
String user = args.length >= 2 ? args[0] : null;
String password = args.length >= 2 ? args[1] : null;
Config config = Config.create();
// configure Helidon security and add the basic auth provider
Security security = Security.builder()
.addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth")))
.build();
// create the gRPC client security call credentials
// setting the properties used by the basic auth provider for user name and password
GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client"))
.property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, user)
.property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, password)
.build();
// create the StringService client stub and use the GrpcClientSecurity call credentials
StringServiceGrpc.StringServiceBlockingStub stub = StringServiceGrpc.newBlockingStub(channel)
.withCallCredentials(clientSecurity);
String text = "ABCDE";
Strings.StringMessage request = Strings.StringMessage.newBuilder().setText(text).build();
Strings.StringMessage response = stub.lower(request);
System.out.println("Text '" + text + "' to lower is '" + response.getText() + "'");
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
/**
* A set of small usage examples. Start with {@link io.helidon.grpc.examples.security.SecureServer Main} class.
*/
package io.helidon.grpc.examples.security;

View File

@@ -0,0 +1,34 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
app:
greeting: "Hello"
grpc:
name: "test.server"
port: 1408
webserver:
port: 8080
http-basic-auth:
users:
- login: "Ted"
password: "secret"
roles: ["user", "admin"]
- login: "Bob"
password: "password"
roles: ["user"]

View File

@@ -0,0 +1,39 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
# Example Logging Configuration File
# For more information see $JAVA_HOME/jre/lib/logging.properties
# Send messages to the console
handlers=java.util.logging.ConsoleHandler
# Global default logging level. Can be overriden by specific handlers and loggers
.level=INFO
AUDIT.level=FINEST
# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
# It replaces "!thread!" with the current thread name
java.util.logging.ConsoleHandler.level=FINEST
java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
#Component specific log levels
#io.helidon.webserver.level=INFO
#io.helidon.config.level=INFO
#io.helidon.security.level=INFO
#io.helidon.common.level=INFO
#io.netty.level=INFO

View File

@@ -45,6 +45,7 @@
<module>todo-app</module>
<module>quickstarts</module>
<module>health</module>
<module>grpc</module>
</modules>
<build>

134
grpc/client/pom.xml Normal file
View File

@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-project</artifactId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<artifactId>helidon-grpc-client</artifactId>
<name>Helidon gRPC Client</name>
<dependencies>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.tracing</groupId>
<artifactId>helidon-tracing</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.tracing</groupId>
<artifactId>helidon-tracing-zipkin</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-grpc</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.json</groupId>
<artifactId>javax.json-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${version.plugin.os}</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-compile</goal>
<goal>test-compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<reuseForks>false</reuseForks>
<includes>
<include>**/*IT.java</include>
</includes>
</configuration>
<executions>
<execution>
<id>verify</id>
<phase>verify</phase>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.client;
/**
* An enum of possible gRPC client call attributes to attach to
* call tracing spans.
*/
public enum ClientRequestAttribute {
/**
* Add the method type to the tracing span.
*/
METHOD_TYPE,
/**
* Add the method name to the tracing span.
*/
METHOD_NAME,
/**
* Add the call deadline to the tracing span.
*/
DEADLINE,
/**
* Add the compressor type to the tracing span.
*/
COMPRESSOR,
/**
* Add the security authority to the tracing span.
*/
AUTHORITY,
/**
* Add the method call options to the tracing span.
*/
ALL_CALL_OPTIONS,
/**
* Add the method call headers to the tracing span.
*/
HEADERS
}

View File

@@ -0,0 +1,386 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.client;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Priority;
import io.helidon.grpc.core.ContextKeys;
import io.helidon.grpc.core.InterceptorPriorities;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ClientInterceptors;
import io.grpc.ForwardingClientCall;
import io.grpc.ForwardingClientCallListener;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.Status;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.contrib.grpc.ActiveSpanSource;
import io.opentracing.contrib.grpc.OperationNameConstructor;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMap;
/**
* A {@link ClientInterceptor} that captures tracing information into
* Open Tracing {@link Span}s for client calls.
*/
@Priority(InterceptorPriorities.TRACING)
public class ClientTracingInterceptor
implements ClientInterceptor {
private final Tracer tracer;
private final OperationNameConstructor operationNameConstructor;
private final boolean streaming;
private final boolean verbose;
private final Set<ClientRequestAttribute> tracedAttributes;
private final ActiveSpanSource activeSpanSource;
/**
* Private constructor called by {@link Builder}.
*
* @param tracer the Open Tracing {@link Tracer}
* @param operationNameConstructor the operation name constructor
* @param streaming flag indicating whether to trace streaming calls
* @param verbose flag to indicate verbose logging to spans
* @param tracedAttributes the set of request attributes to add to the span
* @param activeSpanSource the spurce of the active span
*/
private ClientTracingInterceptor(Tracer tracer,
OperationNameConstructor operationNameConstructor,
boolean streaming,
boolean verbose,
Set<ClientRequestAttribute> tracedAttributes,
ActiveSpanSource activeSpanSource) {
this.tracer = tracer;
this.operationNameConstructor = operationNameConstructor;
this.streaming = streaming;
this.verbose = verbose;
this.tracedAttributes = tracedAttributes;
this.activeSpanSource = activeSpanSource;
}
/**
* Use this interceptor to trace all requests made by this client channel.
*
* @param channel to be traced
* @return intercepted channel
*/
public Channel intercept(Channel channel) {
return ClientInterceptors.intercept(channel, this);
}
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
CallOptions callOptions,
Channel next) {
String operationName = operationNameConstructor.constructOperationName(method);
Span span = createSpanFromParent(activeSpanSource.getActiveSpan(), operationName);
for (ClientRequestAttribute attr : tracedAttributes) {
switch (attr) {
case ALL_CALL_OPTIONS:
span.setTag("grpc.call_options", callOptions.toString());
break;
case AUTHORITY:
if (callOptions.getAuthority() == null) {
span.setTag("grpc.authority", "null");
} else {
span.setTag("grpc.authority", callOptions.getAuthority());
}
break;
case COMPRESSOR:
if (callOptions.getCompressor() == null) {
span.setTag("grpc.compressor", "null");
} else {
span.setTag("grpc.compressor", callOptions.getCompressor());
}
break;
case DEADLINE:
if (callOptions.getDeadline() == null) {
span.setTag("grpc.deadline_millis", "null");
} else {
span.setTag("grpc.deadline_millis", callOptions.getDeadline().timeRemaining(TimeUnit.MILLISECONDS));
}
break;
case METHOD_NAME:
span.setTag("grpc.method_name", method.getFullMethodName());
break;
case METHOD_TYPE:
if (method.getType() == null) {
span.setTag("grpc.method_type", "null");
} else {
span.setTag("grpc.method_type", method.getType().toString());
}
break;
case HEADERS:
break;
default:
// should not happen, but can be ignored
}
}
return new ClientTracingListener<>(next.newCall(method, callOptions), span);
}
private Span createSpanFromParent(Span parentSpan, String operationName) {
if (parentSpan == null) {
return tracer.buildSpan(operationName).start();
} else {
return tracer.buildSpan(operationName).asChildOf(parentSpan).start();
}
}
/**
* Obtain a builder to build a {@link ClientTracingInterceptor}.
*
* @param tracer the {@link Tracer} to use
*
* @return a builder to build a {@link ClientTracingInterceptor}
*/
public static Builder builder(Tracer tracer) {
return new Builder(tracer);
}
/**
* Builds the configuration of a ClientTracingInterceptor.
*/
public static class Builder {
private final Tracer tracer;
private OperationNameConstructor operationNameConstructor;
private boolean streaming;
private boolean verbose;
private Set<ClientRequestAttribute> tracedAttributes;
private ActiveSpanSource activeSpanSource;
/**
* @param tracer to use for this intercepter
* Creates a Builder with default configuration
*/
public Builder(Tracer tracer) {
this.tracer = tracer;
operationNameConstructor = OperationNameConstructor.DEFAULT;
streaming = false;
verbose = false;
tracedAttributes = new HashSet<>();
activeSpanSource = ActiveSpanSource.GRPC_CONTEXT;
}
/**
* @param operationNameConstructor to name all spans created by this intercepter
* @return this Builder with configured operation name
*/
public ClientTracingInterceptor.Builder withOperationName(OperationNameConstructor operationNameConstructor) {
this.operationNameConstructor = operationNameConstructor;
return this;
}
/**
* Logs streaming events to client spans.
*
* @return this Builder configured to log streaming events
*/
public ClientTracingInterceptor.Builder withStreaming() {
streaming = true;
return this;
}
/**
* @param tracedAttributes to set as tags on client spans
* created by this intercepter
* @return this Builder configured to trace attributes
*/
public ClientTracingInterceptor.Builder withTracedAttributes(ClientRequestAttribute... tracedAttributes) {
this.tracedAttributes = new HashSet<>(Arrays.asList(tracedAttributes));
return this;
}
/**
* Logs all request life-cycle events to client spans.
*
* @return this Builder configured to be verbose
*/
public ClientTracingInterceptor.Builder withVerbosity() {
verbose = true;
return this;
}
/**
* @param activeSpanSource that provides a method of getting the
* active span before the client call
* @return this Builder configured to start client span as children
* of the span returned by activeSpanSource.getActiveSpan()
*/
public ClientTracingInterceptor.Builder withActiveSpanSource(ActiveSpanSource activeSpanSource) {
this.activeSpanSource = activeSpanSource;
return this;
}
/**
* @return a ClientTracingInterceptor with this Builder's configuration
*/
public ClientTracingInterceptor build() {
return new ClientTracingInterceptor(tracer,
operationNameConstructor,
streaming,
verbose,
tracedAttributes,
activeSpanSource);
}
}
/**
* A {@link ForwardingClientCall.SimpleForwardingClientCall} that adds information
* to a tracing {@link Span} at different places in the gROC call lifecycle.
*
* @param <ReqT> the gRPC request type
* @param <RespT> the gRPC response type
*/
private class ClientTracingListener<ReqT, RespT>
extends ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT> {
private final Span span;
private ClientTracingListener(ClientCall<ReqT, RespT> delegate, Span span) {
super(delegate);
this.span = span;
}
@Override
public void start(Listener<RespT> responseListener, final Metadata headers) {
if (verbose) {
span.log("Started call");
}
if (tracedAttributes.contains(ClientRequestAttribute.HEADERS)) {
// copy the headers and make sure that the AUTHORIZATION header
// is removed as we do not want auth details to appear in tracing logs
Metadata metadata = new Metadata();
metadata.merge(headers);
metadata.removeAll(ContextKeys.AUTHORIZATION);
span.setTag("grpc.headers", metadata.toString());
}
tracer.inject(span.context(), Format.Builtin.HTTP_HEADERS, new TextMap() {
@Override
public void put(String key, String value) {
Metadata.Key<String> headerKey = Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER);
headers.put(headerKey, value);
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
throw new UnsupportedOperationException(
"TextMapInjectAdapter should only be used with Tracer.inject()");
}
});
Listener<RespT> tracingResponseListener
= new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) {
@Override
public void onHeaders(Metadata headers) {
if (verbose) {
span.log(Collections.singletonMap("Response headers received", headers.toString()));
}
delegate().onHeaders(headers);
}
@Override
public void onMessage(RespT message) {
if (streaming || verbose) {
span.log("Response received");
}
delegate().onMessage(message);
}
@Override
public void onClose(Status status, Metadata trailers) {
if (verbose) {
if (status.getCode().value() == 0) {
span.log("Call closed");
} else {
String desc = String.valueOf(status.getDescription());
span.log(Collections.singletonMap("Call failed", desc));
}
}
span.finish();
delegate().onClose(status, trailers);
}
};
delegate().start(tracingResponseListener, headers);
}
@Override
public void cancel(String message, Throwable cause) {
String errorMessage;
errorMessage = message == null ? "Error" : message;
if (cause == null) {
span.log(errorMessage);
} else {
span.log(Collections.singletonMap(errorMessage, cause.getMessage()));
}
delegate().cancel(message, cause);
}
@Override
public void halfClose() {
if (streaming) {
span.log("Finished sending messages");
}
delegate().halfClose();
}
@Override
public void sendMessage(ReqT message) {
if (streaming || verbose) {
span.log("Message sent");
}
delegate().sendMessage(message);
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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 API.
*/
package io.helidon.grpc.client;

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.client;
import java.util.logging.LogManager;
import io.helidon.grpc.server.GrpcRouting;
import io.helidon.grpc.server.GrpcServer;
import io.helidon.grpc.server.GrpcServerConfiguration;
import services.EchoService;
/**
* A test gRPC server.
*/
public class TestServer {
/**
* Program entry point.
*
* @param args the program command line arguments
* @throws Exception if there is a program error
*/
public static void main(String[] args) throws Exception {
LogManager.getLogManager().readConfiguration(TestServer.class.getResourceAsStream("/logging.properties"));
// Add the EchoService and enable GrpcMetrics
GrpcRouting routing = GrpcRouting.builder()
.register(new EchoService())
.build();
// Run the server on port 0 so that it picks a free ephemeral port
GrpcServerConfiguration serverConfig = GrpcServerConfiguration.builder().build();
GrpcServer.create(serverConfig, routing)
.start()
.thenAccept(s -> {
System.out.println("gRPC server is UP and listening on localhost:" + s.port());
s.whenShutdown().thenRun(() -> System.out.println("gRPC server is DOWN. Good bye!"));
})
.exceptionally(t -> {
System.err.println("Startup failed: " + t.getMessage());
t.printStackTrace(System.err);
return null;
});
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package services;
import io.helidon.grpc.server.GrpcService;
import io.helidon.grpc.server.ServiceDescriptor;
import io.helidon.grpc.client.test.Echo;
import io.grpc.stub.StreamObserver;
/**
* A simple test gRPC echo service.
*/
public class EchoService
implements GrpcService {
@Override
public void update(ServiceDescriptor.Rules rules) {
rules.proto(Echo.getDescriptor())
.unary("Echo", this::echo);
}
/**
* Echo the message back to the caller.
*
* @param request the echo request containing the message to echo
* @param observer the call response
*/
public void echo(Echo.EchoRequest request, StreamObserver<Echo.EchoResponse> observer) {
String message = request.getMessage();
Echo.EchoResponse response = Echo.EchoResponse.newBuilder().setMessage(message).build();
complete(observer, response);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
syntax = "proto3";
option java_package = "io.helidon.grpc.client.test";
service EchoService {
rpc Echo (EchoRequest) returns (EchoResponse) {}
}
message EchoRequest {
string message = 1;
}
message EchoResponse {
string message = 1;
}

View File

@@ -0,0 +1,37 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
# Example Logging Configuration File
# For more information see $JAVA_HOME/jre/lib/logging.properties
# Send messages to the console
handlers=java.util.logging.ConsoleHandler
# Global default logging level. Can be overriden by specific handlers and loggers
.level=INFO
# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
# It replaces "!thread!" with the current thread name
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
#Component specific log levels
#io.helidon.webserver.level=INFO
#io.helidon.config.level=INFO
#io.helidon.security.level=INFO
#io.helidon.common.level=INFO
#io.netty.level=INFO

88
grpc/core/pom.xml Normal file
View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-project</artifactId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<artifactId>helidon-grpc-core</artifactId>
<name>Helidon gRPC Core</name>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-services</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.common</groupId>
<artifactId>helidon-common-http</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.json</groupId>
<artifactId>javax.json-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.core;
import io.grpc.Metadata;
/**
* A collection of common gRPC {@link io.grpc.Context.Key} instances.
*/
public final class ContextKeys {
/**
* The authorization gRPC metadata header key.
*/
public static final Metadata.Key<String> AUTHORIZATION =
Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER);
/**
* Private constructor for utility class.
*/
private ContextKeys() {
}
}

View File

@@ -0,0 +1,153 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.core;
import io.helidon.common.http.Http;
import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.StatusRuntimeException;
/**
* Helper methods for common gRPC tasks.
*/
public final class GrpcHelper {
/**
* Private constructor for utility class.
*/
private GrpcHelper() {
}
/**
* Extract the gRPC service name from a method full name.
*
* @param fullMethodName the gRPC method full name
*
* @return the service name extracted from the full name
*/
public static String extractServiceName(String fullMethodName) {
int index = fullMethodName.indexOf('/');
return index == -1 ? fullMethodName : fullMethodName.substring(0, index);
}
/**
* Extract the name prefix from from a method full name.
* <p>
* The prefix is everything upto the but not including the last
* '/' character in the full name.
*
* @param fullMethodName the gRPC method full name
*
* @return the name prefix extracted from the full name
*/
public static String extractNamePrefix(String fullMethodName) {
int index = fullMethodName.lastIndexOf('/');
return index == -1 ? fullMethodName : fullMethodName.substring(0, index);
}
/**
* Extract the gRPC method name from a method full name.
*
* @param fullMethodName the gRPC method full name
*
* @return the method name extracted from the full name
*/
public static String extractMethodName(String fullMethodName) {
int index = fullMethodName.lastIndexOf('/');
return index == -1 ? fullMethodName : fullMethodName.substring(index + 1);
}
/**
* Convert a gRPC {@link StatusException} to a {@link Http.ResponseStatus}.
*
* @param ex the gRPC {@link StatusException} to convert
*
* @return the gRPC {@link StatusException} converted to a {@link Http.ResponseStatus}
*/
public static Http.ResponseStatus toHttpResponseStatus(StatusException ex) {
return toHttpResponseStatus(ex.getStatus());
}
/**
* Convert a gRPC {@link StatusRuntimeException} to a {@link Http.ResponseStatus}.
*
* @param ex the gRPC {@link StatusRuntimeException} to convert
*
* @return the gRPC {@link StatusRuntimeException} converted to a {@link Http.ResponseStatus}
*/
public static Http.ResponseStatus toHttpResponseStatus(StatusRuntimeException ex) {
return toHttpResponseStatus(ex.getStatus());
}
/**
* Convert a gRPC {@link Status} to a {@link Http.ResponseStatus}.
*
* @param status the gRPC {@link Status} to convert
*
* @return the gRPC {@link Status} converted to a {@link Http.ResponseStatus}
*/
public static Http.ResponseStatus toHttpResponseStatus(Status status) {
Http.ResponseStatus httpStatus;
switch (status.getCode()) {
case OK:
httpStatus = Http.ResponseStatus.create(200, status.getDescription());
break;
case INVALID_ARGUMENT:
httpStatus = Http.ResponseStatus.create(400, status.getDescription());
break;
case DEADLINE_EXCEEDED:
httpStatus = Http.ResponseStatus.create(408, status.getDescription());
break;
case NOT_FOUND:
httpStatus = Http.ResponseStatus.create(404, status.getDescription());
break;
case ALREADY_EXISTS:
httpStatus = Http.ResponseStatus.create(412, status.getDescription());
break;
case PERMISSION_DENIED:
httpStatus = Http.ResponseStatus.create(403, status.getDescription());
break;
case FAILED_PRECONDITION:
httpStatus = Http.ResponseStatus.create(412, status.getDescription());
break;
case OUT_OF_RANGE:
httpStatus = Http.ResponseStatus.create(400, status.getDescription());
break;
case UNIMPLEMENTED:
httpStatus = Http.ResponseStatus.create(501, status.getDescription());
break;
case UNAVAILABLE:
httpStatus = Http.ResponseStatus.create(503, status.getDescription());
break;
case UNAUTHENTICATED:
httpStatus = Http.ResponseStatus.create(401, status.getDescription());
break;
case ABORTED:
case CANCELLED:
case DATA_LOSS:
case INTERNAL:
case RESOURCE_EXHAUSTED:
case UNKNOWN:
default:
httpStatus = Http.ResponseStatus.create(500, status.getDescription());
}
return httpStatus;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.core;
/**
* Constants that represent a priority ordering that interceptors registered with
* a gRPC service or method will be applied.
*/
public class InterceptorPriorities {
/**
* Context priority.
* <p>
* Interceptors with this priority typically <b>only</b> perform tasks
* such as adding state to the call {@link io.grpc.Context}.
*/
public static final int CONTEXT = 1000;
/**
* Tracing priority.
* <p>
* Tracing and metrics interceptors are typically applied after any context
* interceptors so that they can trace and gather metrics on the whole call
* stack of remaining interceptors.
*/
public static final int TRACING = CONTEXT + 1;
/**
* Security authentication priority.
*/
public static final int AUTHENTICATION = 2000;
/**
* Security authorization priority.
*/
public static final int AUTHORIZATION = 2000;
/**
* User-level priority.
*
* This value is also used as a default priority for application-supplied interceptors.
*/
public static final int USER = 5000;
/**
* Cannot create instances.
*/
private InterceptorPriorities() {
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.core;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import javax.inject.Named;
import javax.inject.Singleton;
import io.grpc.MethodDescriptor;
/**
* An implementation of a gRPC {@link MethodDescriptor.Marshaller} that
* uses Java serialization.
*
* @param <T> the type of value to to be marshalled
*/
@Singleton
@Named("java")
public class JavaMarshaller<T>
implements MethodDescriptor.Marshaller<T> {
@Override
public InputStream stream(T obj) {
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out)) {
oos.writeObject(obj);
return new ByteArrayInputStream(out.toByteArray());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
@SuppressWarnings("unchecked")
public T parse(InputStream in) {
try (ObjectInputStream ois = new ObjectInputStream(in)) {
return (T) ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.core;
import com.google.protobuf.MessageLite;
import io.grpc.MethodDescriptor;
import io.grpc.protobuf.lite.ProtoLiteUtils;
/**
* A supplier of {@link MethodDescriptor.Marshaller} instances for specific
* classes.
*/
@FunctionalInterface
public interface MarshallerSupplier {
/**
* Obtain a {@link MethodDescriptor.Marshaller} for a type.
*
* @param clazz the {@link Class} of the type to obtain the {@link MethodDescriptor.Marshaller} for
* @param <T> the type to be marshalled
*
* @return a {@link MethodDescriptor.Marshaller} for a type
*/
<T> MethodDescriptor.Marshaller<T> get(Class<T> clazz);
/**
* Obtain the default marshaller.
*
* @return the default marshaller
*/
static MarshallerSupplier defaultInstance() {
return new DefaultMarshallerSupplier();
}
/**
* The default {@link MarshallerSupplier}.
*/
class DefaultMarshallerSupplier
implements MarshallerSupplier {
/**
* The singleton default {@link MethodDescriptor.Marshaller}.
*/
private static final MethodDescriptor.Marshaller JAVA_MARSHALLER = new JavaMarshaller();
@Override
@SuppressWarnings("unchecked")
public <T> MethodDescriptor.Marshaller<T> get(Class<T> clazz) {
if (MessageLite.class.isAssignableFrom(clazz)) {
try {
java.lang.reflect.Method getDefaultInstance = clazz.getDeclaredMethod("getDefaultInstance");
MessageLite instance = (MessageLite) getDefaultInstance.invoke(clazz);
return (MethodDescriptor.Marshaller<T>) ProtoLiteUtils.marshaller(instance);
} catch (Exception e) {
String msg = String.format(
"Attempting to use class %s, which is not a valid Protobuf message, with a default marshaller",
clazz.getName());
throw new IllegalArgumentException(msg);
}
}
return JAVA_MARSHALLER;
}
}
}

View File

@@ -0,0 +1,205 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Stream;
import javax.annotation.Priority;
import io.helidon.common.Prioritized;
/**
* A bag of values ordered by priority.
* <p>
* An element with lower priority number is more significant than an element
* with a higher priority number.
* <p>
* For cases where priority is the same, elements are ordered in the order that
* they were added to the bag.
* <p>
* Elements added with negative priorities are assumed to have no priority and
* will be least significant in order.
*
* @param <T> the type of elements in the bag
*/
public class PriorityBag<T> implements Iterable<T> {
private final Map<Integer, List<T>> contents;
private final List<T> noPriorityList;
private final int defaultPriority;
/**
* Create a new {@link PriorityBag} where elements
* added with no priority will be last in the order.
*/
public PriorityBag() {
this(new TreeMap<>(), new ArrayList<>(), -1);
}
/**
* Create a new {@link PriorityBag} where elements
* added with no priority will be be given a default
* priority value.
*
* @param defaultPriority the default priority value to assign
* to elements added with no priority
*/
public PriorityBag(int defaultPriority) {
this(new TreeMap<>(), new ArrayList<>(), defaultPriority);
}
private PriorityBag(Map<Integer, List<T>> contents, List<T> noPriorityList, int defaultPriority) {
this.contents = contents;
this.noPriorityList = noPriorityList;
this.defaultPriority = defaultPriority;
}
/**
* Obtain a copy of this {@link PriorityBag}.
*
* @return a copy of this {@link PriorityBag}
*/
public PriorityBag<T> copyMe() {
PriorityBag<T> copy = new PriorityBag<>();
copy.merge(this);
return copy;
}
/**
* Obtain an immutable copy of this {@link PriorityBag}.
*
* @return an immutable copy of this {@link PriorityBag}
*/
public PriorityBag<T> readOnly() {
return new PriorityBag<>(Collections.unmodifiableMap(contents),
Collections.unmodifiableList(noPriorityList),
defaultPriority);
}
/**
* Merge a {@link PriorityBag} into this {@link PriorityBag}.
*
* @param bag the bag to merge
*/
public void merge(PriorityBag<? extends T> bag) {
bag.contents.forEach((priority, value) -> addAll(value, priority));
this.noPriorityList.addAll(bag.noPriorityList);
}
/**
* Add elements to the bag.
* <p>
* If the element's class is annotated with the {@link javax.annotation.Priority}
* annotation then that value will be used to determine priority otherwise the
* default priority value will be used.
*
* @param values the elements to add
*/
public void addAll(Iterable<? extends T> values) {
for (T value : values) {
add(value);
}
}
/**
* Add elements to the bag.
*
* @param values the elements to add
* @param priority the priority to assign to the elements
*/
public void addAll(Iterable<? extends T> values, int priority) {
for (T value : values) {
add(value, priority);
}
}
/**
* Add an element to the bag.
* <p>
* If the element's class is annotated with the {@link javax.annotation.Priority}
* annotation then that value will be used to determine priority otherwise the
* default priority value will be used.
*
* @param value the element to add
*/
public void add(T value) {
if (value != null) {
int priority;
if (value instanceof Prioritized) {
priority = ((Prioritized) value).priority();
} else {
Priority annotation = value.getClass().getAnnotation(Priority.class);
priority = annotation == null ? defaultPriority : annotation.value();
}
add(value, priority);
}
}
/**
* Add an element to the bag with a specific priority.
* <p>
*
* @param value the element to add
* @param priority the priority of the element
*/
public void add(T value, int priority) {
if (value != null) {
if (priority < 0) {
noPriorityList.add(value);
} else {
contents.compute(priority, (key, list) -> combine(list, value));
}
}
}
/**
* Obtain the contents of this {@link PriorityBag} as
* an ordered {@link Stream}.
*
* @return the contents of this {@link PriorityBag} as
* an ordered {@link Stream}
*/
public Stream<T> stream() {
Stream<T> stream = contents.entrySet()
.stream()
.flatMap(e -> e.getValue().stream());
return Stream.concat(stream, noPriorityList.stream());
}
@Override
public Iterator<T> iterator() {
return stream().iterator();
}
private List<T> combine(List<T> list, T value) {
if (list == null) {
list = new ArrayList<>();
}
list.add(value);
return list;
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.core;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
/**
* A {@link io.grpc.stub.StreamObserver} that handles exceptions correctly.
*
* @param <T> the type of response expected
*/
public class SafeStreamObserver<T>
implements StreamObserver<T> {
/**
* Create a {@link io.helidon.grpc.core.SafeStreamObserver} that wraps
* another {@link io.grpc.stub.StreamObserver}.
*
* @param streamObserver the {@link io.grpc.stub.StreamObserver} to wrap
*/
public SafeStreamObserver(StreamObserver<? super T> streamObserver) {
delegate = streamObserver;
}
@Override
public void onNext(T t) {
if (done) {
return;
}
if (t == null) {
onError(Status.INVALID_ARGUMENT
.withDescription("onNext called with null. Null values are generally not allowed.")
.asRuntimeException());
} else {
try {
delegate.onNext(t);
} catch (Throwable thrown) {
throwIfFatal(thrown);
onError(thrown);
}
}
}
@Override
public void onError(Throwable thrown) {
try {
if (!done) {
done = true;
delegate.onError(checkNotNull(thrown));
} else {
LOGGER.log(Level.SEVERE, checkNotNull(thrown), () -> "OnError called after StreamObserver was closed");
}
} catch (Throwable t) {
throwIfFatal(t);
LOGGER.log(Level.SEVERE, t, () -> "Caught exception handling onError");
}
}
@Override
public void onCompleted() {
if (done) {
LOGGER.log(Level.WARNING, "onComplete called after StreamObserver was closed");
} else {
try {
delegate.onCompleted();
} catch (Throwable thrown) {
throwIfFatal(thrown);
LOGGER.log(Level.SEVERE, thrown, () -> "Caught exception handling onComplete");
}
}
}
private Throwable checkNotNull(Throwable thrown) {
if (thrown == null) {
thrown = Status.INVALID_ARGUMENT
.withDescription("onError called with null Throwable. Null exceptions are generally not allowed.")
.asRuntimeException();
}
return thrown;
}
/**
* Throws a particular {@code Throwable} only if it belongs to a set of "fatal" error varieties. These varieties are
* as follows:
* <ul>
* <li>{@code VirtualMachineError}</li>
* <li>{@code ThreadDeath}</li>
* <li>{@code LinkageError}</li>
* </ul>
*
* @param thrown the {@code Throwable} to test and perhaps throw
*/
private static void throwIfFatal(Throwable thrown) {
if (thrown instanceof VirtualMachineError) {
throw (VirtualMachineError) thrown;
} else if (thrown instanceof ThreadDeath) {
throw (ThreadDeath) thrown;
} else if (thrown instanceof LinkageError) {
throw (LinkageError) thrown;
}
}
/**
* Ensure that the specified {@link StreamObserver} is a safe observer.
* <p>
* If the specified observer is not an instance of {@link SafeStreamObserver} then wrap
* it in a {@link SafeStreamObserver}.
*
* @param observer the {@link StreamObserver} to test
* @param <T> the response type expected by the observer
*
* @return a safe {@link StreamObserver}
*/
public static <T> StreamObserver<T> ensureSafeObserver(StreamObserver<T> observer) {
if (observer instanceof SafeStreamObserver) {
return observer;
}
return new SafeStreamObserver<>(observer);
}
// ----- constants ------------------------------------------------------
/**
* The {2link Logger} to use.
*/
private static final Logger LOGGER = Logger.getLogger(SafeStreamObserver.class.getName());
// ----- data members ---------------------------------------------------
/**
* The actual StreamObserver.
*/
private StreamObserver<? super T> delegate;
/**
* Indicates a terminal state.
*/
private boolean done;
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
/**
* Core classes used by both the reactive gRPC server API and gRPC client API.
*/
package io.helidon.grpc.core;

View File

@@ -0,0 +1,666 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.core;
import io.helidon.common.http.Http;
import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.StatusRuntimeException;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* {@link GrpcHelper} unit tests
*/
public class GrpcHelperTest {
@Test
public void shouldExtractServiceName() {
String fullName = "Foo/1234/Bar";
assertThat(GrpcHelper.extractServiceName(fullName), is("Foo"));
}
@Test
public void shouldExtractMethodName() {
String fullName = "Foo/1234/Bar";
assertThat(GrpcHelper.extractMethodName(fullName), is("Bar"));
}
@Test
public void shouldExtractNamePrefix() {
String fullName = "Foo/1234/Bar";
assertThat(GrpcHelper.extractNamePrefix(fullName), is("Foo/1234"));
}
@Test
public void shouldConvertAbortedStatusException() {
StatusException exception = Status.ABORTED.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Internal Server Error"));
}
@Test
public void shouldConvertAbortedStatusExceptionWithDescription() {
StatusException exception = Status.ABORTED.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertAlreadyExistsStatusException() {
StatusException exception = Status.ALREADY_EXISTS.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(412));
assertThat(status.reasonPhrase(), is("Precondition Failed"));
}
@Test
public void shouldConvertAlreadyExistsStatusExceptionWithDescription() {
StatusException exception = Status.ALREADY_EXISTS.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(412));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertOkStatusException() {
StatusException exception = Status.OK.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(200));
assertThat(status.reasonPhrase(), is("OK"));
}
@Test
public void shouldConvertOkStatusExceptionWithDescription() {
StatusException exception = Status.OK.withDescription("Good!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(200));
assertThat(status.reasonPhrase(), is("Good!"));
}
@Test
public void shouldConvertInvalidArgumentStatusException() {
StatusException exception = Status.INVALID_ARGUMENT.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(400));
assertThat(status.reasonPhrase(), is("Bad Request"));
}
@Test
public void shouldConvertInvalidArgumentStatusExceptionWithDescription() {
StatusException exception = Status.INVALID_ARGUMENT.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(400));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertDeadlineExceededStatusException() {
StatusException exception = Status.DEADLINE_EXCEEDED.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(408));
assertThat(status.reasonPhrase(), is("Request Timeout"));
}
@Test
public void shouldConvertDeadlineExceededStatusExceptionWithDescription() {
StatusException exception = Status.DEADLINE_EXCEEDED.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(408));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertNotFoundStatusException() {
StatusException exception = Status.NOT_FOUND.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(404));
assertThat(status.reasonPhrase(), is("Not Found"));
}
@Test
public void shouldConvertNotFoundStatusExceptionWithDescription() {
StatusException exception = Status.NOT_FOUND.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(404));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertPermissionDeniedStatusException() {
StatusException exception = Status.PERMISSION_DENIED.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(403));
assertThat(status.reasonPhrase(), is("Forbidden"));
}
@Test
public void shouldConvertPermissionDeniedStatusExceptionWithDescription() {
StatusException exception = Status.PERMISSION_DENIED.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(403));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertFailedPreconditionStatusException() {
StatusException exception = Status.FAILED_PRECONDITION.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(412));
assertThat(status.reasonPhrase(), is("Precondition Failed"));
}
@Test
public void shouldConvertFailedPreconditionStatusExceptionWithDescription() {
StatusException exception = Status.FAILED_PRECONDITION.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(412));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertOutOfRangeStatusException() {
StatusException exception = Status.OUT_OF_RANGE.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(400));
assertThat(status.reasonPhrase(), is("Bad Request"));
}
@Test
public void shouldConvertOutOfRangeStatusExceptionWithDescription() {
StatusException exception = Status.OUT_OF_RANGE.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(400));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertUnimplementedStatusException() {
StatusException exception = Status.UNIMPLEMENTED.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(501));
assertThat(status.reasonPhrase(), is("Not Implemented"));
}
@Test
public void shouldConvertUnimplementedStatusExceptionWithDescription() {
StatusException exception = Status.UNIMPLEMENTED.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(501));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertUnavailableStatusException() {
StatusException exception = Status.UNAVAILABLE.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(503));
assertThat(status.reasonPhrase(), is("Service Unavailable"));
}
@Test
public void shouldConvertUnavailableStatusExceptionWithDescription() {
StatusException exception = Status.UNAVAILABLE.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(503));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertUnauthenticatedStatusException() {
StatusException exception = Status.UNAUTHENTICATED.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(401));
assertThat(status.reasonPhrase(), is("Unauthorized"));
}
@Test
public void shouldConvertUnauthenticatedStatusExceptionWithDescription() {
StatusException exception = Status.UNAUTHENTICATED.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(401));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertCancelledStatusException() {
StatusException exception = Status.CANCELLED.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Internal Server Error"));
}
@Test
public void shouldConvertCancelledStatusExceptionWithDescription() {
StatusException exception = Status.CANCELLED.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertDataLossStatusException() {
StatusException exception = Status.DATA_LOSS.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Internal Server Error"));
}
@Test
public void shouldConvertDataLossStatusExceptionWithDescription() {
StatusException exception = Status.DATA_LOSS.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertInternalStatusException() {
StatusException exception = Status.INTERNAL.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Internal Server Error"));
}
@Test
public void shouldConvertInternalStatusExceptionWithDescription() {
StatusException exception = Status.INTERNAL.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertResourceExhaustedStatusException() {
StatusException exception = Status.RESOURCE_EXHAUSTED.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Internal Server Error"));
}
@Test
public void shouldConvertResourceExhaustedStatusExceptionWithDescription() {
StatusException exception = Status.RESOURCE_EXHAUSTED.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertUnknownStatusException() {
StatusException exception = Status.UNKNOWN.asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Internal Server Error"));
}
@Test
public void shouldConvertUnknownStatusExceptionWithDescription() {
StatusException exception = Status.UNKNOWN.withDescription("Oops!").asException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertAbortedStatusRuntimeException() {
StatusRuntimeException exception = Status.ABORTED.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Internal Server Error"));
}
@Test
public void shouldConvertAbortedStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.ABORTED.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertAlreadyExistsStatusRuntimeException() {
StatusRuntimeException exception = Status.ALREADY_EXISTS.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(412));
assertThat(status.reasonPhrase(), is("Precondition Failed"));
}
@Test
public void shouldConvertAlreadyExistsStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.ALREADY_EXISTS.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(412));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertOkStatusRuntimeException() {
StatusRuntimeException exception = Status.OK.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(200));
assertThat(status.reasonPhrase(), is("OK"));
}
@Test
public void shouldConvertOkStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.OK.withDescription("Good!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(200));
assertThat(status.reasonPhrase(), is("Good!"));
}
@Test
public void shouldConvertInvalidArgumentStatusRuntimeException() {
StatusRuntimeException exception = Status.INVALID_ARGUMENT.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(400));
assertThat(status.reasonPhrase(), is("Bad Request"));
}
@Test
public void shouldConvertInvalidArgumentStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.INVALID_ARGUMENT.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(400));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertDeadlineExceededStatusRuntimeException() {
StatusRuntimeException exception = Status.DEADLINE_EXCEEDED.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(408));
assertThat(status.reasonPhrase(), is("Request Timeout"));
}
@Test
public void shouldConvertDeadlineExceededStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.DEADLINE_EXCEEDED.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(408));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertNotFoundStatusRuntimeException() {
StatusRuntimeException exception = Status.NOT_FOUND.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(404));
assertThat(status.reasonPhrase(), is("Not Found"));
}
@Test
public void shouldConvertNotFoundStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.NOT_FOUND.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(404));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertPermissionDeniedStatusRuntimeException() {
StatusRuntimeException exception = Status.PERMISSION_DENIED.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(403));
assertThat(status.reasonPhrase(), is("Forbidden"));
}
@Test
public void shouldConvertPermissionDeniedStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.PERMISSION_DENIED.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(403));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertFailedPreconditionStatusRuntimeException() {
StatusRuntimeException exception = Status.FAILED_PRECONDITION.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(412));
assertThat(status.reasonPhrase(), is("Precondition Failed"));
}
@Test
public void shouldConvertFailedPreconditionStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.FAILED_PRECONDITION.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(412));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertOutOfRangeStatusRuntimeException() {
StatusRuntimeException exception = Status.OUT_OF_RANGE.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(400));
assertThat(status.reasonPhrase(), is("Bad Request"));
}
@Test
public void shouldConvertOutOfRangeStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.OUT_OF_RANGE.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(400));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertUnimplementedStatusRuntimeException() {
StatusRuntimeException exception = Status.UNIMPLEMENTED.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(501));
assertThat(status.reasonPhrase(), is("Not Implemented"));
}
@Test
public void shouldConvertUnimplementedStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.UNIMPLEMENTED.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(501));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertUnavailableStatusRuntimeException() {
StatusRuntimeException exception = Status.UNAVAILABLE.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(503));
assertThat(status.reasonPhrase(), is("Service Unavailable"));
}
@Test
public void shouldConvertUnavailableStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.UNAVAILABLE.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(503));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertUnauthenticatedStatusRuntimeException() {
StatusRuntimeException exception = Status.UNAUTHENTICATED.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(401));
assertThat(status.reasonPhrase(), is("Unauthorized"));
}
@Test
public void shouldConvertUnauthenticatedStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.UNAUTHENTICATED.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(401));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertCancelledStatusRuntimeException() {
StatusRuntimeException exception = Status.CANCELLED.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Internal Server Error"));
}
@Test
public void shouldConvertCancelledStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.CANCELLED.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertDataLossStatusRuntimeException() {
StatusRuntimeException exception = Status.DATA_LOSS.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Internal Server Error"));
}
@Test
public void shouldConvertDataLossStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.DATA_LOSS.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertInternalStatusRuntimeException() {
StatusRuntimeException exception = Status.INTERNAL.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Internal Server Error"));
}
@Test
public void shouldConvertInternalStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.INTERNAL.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertResourceExhaustedStatusRuntimeException() {
StatusRuntimeException exception = Status.RESOURCE_EXHAUSTED.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Internal Server Error"));
}
@Test
public void shouldConvertResourceExhaustedStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.RESOURCE_EXHAUSTED.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Oops!"));
}
@Test
public void shouldConvertUnknownStatusRuntimeException() {
StatusRuntimeException exception = Status.UNKNOWN.asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Internal Server Error"));
}
@Test
public void shouldConvertUnknownStatusRuntimeExceptionWithDescription() {
StatusRuntimeException exception = Status.UNKNOWN.withDescription("Oops!").asRuntimeException();
Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception);
assertThat(status.code(), is(500));
assertThat(status.reasonPhrase(), is("Oops!"));
}
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.core;
import java.util.Arrays;
import javax.annotation.Priority;
import io.helidon.common.Prioritized;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
public class PriorityBagTest {
@Test
public void shouldReturnElementsInOrder() {
PriorityBag<String> bag = new PriorityBag<>();
bag.add("Three", 3);
bag.add("Two", 2);
bag.add("One", 1);
assertThat(bag, contains("One", "Two", "Three"));
}
@Test
public void shouldReturnElementsInOrderWithinSamePriority() {
PriorityBag<String> bag = new PriorityBag<>();
bag.add("Two", 2);
bag.add("TwoToo", 2);
assertThat(bag, contains("Two", "TwoToo"));
}
@Test
public void shouldReturnNoPriorityElementsLast() {
PriorityBag<String> bag = new PriorityBag<>();
bag.add("Three", 3);
bag.add("Last");
bag.add("One", 1);
assertThat(bag, contains("One", "Three", "Last"));
}
@Test
public void shouldGetPriorityFromAnnotation() {
PriorityBag<Object> bag = new PriorityBag<>();
Value value = new Value();
bag.add("One", 1);
bag.add("Three", 3);
bag.add(value);
assertThat(bag, contains("One", value, "Three"));
}
@Test
public void shouldGetPriorityFromPrioritized() {
PriorityBag<Object> bag = new PriorityBag<>();
PrioritizedValue value = new PrioritizedValue();
bag.add("One", 1);
bag.add("Three", 3);
bag.add(value);
assertThat(bag, contains("One", value, "Three"));
}
@Test
public void shouldUsePriorityFromPrioritizedOverAnnotation() {
PriorityBag<Object> bag = new PriorityBag<>();
AnnotatedPrioritizedValue value = new AnnotatedPrioritizedValue();
bag.add("One", 1);
bag.add("Three", 3);
bag.add(value);
assertThat(bag, contains("One", value, "Three"));
}
@Test
public void shouldUseDefaultPriority() {
PriorityBag<Object> bag = new PriorityBag<>(2);
bag.add("One", 1);
bag.add("Three", 3);
bag.add("Two");
assertThat(bag, contains("One", "Two", "Three"));
}
@Test
public void shouldAddAll() {
PriorityBag<Object> bag = new PriorityBag<>();
bag.addAll(Arrays.asList("One", "Two", "Three"));
assertThat(bag, contains("One", "Two", "Three"));
}
@Test
public void shouldAddAllWithPriority() {
PriorityBag<Object> bag = new PriorityBag<>();
bag.add("First", 1);
bag.add("Last", 3);
bag.addAll(Arrays.asList("One", "Two", "Three"), 2);
assertThat(bag, contains("First", "One", "Two", "Three", "Last"));
}
@Test
public void shouldMerge() {
PriorityBag<Object> bagOne = new PriorityBag<>();
PriorityBag<Object> bagTwo = new PriorityBag<>();
bagOne.add("A", 1);
bagOne.add("B", 2);
bagOne.add("C", 2);
bagOne.add("D", 3);
bagTwo.add("E", 1);
bagTwo.add("F", 3);
bagTwo.add("G", 3);
bagTwo.add("H", 4);
bagOne.merge(bagTwo);
assertThat(bagOne, contains("A", "E", "B", "C", "D", "F", "G", "H"));
}
@Priority(2)
public static class Value {
}
public static class PrioritizedValue
implements Prioritized {
@Override
public int priority() {
return 2;
}
}
@Priority(0)
public static class AnnotatedPrioritizedValue
implements Prioritized {
@Override
public int priority() {
return 2;
}
}
}

139
grpc/metrics/pom.xml Normal file
View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-project</artifactId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<artifactId>helidon-grpc-metrics</artifactId>
<name>Helidon gRPC Metrics</name>
<dependencies>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-server</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-client</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.helidon.metrics</groupId>
<artifactId>helidon-metrics</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.json</groupId>
<artifactId>javax.json-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>javax.activation-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${version.plugin.os}</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-compile</goal>
<goal>test-compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<reuseForks>false</reuseForks>
<includes>
<include>**/*IT.java</include>
</includes>
</configuration>
<executions>
<execution>
<id>verify</id>
<phase>verify</phase>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,503 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.metrics;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.annotation.Priority;
import io.helidon.grpc.core.GrpcHelper;
import io.helidon.grpc.core.InterceptorPriorities;
import io.helidon.grpc.server.MethodDescriptor;
import io.helidon.grpc.server.ServiceDescriptor;
import io.helidon.metrics.RegistryFactory;
import io.grpc.Context;
import io.grpc.ForwardingServerCall;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.Status;
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Histogram;
import org.eclipse.microprofile.metrics.Meter;
import org.eclipse.microprofile.metrics.MetricRegistry;
import org.eclipse.microprofile.metrics.MetricType;
import org.eclipse.microprofile.metrics.Timer;
/**
* A {@link io.grpc.ServerInterceptor} that enables capturing of gRPC call metrics.
*/
@Priority(InterceptorPriorities.TRACING + 1)
public class GrpcMetrics
implements ServerInterceptor, ServiceDescriptor.Configurer, MethodDescriptor.Configurer {
/**
* The registry of vendor metrics.
*/
private static final MetricRegistry VENDOR_REGISTRY =
RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.VENDOR);
/**
* The registry of application metrics.
*/
private static final MetricRegistry APP_REGISTRY =
RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.APPLICATION);
/**
* The context key name to use to obtain rules to use when applying metrics.
*/
private static final String KEY_STRING = GrpcMetrics.class.getName();
/**
* The context key to use to obtain rules to use when applying metrics.
*/
private static final Context.Key<MetricsRules> KEY = Context.keyWithDefault(KEY_STRING, new MetricsRules(MetricType.INVALID));
/**
* The metric rules to use.
*/
private final MetricsRules metricRule;
/**
* Create a {@link GrpcMetrics}.
*
* @param rules the metric rules to use
*/
private GrpcMetrics(MetricsRules rules) {
this.metricRule = rules;
}
@Override
public void configure(MethodDescriptor.Rules rules) {
rules.addContextValue(KEY, metricRule);
}
@Override
public void configure(ServiceDescriptor.Rules rules) {
rules.addContextValue(KEY, metricRule);
}
/**
* Set the tags to apply to the metric.
*
* @param tags the tags to apply to the metric
* @return a {@link io.helidon.grpc.metrics.GrpcMetrics} interceptor
* @see org.eclipse.microprofile.metrics.Metadata
*/
public GrpcMetrics tags(Map<String, String> tags) {
return new GrpcMetrics(metricRule.tags(tags));
}
/**
* Set the description to apply to the metric.
*
* @param description the description to apply to the metric
* @return a {@link io.helidon.grpc.metrics.GrpcMetrics} interceptor
* @see org.eclipse.microprofile.metrics.Metadata
*/
public GrpcMetrics description(String description) {
return new GrpcMetrics(metricRule.description(description));
}
/**
* Set the units to apply to the metric.
*
* @param units the units to apply to the metric
* @return a {@link io.helidon.grpc.metrics.GrpcMetrics} interceptor
* @see org.eclipse.microprofile.metrics.Metadata
*/
public GrpcMetrics units(String units) {
return new GrpcMetrics(metricRule.units(units));
}
/**
* Set the {@link NamingFunction} to use to generate the metric name.
* <p>
* The default name will be the {@code <service-name>.<method-name>}.
*
* @param function the function to use to create the metric name
* @return a {@link io.helidon.grpc.metrics.GrpcMetrics} interceptor
*/
public GrpcMetrics nameFunction(NamingFunction function) {
return new GrpcMetrics(metricRule.nameFunction(function));
}
/**
* A static factory method to create a {@link GrpcMetrics} instance
* to count gRPC method calls.
*
* @return a {@link GrpcMetrics} instance to capture call counts
*/
public static GrpcMetrics counted() {
return new GrpcMetrics(new MetricsRules(MetricType.COUNTER));
}
/**
* A static factory method to create a {@link GrpcMetrics} instance
* to meter gRPC method calls.
*
* @return a {@link GrpcMetrics} instance to meter gRPC calls
*/
public static GrpcMetrics metered() {
return new GrpcMetrics(new MetricsRules(MetricType.METERED));
}
/**
* A static factory method to create a {@link GrpcMetrics} instance
* to create a histogram of gRPC method calls.
*
* @return a {@link GrpcMetrics} instance to create a histogram of gRPC method calls
*/
public static GrpcMetrics histogram() {
return new GrpcMetrics(new MetricsRules(MetricType.HISTOGRAM));
}
/**
* A static factory method to create a {@link GrpcMetrics} instance
* to time gRPC method calls.
*
* @return a {@link GrpcMetrics} instance to time gRPC method calls
*/
public static GrpcMetrics timed() {
return new GrpcMetrics(new MetricsRules(MetricType.TIMER));
}
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
MetricsRules rules = Context.keyWithDefault(KEY_STRING, metricRule).get();
MetricType type = rules.type();
String fullMethodName = call.getMethodDescriptor().getFullMethodName();
String methodName = GrpcHelper.extractMethodName(fullMethodName);
ServiceDescriptor service = ServiceDescriptor.SERVICE_DESCRIPTOR_KEY.get();
ServerCall<ReqT, RespT> serverCall;
switch (type) {
case COUNTER:
serverCall = new CountedServerCall<>(APP_REGISTRY.counter(rules.metadata(service, methodName)), call);
break;
case METERED:
serverCall = new MeteredServerCall<>(APP_REGISTRY.meter(rules.metadata(service, methodName)), call);
break;
case HISTOGRAM:
serverCall = new HistogramServerCall<>(APP_REGISTRY.histogram(rules.metadata(service, methodName)), call);
break;
case TIMER:
serverCall = new TimedServerCall<>(APP_REGISTRY.timer(rules.metadata(service, methodName)), call);
break;
case GAUGE:
case INVALID:
default:
serverCall = call;
}
serverCall = new MeteredServerCall<>(VENDOR_REGISTRY.meter("grpc.requests.meter"), serverCall);
serverCall = new CountedServerCall<>(VENDOR_REGISTRY.counter("grpc.requests.count"), serverCall);
return next.startCall(serverCall, headers);
}
/**
* A {@link io.grpc.ServerCall} that captures metrics for a gRPC call.
*
* @param <ReqT> the call request type
* @param <RespT> the call response type
* @param <MetricT> the type of metric to capture
*/
private abstract class MetricServerCall<ReqT, RespT, MetricT>
extends ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT> {
/**
* The metric to update.
*/
private final MetricT metric;
/**
* Create a {@link TimedServerCall}.
*
* @param delegate the call to time
*/
MetricServerCall(MetricT metric, ServerCall<ReqT, RespT> delegate) {
super(delegate);
this.metric = metric;
}
/**
* Obtain the metric being tracked.
*
* @return the metric being tracked
*/
protected MetricT getMetric() {
return metric;
}
}
/**
* A {@link GrpcMetrics.MeteredServerCall} that captures call times.
*
* @param <ReqT> the call request type
* @param <RespT> the call response type
*/
private class TimedServerCall<ReqT, RespT>
extends MetricServerCall<ReqT, RespT, Timer> {
/**
* The method start time.
*/
private final long startNanos;
/**
* Create a {@link TimedServerCall}.
*
* @param delegate the call to time
*/
TimedServerCall(Timer timer, ServerCall<ReqT, RespT> delegate) {
super(timer, delegate);
this.startNanos = System.nanoTime();
}
@Override
public void close(Status status, Metadata responseHeaders) {
super.close(status, responseHeaders);
long time = System.nanoTime() - startNanos;
getMetric().update(time, TimeUnit.NANOSECONDS);
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
return o != null && getClass() == o.getClass();
}
@Override
public int hashCode() {
return getClass().hashCode();
}
/**
* A {@link GrpcMetrics.MeteredServerCall} that captures call counts.
*
* @param <ReqT> the call request type
* @param <RespT> the call response type
*/
private class CountedServerCall<ReqT, RespT>
extends MetricServerCall<ReqT, RespT, Counter> {
/**
* Create a {@link CountedServerCall}.
*
* @param delegate the call to time
*/
CountedServerCall(Counter counter, ServerCall<ReqT, RespT> delegate) {
super(counter, delegate);
}
@Override
public void close(Status status, Metadata responseHeaders) {
super.close(status, responseHeaders);
getMetric().inc();
}
}
/**
* A {@link GrpcMetrics.MeteredServerCall} that meters gRPC calls.
*
* @param <ReqT> the call request type
* @param <RespT> the call response type
*/
private class MeteredServerCall<ReqT, RespT>
extends MetricServerCall<ReqT, RespT, Meter> {
/**
* Create a {@link MeteredServerCall}.
*
* @param delegate the call to time
*/
MeteredServerCall(Meter meter, ServerCall<ReqT, RespT> delegate) {
super(meter, delegate);
}
@Override
public void close(Status status, Metadata responseHeaders) {
super.close(status, responseHeaders);
getMetric().mark();
}
}
/**
* A {@link GrpcMetrics.MeteredServerCall} that creates a histogram for gRPC calls.
*
* @param <ReqT> the call request type
* @param <RespT> the call response type
*/
private class HistogramServerCall<ReqT, RespT>
extends MetricServerCall<ReqT, RespT, Histogram> {
/**
* Create a {@link HistogramServerCall}.
*
* @param delegate the call to time
*/
HistogramServerCall(Histogram histogram, ServerCall<ReqT, RespT> delegate) {
super(histogram, delegate);
}
@Override
public void close(Status status, Metadata responseHeaders) {
super.close(status, responseHeaders);
getMetric().update(1);
}
}
/**
* Implemented by classes that can create a metric name.
*/
@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);
}
/**
* An immutable holder of metrics information.
* <p>
* Calls made to mutating methods return a new instance
* of {@link MetricsRules} with the mutation applied.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
static class MetricsRules {
/**
* The metric type.
*/
private MetricType type;
/**
* The tags of the metric.
*
* @see org.eclipse.microprofile.metrics.Metadata
*/
private Optional<HashMap<String, String>> tags = Optional.empty();
/**
* The description of the metric.
*
* @see org.eclipse.microprofile.metrics.Metadata
*/
private Optional<String> description = Optional.empty();
/**
* The unit of the metric.
*
* @see org.eclipse.microprofile.metrics.Metadata
* @see org.eclipse.microprofile.metrics.MetricUnits
*/
private Optional<String> units = Optional.empty();
/**
* The function to use to obtain the metric name.
*/
private Optional<NamingFunction> nameFunction = Optional.empty();
private MetricsRules(MetricType type) {
this.type = type;
}
private MetricsRules(MetricsRules copy) {
this.type = copy.type;
this.tags = copy.tags;
this.description = copy.description;
this.units = copy.units;
this.nameFunction = copy.nameFunction;
}
/**
* Obtain the metric type.
*
* @return the metric type
*/
MetricType type() {
return type;
}
/**
* Obtain the metrics metadata.
*
* @param service the service descriptor
* @param method the method name
* @return the metrics metadata
*/
org.eclipse.microprofile.metrics.Metadata metadata(ServiceDescriptor service, String method) {
String name = nameFunction.orElse(this::defaultName).createName(service, method, type);
org.eclipse.microprofile.metrics.Metadata metadata = new org.eclipse.microprofile.metrics.Metadata(name, type);
this.tags.ifPresent(metadata::setTags);
this.description.ifPresent(metadata::setDescription);
this.units.ifPresent(metadata::setUnit);
return metadata;
}
private String defaultName(ServiceDescriptor service, String methodName, MetricType metricType) {
return (service.name() + "." + methodName).replaceAll("/", ".");
}
private MetricsRules tags(Map<String, String> tags) {
MetricsRules rules = new MetricsRules(this);
rules.tags = Optional.of(new HashMap<>(tags));
return rules;
}
private MetricsRules description(String description) {
MetricsRules rules = new MetricsRules(this);
rules.description = Optional.of(description);
return rules;
}
private MetricsRules nameFunction(NamingFunction function) {
MetricsRules rules = new MetricsRules(this);
rules.nameFunction = Optional.of(function);
return rules;
}
private MetricsRules units(String units) {
MetricsRules rules = new MetricsRules(this);
rules.units = Optional.of(units);
return rules;
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
/**
* Classes to support adding metrics to gRPC calls.
*/
package io.helidon.grpc.metrics;

View File

@@ -0,0 +1,322 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.metrics;
import java.util.Map;
import io.helidon.common.CollectionsHelper;
import io.helidon.grpc.server.GrpcService;
import io.helidon.grpc.server.MethodDescriptor;
import io.helidon.grpc.server.ServiceDescriptor;
import io.helidon.metrics.MetricsSupport;
import io.helidon.metrics.RegistryFactory;
import io.helidon.webserver.Routing;
import io.grpc.Context;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Histogram;
import org.eclipse.microprofile.metrics.Meter;
import org.eclipse.microprofile.metrics.MetricRegistry;
import org.eclipse.microprofile.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.Timer;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* A test for the {@link io.helidon.grpc.metrics.GrpcMetrics} interceptor.
* <p>
* This test runs as an integration test because it causes Helidon metrics
* to be initialised which may impact other tests that rely on metrics being
* configured a specific way.
*/
@SuppressWarnings("unchecked")
public class GrpcMetricsInterceptorIT {
private static MetricRegistry vendorRegsistry;
private static MetricRegistry appRegistry;
private static Meter vendorMeter;
private static Counter vendorCounter;
private long vendorMeterCount;
private long vendorCount;
@BeforeAll
static void configureMetrics() {
Routing.Rules rules = Routing.builder().get("metrics");
MetricsSupport.create().update(rules);
vendorRegsistry = RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.VENDOR);
appRegistry = RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.APPLICATION);
vendorMeter = vendorRegsistry.meter("grpc.requests.meter");
vendorCounter = vendorRegsistry.counter("grpc.requests.count");
}
@BeforeEach
public void setup() {
// obtain the current counts for vendor metrics so that we can assert
// the count in each test
vendorCount = vendorCounter.getCount();
vendorMeterCount = vendorMeter.getCount();
}
@Test
public void shouldUseCountedMetric() throws Exception {
ServiceDescriptor descriptor = ServiceDescriptor.builder(createMockService())
.unary("testCounted", this::dummyUnary)
.build();
MethodDescriptor methodDescriptor = descriptor.method("testCounted");
GrpcMetrics metrics = GrpcMetrics.counted();
ServerCall<String, String> call = call(metrics, methodDescriptor);
call.close(Status.OK, new Metadata());
Counter appCounter = appRegistry.counter("Foo.testCounted");
assertVendorMetrics();
assertThat(appCounter.getCount(), is(1L));
}
@Test
public void shouldUseHistogramMetric() throws Exception {
ServiceDescriptor descriptor = ServiceDescriptor.builder(createMockService())
.unary("barHistogram", this::dummyUnary)
.build();
MethodDescriptor methodDescriptor = descriptor.method("barHistogram");
GrpcMetrics metrics = GrpcMetrics.histogram();
ServerCall<String, String> call = call(metrics, methodDescriptor);
call.close(Status.OK, new Metadata());
Histogram appHistogram = appRegistry.histogram("Foo.barHistogram");
assertVendorMetrics();
assertThat(appHistogram.getCount(), is(1L));
}
@Test
public void shouldUseMeteredMetric() throws Exception {
ServiceDescriptor descriptor = ServiceDescriptor.builder(createMockService())
.unary("barMetered", this::dummyUnary)
.build();
MethodDescriptor methodDescriptor = descriptor.method("barMetered");
GrpcMetrics metrics = GrpcMetrics.metered();
ServerCall<String, String> call = call(metrics, methodDescriptor);
call.close(Status.OK, new Metadata());
Meter appMeter = appRegistry.meter("Foo.barMetered");
assertVendorMetrics();
assertThat(appMeter.getCount(), is(1L));
}
@Test
public void shouldUseTimerMetric() throws Exception {
ServiceDescriptor descriptor = ServiceDescriptor.builder(createMockService())
.unary("barTimed", this::dummyUnary)
.build();
MethodDescriptor methodDescriptor = descriptor.method("barTimed");
GrpcMetrics metrics = GrpcMetrics.timed();
ServerCall<String, String> call = call(metrics, methodDescriptor);
call.close(Status.OK, new Metadata());
Timer appTimer = appRegistry.timer("Foo.barTimed");
assertVendorMetrics();
assertThat(appTimer.getCount(), is(1L));
}
@Test
public void shouldApplyTags() throws Exception {
ServiceDescriptor descriptor = ServiceDescriptor.builder(createMockService())
.unary("barTags", this::dummyUnary)
.build();
MethodDescriptor methodDescriptor = descriptor.method("barTags");
Map<String, String> tags = CollectionsHelper.mapOf("one", "t1", "two", "t2");
GrpcMetrics metrics = GrpcMetrics.counted().tags(tags);
ServerCall<String, String> call = call(metrics, methodDescriptor);
call.close(Status.OK, new Metadata());
Counter appCounter = appRegistry.counter("Foo.barTags");
assertVendorMetrics();
assertThat(appCounter.toString(), containsString("tags='{one=t1, two=t2}'"));
}
@Test
public void shouldApplyDescription() throws Exception {
ServiceDescriptor descriptor = ServiceDescriptor.builder(createMockService())
.unary("barDesc", this::dummyUnary)
.build();
MethodDescriptor methodDescriptor = descriptor.method("barDesc");
GrpcMetrics metrics = GrpcMetrics.counted().description("foo");
ServerCall<String, String> call = call(metrics, methodDescriptor);
call.close(Status.OK, new Metadata());
Counter appCounter = appRegistry.counter("Foo.barDesc");
assertVendorMetrics();
assertThat(appCounter.toString(), containsString("description='foo'"));
}
@Test
public void shouldApplyUnits() throws Exception {
ServiceDescriptor descriptor = ServiceDescriptor.builder(createMockService())
.unary("barUnits", this::dummyUnary)
.build();
MethodDescriptor methodDescriptor = descriptor.method("barUnits");
GrpcMetrics metrics = GrpcMetrics.counted().units(MetricUnits.BITS);
ServerCall<String, String> call = call(metrics, methodDescriptor);
call.close(Status.OK, new Metadata());
Counter appCounter = appRegistry.counter("Foo.barUnits");
assertVendorMetrics();
assertThat(appCounter.toString(), containsString("unit='bits'"));
}
@Test
public void shouldHaveCorrectNameWithDotsForSlashes() throws Exception {
ServiceDescriptor descriptor = ServiceDescriptor.builder(createMockService())
.name("My/Service")
.unary("bar", this::dummyUnary)
.build();
MethodDescriptor methodDescriptor = descriptor.method("bar");
GrpcMetrics metrics = GrpcMetrics.counted();
ServerCall<String, String> call = call(metrics, descriptor, methodDescriptor);
call.close(Status.OK, new Metadata());
Counter appCounter = appRegistry.counter("My.Service.bar");
assertVendorMetrics();
assertThat(appCounter.getCount(), is(1L));
}
@Test
public void shouldUseNameFunction() throws Exception {
ServiceDescriptor descriptor = ServiceDescriptor.builder(createMockService())
.unary("barUnits", this::dummyUnary)
.build();
MethodDescriptor methodDescriptor = descriptor.method("barUnits");
GrpcMetrics metrics = GrpcMetrics.counted().nameFunction((svc, method, type) -> "overridden");
ServerCall<String, String> call = call(metrics, methodDescriptor);
call.close(Status.OK, new Metadata());
Counter appCounter = appRegistry.counter("overridden");
assertVendorMetrics();
assertThat(appCounter.getCount(), is(1L));
}
private ServerCall<String, String> call(GrpcMetrics metrics, MethodDescriptor methodDescriptor) throws Exception {
ServiceDescriptor descriptor = ServiceDescriptor.builder(createMockService()).build();
return call(metrics, descriptor, methodDescriptor);
}
private ServerCall<String, String> call(GrpcMetrics metrics,
ServiceDescriptor descriptor,
MethodDescriptor methodDescriptor) throws Exception {
Metadata headers = new Metadata();
ServerCall<String, String> call = mock(ServerCall.class);
ServerCallHandler<String, String> next = mock(ServerCallHandler.class);
ServerCall.Listener<String> listener = mock(ServerCall.Listener.class);
when(call.getMethodDescriptor()).thenReturn(methodDescriptor.descriptor());
when(next.startCall(any(ServerCall.class), any(Metadata.class))).thenReturn(listener);
Context context = Context.ROOT.withValue(ServiceDescriptor.SERVICE_DESCRIPTOR_KEY, descriptor);
ServerCall.Listener<String> result = context.call(() -> metrics.interceptCall(call, headers, next));
assertThat(result, is(sameInstance(listener)));
ArgumentCaptor<ServerCall> captor = ArgumentCaptor.forClass(ServerCall.class);
verify(next).startCall(captor.capture(), same(headers));
ServerCall<String, String> wrappedCall = captor.getValue();
assertThat(wrappedCall, is(notNullValue()));
return wrappedCall;
}
private void assertVendorMetrics() {
Meter meter = vendorRegsistry.meter("grpc.requests.meter");
Counter counter = vendorRegsistry.counter("grpc.requests.count");
assertThat(meter.getCount(), is(vendorMeterCount + 1));
assertThat(counter.getCount(), is(vendorCount + 1));
}
private GrpcService createMockService() {
GrpcService service = mock(GrpcService.class);
when(service.name()).thenReturn("Foo");
return service;
}
private void dummyUnary(String request, StreamObserver<String> observer) {
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package io.helidon.grpc.metrics;
import java.io.StringReader;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import javax.json.Json;
import javax.json.JsonStructure;
import javax.json.JsonValue;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import io.helidon.grpc.server.GrpcRouting;
import io.helidon.grpc.server.GrpcServer;
import io.helidon.grpc.server.GrpcServerConfiguration;
import io.helidon.grpc.server.test.Echo;
import io.helidon.grpc.server.test.EchoServiceGrpc;
import io.helidon.metrics.MetricsSupport;
import io.helidon.webserver.Routing;
import io.helidon.webserver.ServerConfiguration;
import io.helidon.webserver.WebServer;
import io.grpc.Channel;
import io.grpc.ManagedChannelBuilder;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.logging.LoggingFeature;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import services.EchoService;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Integration tests for gRPC server with metrics.
*/
public class MetricsIT {
// ----- data members ---------------------------------------------------
/**
* The Helidon {@link WebServer} to use for testing.
*/
private static WebServer webServer;
/**
* The JAX-RS {@link Client} to use to make http requests to the {@link WebServer}.
*/
private static Client client;
/**
* The {@link Logger} to use for logging.
*/
private static final Logger LOGGER = Logger.getLogger(MetricsIT.class.getName());
/**
* The Helidon {@link io.helidon.grpc.server.GrpcServer} being tested.
*/
private static GrpcServer grpcServer;
/**
* A gRPC {@link Channel} to connect to the test gRPC server
*/
private static Channel channel;
// ----- test lifecycle -------------------------------------------------
@BeforeAll
public static void setup() throws Exception {
LogManager.getLogManager().readConfiguration(MetricsIT.class.getResourceAsStream("/logging.properties"));
// start the server at a free port
startWebServer();
client = ClientBuilder.newBuilder()
.register(new LoggingFeature(LOGGER, Level.WARNING, LoggingFeature.Verbosity.PAYLOAD_ANY, 500))
.property(ClientProperties.FOLLOW_REDIRECTS, true)
.build();
startGrpcServer();
channel = ManagedChannelBuilder.forAddress("localhost", grpcServer.port())
.usePlaintext()
.build();
}
@AfterAll
public static void cleanup() throws Exception {
if (webServer != null) {
webServer.shutdown()
.toCompletableFuture()
.get(10, TimeUnit.SECONDS);
}
}
// ----- test methods ---------------------------------------------------
@Test
public void shouldPublishMetrics() {
// call the gRPC Echo service so that there should be some metrics
EchoServiceGrpc.newBlockingStub(channel).echo(Echo.EchoRequest.newBuilder().setMessage("foo").build());
// request the application metrics in json format from the web server
String metrics = client.target("http://localhost:" + webServer.port())
.path("metrics/application")
.request()
.accept(MediaType.APPLICATION_JSON)
.get(String.class);
// verify that the json response
JsonStructure json = Json.createReader(new StringReader(metrics)).read();
JsonValue value = json.getValue("/EchoService.Echo");
assertThat(value, is(notNullValue()));
}
// ----- helper methods -------------------------------------------------
/**
* Start the gRPC Server listening on an ephemeral port.
*
* @throws Exception in case of an error
*/
private static void startGrpcServer() throws Exception {
// Add the EchoService and enable GrpcMetrics
GrpcRouting routing = GrpcRouting.builder()
.intercept(GrpcMetrics.timed())
.register(new EchoService(), rules -> rules.intercept(GrpcMetrics.metered())
.intercept("Echo",
GrpcMetrics.counted()))
.build();
// Run the server on port 0 so that it picks a free ephemeral port
GrpcServerConfiguration serverConfig = GrpcServerConfiguration.builder().port(0).build();
grpcServer = GrpcServer.create(serverConfig, routing)
.start()
.toCompletableFuture()
.get(10, TimeUnit.SECONDS);
LOGGER.info("Started gRPC server at: localhost:" + grpcServer.port());
}
/**
* Start the Web Server listening on an ephemeral port.
*
* @throws Exception in case of an error
*/
private static void startWebServer() throws Exception {
// Add metrics to the web server routing
Routing routing = Routing.builder()
.register(MetricsSupport.create())
.build();
// Run the web server on port 0 so that it picks a free ephemeral port
ServerConfiguration webServerConfig = ServerConfiguration.builder().port(0).build();
webServer = WebServer.create(webServerConfig, routing)
.start()
.toCompletableFuture()
.get(10, TimeUnit.SECONDS);
LOGGER.info("Started web server at: http://localhost:" + webServer.port());
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* 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.
*/
package services;
import io.helidon.grpc.server.GrpcService;
import io.helidon.grpc.server.ServiceDescriptor;
import io.helidon.grpc.server.test.Echo;
import io.grpc.stub.StreamObserver;
/**
* A simple test gRPC echo service.
*/
public class EchoService
implements GrpcService {
@Override
public void update(ServiceDescriptor.Rules rules) {
rules.proto(Echo.getDescriptor())
.unary("Echo", this::echo);
}
/**
* Echo the message back to the caller.
*
* @param request the echo request containing the message to echo
* @param observer the call response
*/
public void echo(Echo.EchoRequest request, StreamObserver<Echo.EchoResponse> observer) {
String message = request.getMessage();
Echo.EchoResponse response = Echo.EchoResponse.newBuilder().setMessage(message).build();
complete(observer, response);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
syntax = "proto3";
option java_package = "io.helidon.grpc.server.test";
service EchoService {
rpc Echo (EchoRequest) returns (EchoResponse) {}
}
message EchoRequest {
string message = 1;
}
message EchoResponse {
string message = 1;
}

View File

@@ -0,0 +1,37 @@
#
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
# Example Logging Configuration File
# For more information see $JAVA_HOME/jre/lib/logging.properties
# Send messages to the console
handlers=java.util.logging.ConsoleHandler
# Global default logging level. Can be overriden by specific handlers and loggers
.level=INFO
# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
# It replaces "!thread!" with the current thread name
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.WebServerLogFormatter
java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
#Component specific log levels
#io.helidon.webserver.level=INFO
#io.helidon.config.level=INFO
#io.helidon.security.level=INFO
#io.helidon.common.level=INFO
#io.netty.level=INFO

43
grpc/pom.xml Normal file
View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>helidon-project</artifactId>
<groupId>io.helidon</groupId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-project</artifactId>
<name>Helidon gRPC Project</name>
<packaging>pom</packaging>
<modules>
<module>core</module>
<module>server</module>
<module>client</module>
<module>metrics</module>
</modules>
</project>

201
grpc/server/pom.xml Normal file
View File

@@ -0,0 +1,201 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-project</artifactId>
<version>1.0.4-SNAPSHOT</version>
</parent>
<artifactId>helidon-grpc-server</artifactId>
<name>Helidon gRPC Server</name>
<dependencies>
<dependency>
<groupId>io.helidon.grpc</groupId>
<artifactId>helidon-grpc-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.common</groupId>
<artifactId>helidon-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.health</groupId>
<artifactId>helidon-health</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.tracing</groupId>
<artifactId>helidon-tracing</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-grpc</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.json</groupId>
<artifactId>javax.json-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.health</groupId>
<artifactId>helidon-health-checks</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.tracing</groupId>
<artifactId>helidon-tracing-zipkin</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config-hocon</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config-yaml</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!--<dependency>-->
<!--<groupId>org.glassfish.jersey.core</groupId>-->
<!--<artifactId>jersey-client</artifactId>-->
<!--<scope>test</scope>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>org.glassfish.jersey.inject</groupId>-->
<!--<artifactId>jersey-hk2</artifactId>-->
<!--<scope>test</scope>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>javax.activation</groupId>-->
<!--<artifactId>javax.activation-api</artifactId>-->
<!--<scope>test</scope>-->
<!--</dependency>-->
<dependency>
<groupId>io.zipkin.zipkin2</groupId>
<artifactId>zipkin-junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.oracle.bedrock</groupId>
<artifactId>bedrock-testing-support</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${version.plugin.os}</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-compile</goal>
<goal>test-compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<reuseForks>false</reuseForks>
<includes>
<include>**/*IT.java</include>
</includes>
</configuration>
<executions>
<execution>
<id>verify</id>
<phase>verify</phase>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Some files were not shown because too many files have changed in this diff Show More