grpc and WildFly - Part I
We are pleased to announce the first release of support for gRPC services in WildFly[1]. gRPC, a Google project, is, as its name suggests, a Remote Procedure Call framework. In some ways, it is a competitor to Jakarta RESTFul Web Services, but it has different semantics and a different performance profile.
gRPC rests on another Google project, protobuf, which is, according to the website, "a language-neutral, platform-neutral extensible mechanism for serializing structured data." It consists of 1) a programming language independent data definition language and 2) a compressed wire protocol for transmitting data. Moreover, protobuf data is transported over HTTP/2, and taken together, gRPC is well positioned for speedy transmission. On the other hand, the most common data format in the Jakarta REST world is the more readable but more verbose JSON, and there is evidence (Evaluating Performance of REST vs. gRPC) that gRPC transmission can be considerably faster than Jakarta REST transmission. Of course, your mileage may vary.
Given that this is a WildFly blog, and given that RESTEasy, as an implementation of Jakarta RESTful Web Services, is a foundational technology in WildFly, we will assume that the reader is familiar with Jakarta REST but not necessarily familiar with gRPC, and so we’ll take a minute to introduce the latter.
What is gRPC?
gRPC is a modern iteration in a long history of RPC frameworks, a notable example of which is CORBA. In particular, as one smart guy said[2], "gRPC is to CORBA what REST is to SOAP", which should be a relief to anyone proposing to learn gRPC.
A gRPC application begins with a language neutral description of data types and procedure calls. Consider, for example, the following, which comes from the helloworld example in the wildfly-grpc-feature-pack project (https://github.com/wildfly-extras/wildfly-grpc-feature-pack):
syntax = "proto3";
option java_multiple_files = true;
option java_package = "org.wildfly.extension.grpc.example.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
Here, HelloRequest
and HelloReply
are message types, and SayHello
is a procedure call that sends the former and receives the latter. This
file can be compiled into any number of programming languages, including
C++, Python, and, of course, Java. Given that the configuration
parameter java_multiple_files
is set to true
, each of HelloRequest
and HelloResponse
is compiled to a somewhat opaque Java class with
roughly 600 lines, not really meant for human consumption.
An additional class generated by the gRPC compiler, GreeterGrpc
, has
the client and server side infrastructure. For the client there is
GreeterBlockingStub
, which allows the client to make calls to the
server, something like this:
ManagedChannelchannel = ManagedChannelBuilder
.forTarget("localhost:9555")
.usePlaintext()
.build();
GreeterGrpc.GreeterBlockingStub blockingStub = GreeterGrpc.newBlockingStub(channel);
HelloRequest request = HelloRequest.newBuilder().setName("Bill").build();
HelloReply response = blockingStub.sayHello(request);
System.out.println(response.getMessage());
For the server side, there is the inner class
GreeterGrpc.GreeterImplBase
, which, for each procedure call, has a
method that throws an exception. Implementing the service, then, is a
matter of overriding each such method with real content, as in
GreeterServiceImpl
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
String name = request.getName();
String message = "Hello " + name;
responseObserver.onNext(HelloReply.newBuilder().setMessage(message).build());
responseObserver.onCompleted();
}
By virtue of implementing io.grpc.BindableService
,
GreeterServiceImpl
is a gRPC service, and it is installed as follows:
io.grpc.Server server = ServerBuilder.forPort(9555)
.addService(new GreeterServiceImpl())
.build()
.start();
gRPC in WildFly
The previous snippet can be used to set up a free standing gRPC server, but we’re here to talk about the new gRPC subsystem in WildFly. It is packaged in the form of a galleon feature pack, and it can be installed in WildFly as follows
galleon.sh install wildfly:current --dir=wildfly
galleon.sh install org.wildfly.extras.grpc:wildfly-grpc-feature-pack:0.1.0.Final --layers=grpc --dir=wildfly
An instance of WildFly with the grpc feature pack will recognize any
deployment with one or more instances of io.grpc.BindableService
, will
install them all, and will start listening on a port which defaults to
9555.
The grpc subsystem has over 20 configurable parameters which can be discovered by way of the jboss-cli interface:
[standalone@localhost:9990 /] cd subsystem=grpc
[standalone@localhost:9990 subsystem=grpc] ls
flow-control-window=undefined permit-keep-alive-time=undefined
handshake-timeout=undefined permit-keep-alive-without-calls=undefined
initial-flow-control-window=undefined protocol-provider=undefined
keep-alive-time=undefined server-host=localhost
keep-alive-timeout=undefined server-port=9555
key-manager-name=key-manager-afcdd1f8-d1a7-4137-aa13-c45237e32428 session-cache-size=undefined
max-concurrent-calls-per-connection=undefined session-timeout=undefined
max-connection-age=undefined shutdown-timeout=3
max-connection-age-grace=undefined ssl-context-name=undefined
max-connection-idle=undefined start-tls=undefined
max-inbound-message-size=undefined trust-manager-name=key-manager-trust-store-eeeecd12-36f9-4156-92c7-a889383f17a1
max-inbound-metadata-size=undefined
Most of these are technical parameters used by
io.grpc.netty.NettyServerBuilder
. Some of the more prominent
parameters are server-port
, key-manager-name
, and
trust-manager-name
.
The latter two parameters are used to configure the SSL properties of the connections between the gRPC clients and the server.
gRPC over SSL connections
SSL connections are configured by way of the elytron subsystem. First,
note that if you want a plaintext connection, the key-manager-name
property should be set to null. Otherwise, consider the following
fragment from a standalone.xml file, which is configured for identities
to be verified on both the server and the client:
<subsystem xmlns="urn:wildfly:elytron:17.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
...
<tls>
<key-stores>
...
<key-store name="key-store-afcdd1f8-d1a7-4137-aa13-c45237e32428">
<credential-reference clear-text="secret"/>
<implementation type="JKS"/>
<file required="false" path="server.keystore.jks" relative-to="jboss.server.config.dir"/>
</key-store>
<key-store name="trust-store-eeeecd12-36f9-4156-92c7-a889383f17a1">
<credential-reference clear-text="secret"/>
<implementation type="JKS"/>
<file required="false" path="server.truststore.jks" relative-to="jboss.server.config.dir"/>
</key-store>
</key-stores>
<key-managers>
...
<key-manager name="key-manager-afcdd1f8-d1a7-4137-aa13-c45237e32428" key-store="key-store-afcdd1f8-d1a7-4137-aa13-c45237e32428">
<credential-reference clear-text="secret"/>
</key-manager>
</key-managers>
<trust-managers>
<trust-manager name="key-manager-trust-store-eeeecd12-36f9-4156-92c7-a889383f17a1" key-store="trust-store-eeeecd12-36f9-4156-92c7-a889383f17a1"/>
</trust-managers>
</tls>
</subsystem>
<subsystem xmlns="urn:wildfly:grpc:1.0" key-manager-name="key-manager-afcdd1f8-d1a7-4137-aa13-c45237e32428" trust-manager-name="key-manager-trust-store-eeeecd12-36f9-4156-92c7-a889383f17a1"/>
Note that the grpc parameter key-manager-name
is set to
"key-manager-afcdd1f8-d1a7-4137-aa13-c45237e32428", which refers to a
key-manager configured in elytron. That key-manager refers to a keystore
named "key-store-afcdd1f8-d1a7-4137-aa13-c45237e32428, which refers
to file "server.keystore.jks" in the standalone/configuration
directory (the value of "jboss.server.config.dir"). So,
"server.keystore.jks" should be there.
Next, note that the grpc parameter trust-manager-name
is set to
"key-manager-trust-store-eeeecd12-36f9-4156-92c7-a889383f17a1", which
is the name of a trust-manager that refers to keystore
"trust-store-eeeecd12-36f9-4156-92c7-a889383f17a1", which refers to
file "server.truststore.jks" in standalone/configuration. Again, that
file should be present.
So, there is a keystore and a truststore on the server, and there must be a matching truststore and keystore on the client. Those can be used as follows by the client:
ClassLoader classLoader = GreeterClient.class.getClassLoader();
InputStream trustStore = classLoader.getResourceAsStream("client.truststore.pem");
InputStream keyStore = classLoader.getResourceAsStream("client.keystore.pem");
InputStream key = classLoader.getResourceAsStream("client.key.pem");
ChannelCredentials creds = TlsChannelCredentials
.newBuilder()
.trustManager(trustStore)
.keyManager(keyStore, key)
.build();
ManagedChannel channel = Grpc.newChannelBuilderForAddress("localhost", 9555, creds).build();
GreeterClient client = new GreeterClient(channel);
client.greet("world");
A more common scenario would be where only the server is required to present credentials to the client, in which case the grpc subsystem would need just a key-manager-name, associated with a keystore, and trust-manager-name is null.
Downloading
The wildfly-grpc-feature-pack jar can be downloaded from
The source code for the subsystem and examples is found here:
A more detailed discussion can be found here: