How to add a global exception hook to the gRPC server?

In gRPC, how do I add a global exception hook that catches any RuntimeException and propagates meaningful information to the client?

for example, the divide method may throw an ArithmeticException with the message / by zero . On the server side, I can 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 the denominator = 0, he will receive:

 Exception in thread "main" io.grpc.StatusRuntimeException: UNKNOWN 

And the server displays

 Exception while executing runnable io.grpc.in ternal.ServerImpl$JumpToApplicationThreadServerStreamListener$2@ 62e95ade java.lang.ArithmeticException: / by zero 

The client does not know what is happening.

If I want to pass the / by zero message to the client, I need to change the 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 the client sends the denominator = 0, it will receive:

 Exception in thread "main" io.grpc.StatusRuntimeException: INTERNAL: / by zero 

Good, / by zero is passed to the client.

But the problem is that in a truly corporate environment there will be a lot of RuntimeException , and if I want to pass these exception messages to the client, I will have to try to catch every method, which is very cumbersome.

Is there any global interceptor that intercepts each method, catches a RuntimeException and onError and propagates an error message to the client? So I do not need to deal with a RuntimeException 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 
+18
source share
5 answers

The code below will catch all exceptions at runtime. Also see 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()); } } }; } } 
+2
source

Have you read grpc java examples for interceptor?

So, in my case, we use the code and the message as a standard to determine which error the server sent to the client.

Examples: server send response, e.g.

 { code: 409, message: 'Id xxx aldready exist' } 

So, in the client, you can configure the client interceptor to receive this code and response using Reflection. Fyi we use Lognet Spring Bootstarter for grpc as a server and Spring for a client.

+1
source

TransmitStatusRuntimeExceptionInterceptor is very similar to what you want, except that it only catches a StatusRuntimeException . You can fork out and make it catch all exceptions.

To set an interceptor for all services on the server, you can use ServerBuilder.intercept() , which was added in gRPC 1.5.0

+1
source
 public class GrpcExceptionHandler implements ServerInterceptor { private final Logger logger = LoggerFactory.getLogger (GrpcExceptionHandler.class); @Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall (ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { logger.info ("GRPC call at: {}", Instant.now ()); ServerCall.Listener<ReqT> listener; try { listener = next.startCall (call, headers); } catch (Throwable ex) { logger.error ("Uncaught exception from grpc service"); call.close (Status.INTERNAL .withCause (ex) .withDescription ("Uncaught exception from grpc service"), null); return new ServerCall.Listener<ReqT>() {}; } return listener; } 

}

Interceptor example above.

You need to download it first before you expect anything from it;

 serverBuilder.addService (ServerInterceptors.intercept (bindableService, interceptor)); 

UPDATE

 public interface ServerCallHandler<RequestT, ResponseT> { /** * Produce a non-{@code null} listener for the incoming call. Implementations are free to call * methods on {@code call} before this method has returned. * * <p>If the implementation throws an exception, {@code call} will be closed with an error. * Implementations must not throw an exception if they started processing that may use {@code * call} on another thread. * * @param call object for responding to the remote client. * @return listener for processing incoming request messages for {@code call} */ ServerCall.Listener<RequestT> startCall( ServerCall<RequestT, ResponseT> call, Metadata headers); } 

Unfortunately, a different thread context means no exception handling scope, so my answer is not the solution you are looking for.

0
source

I used AOP to troubleshoot rpc errors all over the world and I find it convenient. I use AOP in tricks, and the way to use it spring should be similar

  1. define an interceptor method

'' '

 public class ServerExceptionInterceptor implements MethodInterceptor { final static Logger logger = LoggerFactory.getLogger(ServerExceptionInterceptor.class); public Object invoke(MethodInvocation invocation) throws Throwable { try { return invocation.proceed(); } catch (Exception ex) { String stackTrace = Throwables.getStackTraceAsString(ex); logger.error("##grpc server side error, {}", stackTrace); Object[] args = invocation.getArguments(); StreamObserver<?> responseObserver = (StreamObserver<?>)args[1]; responseObserver.onError(Status.INTERNAL .withDescription(stackTrace) .withCause(ex) .asRuntimeException()); return null; } } @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RUNTIME) public @interface WrapError { String value() default ""; } } 

'' '

  1. add @WrapError to all RPC methods @Override @WrapError public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) { HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); logger.info("#rpc server, sayHello, planId: {}", req.getName()); if(true) throw new RuntimeException("testing-rpc-error"); //simulate an exception responseObserver.onNext(reply); responseObserver.onCompleted(); } @Override @WrapError public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) { HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); logger.info("#rpc server, sayHello, planId: {}", req.getName()); if(true) throw new RuntimeException("testing-rpc-error"); //simulate an exception responseObserver.onNext(reply); responseObserver.onCompleted(); }

    1. snap interceptor in Guice module

ServerExceptionInterceptor interceptor = new ServerExceptionInterceptor(); requestInjection(interceptor); bindInterceptor(Matchers.any(), Matchers.annotatedWith(WrapError.class), interceptor);

4.Testing

-one
source

All Articles