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 :HelloServer
bazel run :HelloClient
Files
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();
}
}