gRPC
To round off the list of useful Google technologies for now, let’s look at gRPC. Building on top of Protocol Buffers, it enables a language agnostic RPC definition. Similar to protobufs, auto-generated source code for a long list of compatible languages can easily be integrated. This includes the source for both the client and the server side. The communication is by protobuf objects, over HTTP.
Proto Service
Like protobufs, the RPC service API is defined in a .proto file.
Notice the service and rpc keywords. Within a service, there can be one or more rpc methods. The method declaration follows the pattern “rpc MethodName (InputType) returns (ReturnType) {}”. However, as opposed to many languages, there can only be one input and one return object, which means that it is common to have dedicated Request and Response types with nested fields.
Finally, the import statement includes the proto we looked in the previous example. The Person message can then be used in the fields (or grpc) of this proto file.
import "_includes/src/com/rememberjava/protobuf/person.proto";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
Person from = 1;
Person to = 2;
string message = 3;
}
message HelloReply {
string reply = 1;
}Bazel
To compile the gRPC service code, we need an additional Bazel rule. Now, gRPC compile rules have changed frequently over the years, often with incompatible versions and upgrades. For now, we’re using the java_grpc_library rule from rules_proto_grpc_java. This rule will also compile the imported person.proto.
Notice the protos field, similar to srcs in other rules. It takes the proto_library dependencies of all protos and dependencies to compile.
load("@rules_proto_grpc_java//:defs.bzl", "java_grpc_library")
proto_library(
name = "hello_proto",
srcs = ["hello.proto"],
deps = [
"//_includes/src/com/rememberjava/protobuf:person_proto",
],
)
java_grpc_library(
name = "hello_java_grpc",
protos = [
":hello_proto",
"//_includes/src/com/rememberjava/protobuf:person_proto",
],
)Finally, there are two java_binary rules for the server and client which we’ll look at next.
java_binary(
name = "HelloServer",
srcs = ["HelloServer.java"],
deps = [":hello_java_grpc"],
)
java_binary(
name = "HelloClient",
srcs = ["HelloClient.java"],
deps = [":hello_java_grpc"],
)Server
This examples includes a very minimal server class, which combines the main, start and RPC methods. Typically, these would be split over multiple classes and files.
First, the sayHello RPC method, which takes the protobufs HelloRequest input and HelloReply output as parameters. Within the methods, the usual protobuf generated API methods apply.
@Override
public void sayHello(HelloRequest request,
StreamObserver<HelloReply> responseObserver) {
String from = request.getFrom().getFirstname();
String to = request.getTo().getFirstname();
System.out.println("Server: " + from + " says hello to " + to);
HelloReply response = HelloReply
.newBuilder()
.setReply(to + " sends greetings.")
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}To start and let the server run, a io.grpc.Server objected is constructed our GreeterGrpc as the Service. This runs in blocking mode.
void start() throws IOException, InterruptedException {
int port = 1234;
System.out.println("Starting gRPC HelloServer on port " + port);
ServerBuilder<?> serverBuilder = Grpc.newServerBuilderForPort(
port, InsecureServerCredentials.create());
Server server = serverBuilder.addService(this).build();
server.start();
server.awaitTermination();
}Client
Finally, we fire up a minimal gRPC client to make a request on the server. It news a channel, a stub (which has the service API) and a request. After the call, the reply from the server is printed and we shutdown and exit.
void hello() {
int port = 1234;
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", port)
.usePlaintext().build();
GreeterGrpc.GreeterBlockingStub stub =
GreeterGrpc.newBlockingStub(channel);
HelloRequest request = HelloRequest.newBuilder()
.setFrom(Person.newBuilder()
.setFirstname("Foo").build())
.setTo(Person.newBuilder()
.setFirstname("Bar").build())
.build();
HelloReply reply = stub.sayHello(request);
System.out.println("Reply on client: " + reply.getReply());
channel.shutdown();
}Run it
To run the server and client, use two separate terminal windows and call, in order:
bazel run :HelloServerbazel run :HelloClientFiles
Here are files includes in this example:
syntax = "proto3";
import "_includes/src/com/rememberjava/protobuf/person.proto";
option java_package = "com.rememberjava.grpc";
option java_multiple_files = true;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
Person from = 1;
Person to = 2;
string message = 3;
}
message HelloReply {
string reply = 1;
}syntax = "proto3";
option java_package = "com.rememberjava.protobuf";
option java_multiple_files = true;
message Person {
string firstname = 1;
string lastname = 2;
int32 id = 3;
string email = 4;
}load("@rules_proto_grpc_java//:defs.bzl", "java_grpc_library")
proto_library(
name = "hello_proto",
srcs = ["hello.proto"],
deps = [
"//_includes/src/com/rememberjava/protobuf:person_proto",
],
)
java_grpc_library(
name = "hello_java_grpc",
protos = [
":hello_proto",
"//_includes/src/com/rememberjava/protobuf:person_proto",
],
)
java_binary(
name = "HelloServer",
srcs = ["HelloServer.java"],
deps = [":hello_java_grpc"],
)
java_binary(
name = "HelloClient",
srcs = ["HelloClient.java"],
deps = [":hello_java_grpc"],
)package com.rememberjava.grpc;
import java.io.IOException;
import io.grpc.Grpc;
import io.grpc.InsecureServerCredentials;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import com.rememberjava.grpc.GreeterGrpc;
import com.rememberjava.grpc.HelloReply;
import com.rememberjava.grpc.HelloRequest;
class HelloServer extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest request,
StreamObserver<HelloReply> responseObserver) {
String from = request.getFrom().getFirstname();
String to = request.getTo().getFirstname();
System.out.println("Server: " + from + " says hello to " + to);
HelloReply response = HelloReply
.newBuilder()
.setReply(to + " sends greetings.")
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
void start() throws IOException, InterruptedException {
int port = 1234;
System.out.println("Starting gRPC HelloServer on port " + port);
ServerBuilder<?> serverBuilder = Grpc.newServerBuilderForPort(
port, InsecureServerCredentials.create());
Server server = serverBuilder.addService(this).build();
server.start();
server.awaitTermination();
}
public static void main(String[] args) throws IOException, InterruptedException {
new HelloServer().start();
}
}package com.rememberjava.grpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import com.rememberjava.protobuf.Person;
import com.rememberjava.grpc.GreeterGrpc;
import com.rememberjava.grpc.HelloReply;
import com.rememberjava.grpc.HelloRequest;
class HelloClient {
void hello() {
int port = 1234;
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", port)
.usePlaintext().build();
GreeterGrpc.GreeterBlockingStub stub =
GreeterGrpc.newBlockingStub(channel);
HelloRequest request = HelloRequest.newBuilder()
.setFrom(Person.newBuilder()
.setFirstname("Foo").build())
.setTo(Person.newBuilder()
.setFirstname("Bar").build())
.build();
HelloReply reply = stub.sayHello(request);
System.out.println("Reply on client: " + reply.getReply());
channel.shutdown();
}
public static void main(String[] args) {
new HelloClient().hello();
}
}