Calling a gRPC service
Table of contents
Let's assume we have the following gRPC service definition, served at http://127.0.0.1:8080/
, just like
what we used in Running a gRPC service:
syntax = "proto3";
package grpc.hello;
option java_package = "com.example.grpc.hello";
service HelloService {
rpc Hello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Making a call starts from creating a client:
import com.linecorp.armeria.client.grpc.GrpcClients;
HelloServiceBlockingStub helloService = GrpcClients.newClient(
"gproto+http://127.0.0.1:8080/",
HelloServiceBlockingStub.class); // or HelloServiceFutureStub.class or HelloServiceStub.class
HelloRequest request = HelloRequest.newBuilder().setName("Armerian World").build();
HelloReply reply = helloService.hello(request);
assert reply.getMessage().equals("Hello, Armerian World!");
Note that we added the serialization format of the call using the +
operator in the scheme part of the URI.
Because we are calling a gRPC server, we can choose: gproto
or gjson
. If you are using gRPC-Web,
you can use gproto-web
, gproto-web-text
or gjson-web
.
Since we specified HelloServiceBlockingStub.class
as the client type, Clients.newClient()
will return a
synchronous client implementation. If we specified HelloServiceFutureStub
, the calling code would have
looked like the following:
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.linecorp.armeria.client.grpc.GrpcClients;
import java.util.concurrent.ForkJoinPool;
HelloServiceFutureStub helloService = GrpcClients.newClient(
"gproto+http://127.0.0.1:8080/",
HelloServiceFutureStub.class);
HelloRequest request = HelloRequest.newBuilder().setName("Armerian World").build();
ListenableFuture<HelloReply> future = helloService.hello(request);
Futures.addCallback(future, new FutureCallback<HelloReply>() {
@Override
public void onSuccess(HelloReply result) {
assert result.getMessage().equals("Hello, Armerian World!");
}
@Override
public void onFailure(Throwable t) {
t.printStackTrace();
}
}, MoreExecutors.directExecutor());
// You can also wait until the call is finished.
HelloReply reply = future.get();
The asynchronous stub uses Guava's ListenableFuture and can be operated on using methods on Futures. The futures-extra library is very convenient for working with ListenableFuture in Java 8, including the ability to convert it to CompletableFuture.
gRPC also natively supports streaming RPC. If our service definition included this method:
service HelloService {
rpc ManyHellos (stream HelloRequest) returns (stream HelloReply) {}
}
You can also use the builder pattern for client construction:
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.grpc.GrpcSerializationFormats;
HelloServiceBlockingStub helloService =
GrpcClients.builder("http://127.0.0.1:8080/")
.serializationFormat(GrpcSerializationFormats.PROTO)
.responseTimeoutMillis(10000)
.decorator(LoggingClient.newDecorator())
.build(HelloServiceBlockingStub.class); // or HelloServiceFutureStub.class
// or HelloServiceStub.class
HelloRequest request = HelloRequest.newBuilder().setName("Armerian World").build();
HelloReply reply = helloService.hello(request);
assert reply.getMessage().equals("Hello, Armerian World!");
As you might have noticed already, we decorated the client using LoggingClient
, which logs all
requests and responses. You might be interested in decorating a client using other decorators, for example
to gather metrics. Please also refer to ClientBuilder
for more configuration options.
Exception propagation
If you have enabled Flags.verboseResponses()
in the server being accessed by specifying
-Dcom.linecorp.armeria.verboseResponses=true
system property, then any exception during processing
in the server will be returned to the client as a StatusCauseException
attached to the normal gRPC
Status
. This can be used for programmatic access to the exception that happened in the server. In this
example, the server always fails with throw new IllegalStateException("Failed!");
import com.linecorp.armeria.client.grpc.GrpcClients;
import com.linecorp.armeria.common.grpc.StatusCauseException;
import io.grpc.StatusRuntimeException;
HelloServiceBlockingStub helloService = GrpcClients.newClient(
"gproto+http://127.0.0.1:8080/",
HelloServiceBlockingStub.class); // or HelloServiceFutureStub.class or HelloServiceStub.class
HelloRequest request = HelloRequest.newBuilder().setName("Armerian World").build();
try {
HelloReply reply = helloService.hello(request);
} catch (StatusRuntimeException e) {
if (e.getCause() instanceof StatusCauseException) {
StatusCauseException cause = (StatusCauseException) e.getCause();
// The name of the class of the exception and its message in the server can be accessed.
assert cause.getOriginalClassName().equals("java.lang.IllegalStateException");
assert cause.getOriginalMessage().equals("Failed!");
// The exception's message is a combination of both the class name and original message.
assert cause.getMessage().equals("java.lang.IllegalStateException: Failed!");
// The exception's stack trace is that which occurred when the server threw the exception.
cause.printStackTrace();
// Logging frameworks, as used by e.g., LoggingClient, will print the stack trace if configured
// to do so.
// Now you know exactly where to look in the server to figure out what may have gone wrong.
}
}