Details
Client Lifecycle
Instances of the generated client may be long-lived and can be used concurrently. You can keep the client running until your system terminates, or close it earlier. To avoid leaking in the latter case, you should call .close()
on the client.
When the connection breaks, the client will try reconnecting to the server automatically. On each reconnection attempt, If a connection the ServiceDiscovery
will be used and a new host may be found.
When using client-side load balancing the reconnection loop will run indefinitely.
When using a direct client (not load balanced) when the connection breaks you can set up a maximum number of reconnection attempts. If that limit is reached, the client will shutdown. The default number of attempts to reconnect is infinite and configurable via GrpcClientSettings
’s connectionAttempts
.
The client offers a method closed()
that returns a Future
CompletionStage
that will complete once the client is explicitly closed after invoking close()
. The returned Future
CompletionStage
will complete with a failure when the maximum number of connectionAttempts
(which causes a shutdown).
Load balancing
When multiple endpoints are discovered for a gRPC client, currently one is selected and used for all outgoing calls.
This approach, while naïve, in fact works well in many cases: when there are multiple nodes available to handle requests, a server-side load balancer is better-positioned to make decisions than any single client, as it can take into account information from multiple clients, and sometimes even lifecycle information (e.g. not forward requests to nodes that are scheduled to shut down).
Client-side load balancing is desirable when you are using the default static
or the grpc-dns
discovery mechanism. You can set the load-balancing-policy
client configuration option to round_robin
to enable the round_robin client-side load balancing strategy provided by grpc-java.
Note that load balancing is marked as experimental in grpc-java.
Client-side load balancing for other discovery mechanisms is not yet supported.
Request Metadata
Default request metadata, for example for authentication, can be provided through the GrpcClientSettings
GrpcClientSettings
passed to the client when it is created, it will be the base metadata used for each request.
In some cases you will want to provide specific metadata to a single request, this can be done through the “lifted” client API, each method of the service has an empty parameter list version (.sayHello()
) on the client returning a SingleResponseRequestBuilder
SingleResponseRequestBuilder
or StreamResponseRequestBuilder
StreamResponseRequestBuilder
.
After adding the required metadata the request is done by calling invoke
with the request parameters.
Notice: method addHeader
return a new object, you should use it like String
or use it in the chain structure.
- Scala
-
source
def singleRequestReply(): Unit = { sys.log.info("Performing request") val reply = client.sayHello().addHeader("key", "value").invoke(HelloRequest("Alice")) println(s"got single reply: ${Await.result(reply, 5.seconds).message}") }
- Java
-
source
private static void singleRequestReply(GreeterServiceClient client) throws Exception { HelloRequest request = HelloRequest.newBuilder().setName("Alice").build(); CompletionStage<HelloReply> reply = client.sayHello() .addHeader("key", "value") .invoke(request); System.out.println("got single reply: " + reply.toCompletableFuture().get(5, TimeUnit.SECONDS)); }
Rich error model
Beyond status codes you can also use the Rich error model. Currently there is no native support for this concept in Akka gRPC. However you can use the following manual approach.
Add the following dependency to receive required classes (that are based on the common protobuf):
sbt
-
libraryDependencies += "io.grpc" % "grpc-protobuf" % "1.47.0"
gradle
-
dependencies { implementation 'io.grpc:grpc-protobuf:1.47.0' }
maven
-
<dependency> <groupId>io.grpc</groupId> <artifactId>grpc-protobuf</artifactId> <version>1.47.0</version> </dependency>
Extract the StatusRuntimeException
and parse the Rich error model to access code
, message
and details
.
- Scala
-
source
val richErrorResponse = client.sayHello(HelloRequest("Bob")).failed.futureValue richErrorResponse match { case ex: StatusRuntimeException => val status: com.google.rpc.Status = StatusProto.fromStatusAndTrailers(ex.getStatus, ex.getTrailers) def fromJavaProto(javaPbSource: com.google.protobuf.Any): com.google.protobuf.any.Any = com.google.protobuf.any.Any(typeUrl = javaPbSource.getTypeUrl, value = javaPbSource.getValue) status.getDetails(0).getTypeUrl should be("type.googleapis.com/google.rpc.LocalizedMessage") import LocalizedMessage.messageCompanion val customErrorReply: LocalizedMessage = fromJavaProto(status.getDetails(0)).unpack status.getCode should be(3) status.getMessage should be("What is wrong?") customErrorReply.message should be("The password!") customErrorReply.locale should be("EN") case ex => fail(s"This should be a StatusRuntimeException but it is ${ex.getClass}") }
- Java
-
source
HelloRequest request = HelloRequest.newBuilder().setName("Alice").build(); CompletionStage<HelloReply> response = client.sayHello(request); StatusRuntimeException statusEx = response.toCompletableFuture().handle((res, ex) -> { return (StatusRuntimeException) ex; }).get(); com.google.rpc.Status status = StatusProto.fromStatusAndTrailers(statusEx.getStatus(), statusEx.getTrailers()); assertEquals("type.googleapis.com/google.rpc.LocalizedMessage", status.getDetails(0).getTypeUrl()); com.google.rpc.error_details.LocalizedMessage details = fromJavaProto(status.getDetails(0)).unpack(com.google.rpc.error_details.LocalizedMessage.messageCompanion()); assertEquals(Status.INVALID_ARGUMENT.getCode().value(), status.getCode()); assertEquals("What is wrong?", status.getMessage()); assertEquals("The password!", details.message()); assertEquals("EN", details.locale());
Please look here how to create errors with such details on the server side.