In gRPC , how to add a global exception interceptor that intercepts any RuntimeException
and propagate meaningful information to the client ?
for example , a divide
method may throw ArithmeticException
with / by zero
message . In the server side , I may write :
@Override
public void divide(DivideRequest request, StreamObserver<DivideResponse> responseObserver) {
int dom = request.getDenominator();
int num = request.getNumerator();
double result = num / dom;
responseObserver.onNext(DivideResponse.newBuilder().setValue(result).build());
responseObserver.onCompleted();
}
If the client passes denominator = 0 , it will get :
Exception in thread "main" io.grpc.StatusRuntimeException: UNKNOWN
And the server outputs
Exception while executing runnable io.grpc.internal.ServerImpl$JumpToApplicationThreadServerStreamListener$2@62e95ade
java.lang.ArithmeticException: / by zero
The client doesn't know what's going on.
If I want to pass / by zero
message to client , I have to modify server to :
(as described in this question )
try {
double result = num / dom;
responseObserver.onNext(DivideResponse.newBuilder().setValue(result).build());
responseObserver.onCompleted();
} catch (Exception e) {
logger.error("onError : {}" , e.getMessage());
responseObserver.onError(new StatusRuntimeException(Status.INTERNAL.withDescription(e.getMessage())));
}
And if client sends denominator = 0 , it will get :
Exception in thread "main" io.grpc.StatusRuntimeException: INTERNAL: / by zero
Good , / by zero
is passed to client.
But the problem is , in a truly enterprise environment, there will be a lot of RuntimeException
s , and if I want to pass these exception's messages to client , I will have to try catch each method , which is very cumbersome.
Is there any global interceptor that intercepts every method , catching RuntimeException
and trigger onError
and propagate the error message to client ? So that I don't have to deal with RuntimeException
s in my server code .
Thanks a lot !
Note :
<grpc.version>1.0.1</grpc.version>
com.google.protobuf:proton:3.1.0
io.grpc:protoc-gen-grpc-java:1.0.1
What are gRPC Interceptors? Interceptors are neat little components of a gRPC application that allow us to interact with a proto message or context either before — or after — it is sent or received by the client or server.
gRPC uses HTTP/2, which multiplexes multiple calls on a single TCP connection. All gRPC calls over that connection go to one endpoint.
a blocking/synchronous stub: this means that the RPC call waits for the server to respond, and will either return a response or raise an exception. a non-blocking/asynchronous stub that makes non-blocking calls to the server, where the response is returned asynchronously.
Below code will catch all runtime exceptions, Also refer the link https://github.com/grpc/grpc-java/issues/1552
public class GlobalGrpcExceptionHandler implements ServerInterceptor { @Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata requestHeaders, ServerCallHandler<ReqT, RespT> next) { ServerCall.Listener<ReqT> delegate = next.startCall(call, requestHeaders); return new SimpleForwardingServerCallListener<ReqT>(delegate) { @Override public void onHalfClose() { try { super.onHalfClose(); } catch (Exception e) { call.close(Status.INTERNAL .withCause (e) .withDescription("error message"), new Metadata()); } } }; } }
TransmitStatusRuntimeExceptionInterceptor is very similar to what you want, except that it only catches StatusRuntimeException
. You can fork it and make it catch all exceptions.
To install an interceptor for all services on a server, you can use ServerBuilder.intercept()
, which was added in gRPC 1.5.0
If you want to catch exceptions in all gRPC endpoints (including the ones processing client streams) and interceptors, you probably want something similar to the following:
import io.grpc.ForwardingServerCallListener;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.Status;
public class GlobalGrpcExceptionHandler implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall,
Metadata requestHeaders,
ServerCallHandler<ReqT, RespT> serverCallHandler) {
try {
ServerCall.Listener<ReqT> delegate = serverCallHandler.startCall(serverCall, requestHeaders);
return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(delegate) {
@Override
public void onMessage(ReqT message) {
try {
super.onMessage(message); // Here onNext is called (in case of client streams)
} catch (Throwable e) {
handleEndpointException(e, serverCall);
}
}
@Override
public void onHalfClose() {
try {
super.onHalfClose(); // Here onCompleted is called (in case of client streams)
} catch (Throwable e) {
handleEndpointException(e, serverCall);
}
}
};
} catch (Throwable t) {
return handleInterceptorException(t, serverCall);
}
}
private <ReqT, RespT> void handleEndpointException(Throwable t, ServerCall<ReqT, RespT> serverCall) {
serverCall.close(Status.INTERNAL
.withCause(t)
.withDescription("An exception occurred in the endpoint implementation"), new Metadata());
}
private <ReqT, RespT> ServerCall.Listener<ReqT> handleInterceptorException(Throwable t, ServerCall<ReqT, RespT> serverCall) {
serverCall.close(Status.INTERNAL
.withCause(t)
.withDescription("An exception occurred in a **subsequent** interceptor"), new Metadata());
return new ServerCall.Listener<ReqT>() {
// no-op
};
}
}
DISCLAIMER: I have gathered this by inspecting the implementation, I have not read it in the documentation and I'm not sure if it will change. For reference, I'm referring to io.grpc
version 1.30
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With