mirror of
https://github.com/jlengrand/helidon.git
synced 2026-03-10 08:21:17 +00:00
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:
committed by
Tomas Langer
parent
55f3ea5f7d
commit
baefb32d24
24
bom/pom.xml
24
bom/pom.xml
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
80
docs/src/main/docs/grpc/01_introduction.adoc
Normal file
80
docs/src/main/docs/grpc/01_introduction.adoc
Normal 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.
|
||||
69
docs/src/main/docs/grpc/02_configuration.adoc
Normal file
69
docs/src/main/docs/grpc/02_configuration.adoc
Normal 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].
|
||||
|
||||
102
docs/src/main/docs/grpc/03_routing.adoc
Normal file
102
docs/src/main/docs/grpc/03_routing.adoc
Normal 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).
|
||||
163
docs/src/main/docs/grpc/04_service_implementation.adoc
Normal file
163
docs/src/main/docs/grpc/04_service_implementation.adoc
Normal 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.
|
||||
123
docs/src/main/docs/grpc/05_interceptors.adoc
Normal file
123
docs/src/main/docs/grpc/05_interceptors.adoc
Normal 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
|
||||
116
docs/src/main/docs/grpc/06_health_checks.adoc
Normal file
116
docs/src/main/docs/grpc/06_health_checks.adoc
Normal 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`
|
||||
199
docs/src/main/docs/grpc/07_metrics.adoc
Normal file
199
docs/src/main/docs/grpc/07_metrics.adoc
Normal 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".
|
||||
172
docs/src/main/docs/grpc/08_security.adoc
Normal file
172
docs/src/main/docs/grpc/08_security.adoc
Normal 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();
|
||||
----
|
||||
36
docs/src/main/docs/grpc/09_marshalling.adoc
Normal file
36
docs/src/main/docs/grpc/09_marshalling.adoc
Normal 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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
4
examples/grpc/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
# Helidon SE gRPC Server Examples
|
||||
|
||||
|
||||
16
examples/grpc/basics/README.md
Normal file
16
examples/grpc/basics/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
# Helidon gRPC Example
|
||||
|
||||
A basic example gRPC server.
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
mvn package
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```
|
||||
mvn exec:java
|
||||
```
|
||||
67
examples/grpc/basics/pom.xml
Normal file
67
examples/grpc/basics/pom.xml
Normal 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>
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
26
examples/grpc/basics/src/main/resources/application.yaml
Normal file
26
examples/grpc/basics/src/main/resources/application.yaml
Normal 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"
|
||||
37
examples/grpc/basics/src/main/resources/logging.properties
Normal file
37
examples/grpc/basics/src/main/resources/logging.properties
Normal 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
|
||||
98
examples/grpc/common/pom.xml
Normal file
98
examples/grpc/common/pom.xml
Normal 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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
40
examples/grpc/common/src/main/proto/greet.proto
Normal file
40
examples/grpc/common/src/main/proto/greet.proto
Normal 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;
|
||||
}
|
||||
31
examples/grpc/common/src/main/proto/strings.proto
Normal file
31
examples/grpc/common/src/main/proto/strings.proto
Normal 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;
|
||||
}
|
||||
66
examples/grpc/metrics/pom.xml
Normal file
66
examples/grpc/metrics/pom.xml
Normal 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>
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
26
examples/grpc/metrics/src/main/resources/application.yaml
Normal file
26
examples/grpc/metrics/src/main/resources/application.yaml
Normal 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"
|
||||
37
examples/grpc/metrics/src/main/resources/logging.properties
Normal file
37
examples/grpc/metrics/src/main/resources/logging.properties
Normal 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
|
||||
86
examples/grpc/opentracing/README.md
Normal file
86
examples/grpc/opentracing/README.md
Normal 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!%
|
||||
```
|
||||
|
||||
|
||||
123
examples/grpc/opentracing/pom.xml
Normal file
123
examples/grpc/opentracing/pom.xml
Normal 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>
|
||||
23
examples/grpc/opentracing/src/main/docker/Dockerfile
Normal file
23
examples/grpc/opentracing/src/main/docker/Dockerfile
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
@@ -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
43
examples/grpc/pom.xml
Normal 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>
|
||||
16
examples/grpc/security-abac/README.md
Normal file
16
examples/grpc/security-abac/README.md
Normal 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
|
||||
```
|
||||
103
examples/grpc/security-abac/pom.xml
Normal file
103
examples/grpc/security-abac/pom.xml
Normal 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>
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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() + "'");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
|
||||
|
||||
@@ -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
|
||||
17
examples/grpc/security-outbound/README.md
Normal file
17
examples/grpc/security-outbound/README.md
Normal 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
|
||||
```
|
||||
95
examples/grpc/security-outbound/pom.xml
Normal file
95
examples/grpc/security-outbound/pom.xml
Normal 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>
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
16
examples/grpc/security/README.md
Normal file
16
examples/grpc/security/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
# Helidon gRPC Security Example
|
||||
|
||||
An example gRPC server using basic auth security.
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
mvn package
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```
|
||||
mvn exec:java
|
||||
```
|
||||
94
examples/grpc/security/pom.xml
Normal file
94
examples/grpc/security/pom.xml
Normal 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>
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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() + "'");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
34
examples/grpc/security/src/main/resources/application.yaml
Normal file
34
examples/grpc/security/src/main/resources/application.yaml
Normal 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"]
|
||||
39
examples/grpc/security/src/main/resources/logging.properties
Normal file
39
examples/grpc/security/src/main/resources/logging.properties
Normal 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
|
||||
@@ -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
134
grpc/client/pom.xml
Normal 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>
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
48
grpc/client/src/test/java/services/EchoService.java
Normal file
48
grpc/client/src/test/java/services/EchoService.java
Normal 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);
|
||||
}
|
||||
}
|
||||
30
grpc/client/src/test/proto/echo.proto
Normal file
30
grpc/client/src/test/proto/echo.proto
Normal 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;
|
||||
}
|
||||
37
grpc/client/src/test/resources/logging.properties
Normal file
37
grpc/client/src/test/resources/logging.properties
Normal 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
88
grpc/core/pom.xml
Normal 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>
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
153
grpc/core/src/main/java/io/helidon/grpc/core/GrpcHelper.java
Normal file
153
grpc/core/src/main/java/io/helidon/grpc/core/GrpcHelper.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
205
grpc/core/src/main/java/io/helidon/grpc/core/PriorityBag.java
Normal file
205
grpc/core/src/main/java/io/helidon/grpc/core/PriorityBag.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
666
grpc/core/src/test/java/io/helidon/grpc/core/GrpcHelperTest.java
Normal file
666
grpc/core/src/test/java/io/helidon/grpc/core/GrpcHelperTest.java
Normal 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!"));
|
||||
}
|
||||
}
|
||||
@@ -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
139
grpc/metrics/pom.xml
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
48
grpc/metrics/src/test/java/services/EchoService.java
Normal file
48
grpc/metrics/src/test/java/services/EchoService.java
Normal 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);
|
||||
}
|
||||
}
|
||||
30
grpc/metrics/src/test/proto/echo.proto
Normal file
30
grpc/metrics/src/test/proto/echo.proto
Normal 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;
|
||||
}
|
||||
37
grpc/metrics/src/test/resources/logging.properties
Normal file
37
grpc/metrics/src/test/resources/logging.properties
Normal 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
43
grpc/pom.xml
Normal 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
201
grpc/server/pom.xml
Normal 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
Reference in New Issue
Block a user