Google Remote Procedure Call, or gRPC, is an open source Remote Procedure Call (RPC) framework focused on high performance and portable to any environment. It is an alternative to REST, deemed by many as more efficient.
After one of our customers requested it, the extension became available in our April 2022 Payara Community and Enterprise releases: 5.2022.2 and 5.38.0.
After providing a brief overview of the gRPC extension in Payara in ourApril release blog, here we provide more technical detail about how to use it in Payara Community, where you will build the module yourself and place it in the modules directory.
How Does gRPC Work?
gRPC is based on the idea of defining a service by specifying the contract interface. The server implements this interface and runs a gRPC server to handle client requests. The client has a stub which has the same methods as the server on its side.
gRPC was designed for HTTP/2, which provides important performance benefits over HTTP 1.x:
It uses binary compression and framing. HTTP/2 is more compact and has improved efficiency both sending and receiving.
It implements multiple HTTP/2 calls over a single TCP connection.
Protocol Buffers (Protbuf) are used by default to serialize the messages. Protbuf is the Interface Definition Language (IDL) to define the payload messages structure and the service interface. The serialization is very quick on both server and client side. It generates small payload messages which are suitable for limited bandwidth devices like mobile phones. The picture below shows the communication between a gRPC server implemented in Java and two different clients: a C++ Client and an Android App.
gRPC Support in Payara
Payara has developed a module to support gRPC. It is avaliable in Payara Community GitHub repository at: https://github.com/payara/gRPC. The user can clone and build the project using Maven or just download the JAR file from: gRPC Support JAR.
The user can manually copy this file to Payara modules:
After restarting Payara Server, the user should run the following commads to make sure HTTP/2 and HTTP Push are activated:
./asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.http2-push-enabled=true ./asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.http2-enabled=true
We use Protbuf to define the gRPC Service with its types for request and response methods. The complete file can be found in route_guide.proto.
The first step is to define the service name in .proto file:
service RouteGuide {}
Now we can define some rpc methods into the service definition with their types for request and response. Our service includes four methods that encompass the four possible kinds:
GetFeature returns the Feature at a given Point. It acts like a simple function call done by the client as a request to the server through the client Stub.
ListFeatures represents a type where the client sends a request and gets a stream. It uses the stream returned to read a list of messages sequentially.
RecordRoute is of type: client side stream. The client writes a list of messages in a stream that is part of the payload request. After this it waits for the server to read all messages and send a response.
RouteChat is a bidirectional streaming RPC. Both client and server write messages in its streams. The detail here is that these streams are independent from each other. It means there are no rules that amount to: ‘wait for all messages come before sending messages’. It is up to each side to manage simultaneous message streams.
We can also see some message types defined in our .proto file. For instance, see the definition of a Rectangle message:
message Rectangle { // One corner of the rectangle. Point lo = 1; // The other corner of the rectangle. Point hi = 2; }
At this point, we have the service defined. Then we can create the Stubs and work on Server and Client creations.
We created a module in our gRPC example called grpc-stubs, as shown in picture below. The .proto file was copied into proto folder. /
In grpc-stubs->pom.xml we included the gRPC dependencies and protobuf-maven-plugin that can generate the code during the Maven build. If the user clones Payara-Examples project, then he just need to run the following Maven command from grpc-stubs directory:
The results can be found in target->generated-sources. The main files generated by proto-plugin are:
RouteGuideGRpc.java which comprises:
stub classes used by clients to communicate with a server.
a RouteGuideGrpc.RouteGuideImplBase abstract class to be implemented by Servers and have the methods defined in RouteGuide proto service.
Rectangle.java, Point.java and Feature.java. There are also other classes that implement a protocol buffer to manipulate request and response message types.
Server Creation
The RouteGuide server is implemented in our example by classRouteGuideService. It overrides the methods defined in RouteGuideGrpc.RouteGuideImplBase giving the actual behavior to the service.
In the official gRPC example, specific methods are included for running the gRPC Server and responding to requests from clients. These methods are not necessary in the Payara example since we created a web project to be deployed in Payara Server. Therefore, we will now look into our RouteGuide implementation.
Route Guide Implementation
First of all, our class implements the abstract base class:
@ApplicationScoped public classRouteGuideService extendsRouteGuideGrpc.RouteGuideImplBase { ... }
Inside the class we have the implementation of ALL service methods. For instance, see getFeature method which receives a Point and a StreamObserver from client. Then it finds the feature in the local database and sends it to the observer:
We also have the implementation for other three types of RCP calls: Streaming in Server-Side, Streaming in Client-Side and Bidirectional Streaming.
Streaming in Server-Side
The method listFeaturesis used to send back multiple Features to the client.
For each Feature in the database that is inside the Rectangle in the payload, the method sends the Feature to the client Observer. When the loop finishes, it calls method onCompleted to tell the Observer that all messages were sent.
intlat = feature.getLocation().getLatitude(); intlon = feature.getLocation().getLongitude(); if(lon >= left && lon <= right && lat >= bottom && lat <= top) { responseObserver.onNext(feature); } } responseObserver.onCompleted(); }
Streaming in Client-Side
The next method we will get into is recordRoute. This method receives a stream of Points from client and returns through the StreamObserver a RouteSummary.
@Override publicStreamObserver<Point> recordRoute(finalStreamObserver<RouteSummary> responseObserver) { return new StreamObserver<Point>() { ...
Inside the method, the example implements interface StreamObserver anonymously by overriding the methods:
onNext: called everytime client writes a Point into stream message.
onCompleted: called when client finishes writing into stream message. Used to build the RouteSummary and call onComplete over the responseObserver to send the results to the client.
Bidirectional Streaming
To finish the server creation we examine the bidirectional method routeChat:
Here we also receive and return a stream as in previous method. The main difference is: the two streams are completely independent. Server and client can write and read in any order.
Client Creation
We created our client into the same module: grpc-web, but it belongs to the test source folder. Therefore, we encapulated the grpc client into our tests. As we have a dependency to project grpc-stubs in grpc-web->pom.xml:
return; } if(routeGuideUtil.exists(feature)) { LogHelper.info(clientPrefix+"Found feature called "{0}" at {1}, {2}", feature.getName(), routeGuideUtil.getLatitude(feature.getLocation()), routeGuideUtil.getLongitude(feature.getLocation())); } else{ LogHelper.info(clientPrefix+"Found no feature at {0}, {1}", routeGuideUtil.getLatitude(feature.getLocation()), routeGuideUtil.getLongitude(feature.getLocation())); } }
It acts like calling a local method. The results after the call: getFeature(409146138, -746188906)
INFO: [] *** GetFeature: lat=409,146,138 lon=-746,188,906 Apr 18, 2022 8:26:39 AM fish.payara.example.grpc.LogHelper info INFO: [] Found feature called "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" at 40.915, -74.619
And after second call: getFeature(0, 0)
INFO: [] *** GetFeature: lat=0 lon=0 Apr 18, 2022 8:26:39 AM fish.payara.example.grpc.LogHelper info INFO: [] Found no feature at 0, 0
Server-side Streaming Call
Now our client calls listFeatures method:
// Looking for features between 40, -75 and 42, -73. client.listFeatures(400000000, -750000000, 420000000, -730000000);
This time blockingStub will not return just a Feature, but an Iterator which is used to access all Features sent by the server.
INFO: [] Result #2: name: "101 New Jersey 10, Whippany, NJ 07981, USA" location { latitude: 408122808 longitude: -743999179 }
...
INFO: [] Result #64: name: "3 Hasta Way, Newton, NJ 07860, USA" location { latitude: 410248224 longitude: -747127767 }
Client-side Streaming Call
Next we will test the call to method recordRoute passing features list and the number of points we want to send.
// Record a few randomly selected points from the features file. client.recordRoute(features, 10);
StreamObserver<Point> requestObserver = asyncStub.recordRoute(responseObserver); try{ for(inti = 0; i < numPoints; ++i) { int index = random.nextInt(features.size()); Point point = features.get(index).getLocation(); LogHelper.info(clientPrefix+"Visiting point {0}, {1}", routeGuideUtil.getLatitude(point), routeGuideUtil.getLongitude(point)); requestObserver.onNext(point); Thread.sleep(random.nextInt(1000) + 500); if(finishLatch.getCount() == 0) { // RPC completed or errored before we finished sending. // Sending further requests won't error, but they will just be thrown away. return; } } } catch(RuntimeException e) { requestObserver.onError(e); throwe; } requestObserver.onCompleted();
Above we see part of recordRoute implementation in RouteGuideClient. This time we use asyncStub in recordRoute implementation to send ten random points asynchronously.
In order to print out the RouteSummary written by the server, we override onNext method:
@Override publicvoidonNext(RouteSummary summary) { LogHelper.info(clientPrefix+"Finished trip with {0} points. Passed {1} features. " + "Travelled {2} meters. It took {3} seconds.", summary.getPointCount(), summary.getFeatureCount(), summary.getDistance(), summary.getElapsedTime());
}
We also override onCompleted method to reduce CountDownLatch to zero when the server finishes writing:
Method asyncStub.routeChat also receives and returns a StreamObserver as in asyncStub.recordRoute method. Although this time the client sends messages to the stream at the same time that the server writes messages into the other stream and these streams are completely independent from each other.
To run the tests in client side, we used the following configuration:
Operating System: Ubuntu 20.04 LTS
Maven: v3.8.4
Java: OpenJDK Zulu8 v1.8.0_322
Payara Server: v5.2022.2
Summary
We hoped you found this helpful as a way to get started with gRPC and Payara Community. We have also added information on gRPC to our documentation and it is available here.
However, by using Payara Enterprise, you can access gRPC functionality even more easily. The iteration of Payara Platform designed for mission critical applications, Payara Enterprise now has a compiled gRPC module ready that customers can download, then place in the modules directory. Request Payara Enterprise here.
By adding the modern framework gRPC to Payara Server, we continue to expand your choices when using Jakarta EE APIs. gRPC is totally optional, and having it available as an extension means you can pick and choose when it is used.
Please feel free to provide your feedback and tips as you get to grips with this exciting new feature, by posting on the Payara Forum.