How to add global exception interceptor in gRPC server?
Solution 1
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());
}
}
};
}
}
Solution 2
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
Solution 3
In Kotlin, you have to structure your ServerInterceptor differently. I was using grpc-kotlin in Micronaut and the exceptions never appeared in the SimpleForwardingServerCallListener
onHalfClose
or other handlers.
Solution 4
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
.
Solution 5
Have you read the grpc java examples for interceptor?
So in my case, we use code and message as standard for defining what kind of error the server sent onto client.
Examples: server send response like
{
code: 409,
message: 'Id xxx aldready exist'
}
So in the client you can setup client interceptor for getting that code and response with Reflection. Fyi we use Lognet Spring Boot starter for grpc as the server, and Spring boot for the client.
smallufo
A Java/Kotlin programmer twitter : http://twitter.com/smallufo
Updated on June 02, 2022Comments
-
smallufo almost 2 years
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 throwArithmeticException
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 triggeronError
and propagate the error message to client ? So that I don't have to deal withRuntimeException
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