gRPC
February 7, 2021 · View on GitHub
Introduction
From Hunt Framework 3.4, a new component named GRPC is added, which provides a convenient way for programming with RPC.
Setup
You should have already installed the tools needed to generate client and server interface code – if you haven’t, see the here.
Defining the service
Our first step (as you’ll know from the Introduction to gRPC) is to define the gRPC service and the method request and response types using protocol buffers. For the complete .proto file, see helloworld.proto.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.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) {}
// Sends another greeting
rpc SayGoodBye (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;
}
Generating client and server code
Next we need to generate the gRPC client and server interfaces from our .proto service definition. We do this using the protocol buffer compiler protoc with a special gRPC D plugin. This is similar to what we did in the grpc-dlang.
From the protos directory, run the following command:
protoc --plugin=/usr/local/bin/protoc-gen-d --d_out=../source -I ./ helloworld.proto
protoc --plugin=protoc-gen-grpc=/usr/local/bin/grpc_dlang_plugin -I ./ --grpc_out=../source/helloworld helloworld.proto
Running this command generates the following files in the helloworld directory:
helloworld.d, which contains all the protocol buffer code to populate, serialize, and retrieve request and response message types.helloworldrpc.d, which contains the following: An interface type (or stub) for clients to call with the methods defined in theGreeterservice. An interface type for servers to implement, also with the methods defined in theGreeterservice.
Implementing Greeter
In the source directory, create a file named GreeterImpl.d:
module GreeterImpl;
import hunt.logging;
import helloworld.helloworld;
import helloworld.helloworldrpc;
import grpc;
class GreeterImpl : GreeterBase {
override Status SayHello(HelloRequest request, ref HelloReply reply) {
reply.message = "Hello " ~ request.name;
tracef("request: %s, reply: %s", request.name, reply.message);
return Status.OK;
}
override Status SayGoodBye(HelloRequest request, ref HelloReply reply) {
reply.message = "Bye " ~ request.name;
tracef("request: %s, reply: %s", request.name, reply.message);
return Status.OK;
}
}
Creating the server
Configuration
Open the configuration file application.conf for your Hunt Framework application, and add the settings.
# gRPC
grpc.server.enabled = true
grpc.server.host = 127.0.0.1
grpc.server.port = 50051
grpc.server.workerThreads = 4
Registering the service
Open the main file of the Hunt application:
import hunt.framework;
import GreeterImpl;
void main(string[] args) {
app.booting(() {
app.grpc.server.register( new GreeterImpl());
});
}
Creating the client as a channel
In this section, we’ll look at creating a D client for our Greeter service.
Configuration
In the configuration file application.conf, you can add some settings for one or more gRPC clients:
grpc.clientChannels[0].name = ch1
grpc.clientChannels[0].host = 127.0.0.1
grpc.clientChannels[0].port = 50051
grpc.clientChannels[0].timeout = 15000
grpc.clientChannels[1].name = ch2
grpc.clientChannels[1].host = 127.0.0.1
grpc.clientChannels[1].port = 50052
grpc.clientChannels[1].timeout = 15000
Then you can use the channel name to get one gRPC client.
Creating a stub and call service methods
To call service methods, we first need to create a gRPC channel to communicate with the server. Once the gRPC channel is setup, we need a client stub to perform RPCs. We get it by creating a GreeterClient instance which is provided by the helloworld package generated from the example .proto file. Here is the complete example client code.
import hunt.logging;
import grpc;
import helloworld.helloworld;
import helloworld.helloworldrpc;
class IndexController : Controller {
mixin MakeController;
@Action
string testGrpc() {
string[string] queryParameters = request.queries();
string channelName = request.queries()["channel"];
try {
Channel channel = app.grpc.getChannel(channelName);
scope(exit) {
channel.destroy();
}
GreeterClient client = new GreeterClient(channel);
HelloRequest request = new HelloRequest();
request.name = "Hunt";
HelloReply replyHello = client.SayHello(request);
tracef("++++++++++++++++%s", replyHello.message);
HelloReply replyBye = client.SayGoodBye(request);
tracef("++++++++++++++++%s", replyBye.message);
return channelName ~ " passed";
} catch(Exception ex) {
return ex.msg;
}
}
}
Try it out!
-
Build the Hunt Framework application, and run the server.
2021-Feb-07 14:57:26.399074 | 15835 | info | register.__lambda1 | gRPC server started at 0.0.0.0:50051. | ../../hunt-framework/source/hunt/framework/provider/GrpcServiceProvider.d:41 -
Visit the server in the browser or
curl.$ curl 'http://127.0.0.1:8080/grpc?channel=ch1'
You’ll see output in the server's terminal like this:
2021-Feb-07 14:57:41.59891 | 15854 | trace | GreeterImpl.SayHello | request: Hunt, reply: Hello Hunt | source/GreeterImpl.d:15
2021-02-07 14:57:41 (15892) [debug] testGrpc - ++++++++++++++++Hello Hunt - source/app/controller/IndexController.d:788
2021-Feb-07 14:57:41.6429258 | 15854 | trace | GreeterImpl.SayGoodBye | request: Hunt, reply: Bye Hunt | source/GreeterImpl.d:21
2021-02-07 14:57:41 (15892) [debug] testGrpc - ++++++++++++++++Bye Hunt - source/app/controller/IndexController.d:791