Recently, I used gRPC to create a service for the first time. On the server side, I want to throw a custom exception directly so that the client can see it. At first, I tried this:
// responseObserver.onError(new CustomException("custom exception")); throw new CustomException("one error occurs");
But I got an awkward result:
io.grpc.StatusRuntimeException: UNKNOWN
The client can't see the exception error message that my custom throws. After some research, we find two kinds of clients that can get the custom exception information thrown out by the server.
Mode 1: set the description from the exception message to Status
The server implementation is as follows:
// Custom exception handling @Override public void customException(EchoRequest request, StreamObserver<EchoResponse> responseObserver) { try { if (request.getMessage().equals("error")) { throw new CustomException("custom exception message"); } EchoResponse echoResponse = EchoResponse.newBuilder().build(); responseObserver.onNext(echoResponse); responseObserver.onCompleted(); } catch (CustomException e) { responseObserver.onError(Status.INVALID_ARGUMENT // Here is our custom exception information .withDescription(e.getMessage()) .withCause(e) .asRuntimeException()); } }
Use status.invalid'argument to specify the exception code. This code also indicates that there is a problem with the parameter. The exceptions that need to be explicitly thrown by the normal server are mostly parameter problems. If it is a service problem, you don't need to do special processing. Let it throw directly.
Client call:
try { EchoResponse echoResponse = stub.customException( EchoRequest.newBuilder().setMessage("error").build()); System.out.println(echoResponse.getMessage()); } catch (StatusRuntimeException e) { e.printStackTrace(); // INVALID_ARGUMENT: occurs exception // This message will contain invalid menu argument, which is not what we want System.out.println(e.getMessage()); if (e.getStatus().getCode() == Status.Code.INVALID_ARGUMENT) { // This is the custom exception information we want System.out.println(e.getStatus().getDescription()); // Throw CustomException for our ExceptionHandler throw new CustomException(e.getStatus().getDescription()); } else { throw e; } }
Mode 2: pass more detailed error information through MetaData
In this way, an ErrorInfo is customized in the proto file:
message ErrorInfo { // There are a lot of error messages in the list repeated string message = 1; }
The ErrorInfo defined here can carry a lot of information, for example, you can define a code field in it, and then you can represent more abundant information.
In the server implementation class:
private static final Metadata.Key<ErrorInfo> ERROR_INFO_TRAILER_KEY = ProtoUtils.keyForProto(ErrorInfo.getDefaultInstance()); @Override public void detailErrorMessage(EchoRequest request, StreamObserver<EchoResponse> responseObserver) { try { if (request.getMessage().equals("error")) { throw new CustomException("custom exception message"); } EchoResponse echoResponse = EchoResponse.newBuilder().build(); responseObserver.onNext(echoResponse); responseObserver.onCompleted(); } catch (CustomException e) { Metadata trailers = new Metadata(); ErrorInfo.Builder builder = ErrorInfo.newBuilder() .addMessage(e.getMessage()); trailers.put(ERROR_INFO_TRAILER_KEY, builder.build()); responseObserver.onError(Status.INVALID_ARGUMENT .withCause(e) .asRuntimeException(trailers)); } }
Then look at the client call:
try { EchoResponse echoResponse = stub.detailErrorMessage( EchoRequest.newBuilder().setMessage("error").build()); System.out.println(echoResponse.getMessage()); } catch (StatusRuntimeException e) { if (e.getStatus().getCode() == Status.Code.INVALID_ARGUMENT) { Metadata trailers = Status.trailersFromThrowable(e); if (trailers.containsKey(ERROR_INFO_TRAILER_KEY)) { ErrorInfo errorInfo = trailers.get(ERROR_INFO_TRAILER_KEY); if (errorInfo.getMessageList() != null && errorInfo.getMessageList().size() != 0) { // This is the custom exception information we want System.out.println(errorInfo.getMessageList()); } } } else { throw e; } }
The above are all exception handling of synchronous invocation of client. There are some small differences in exception handling of asynchronous invocation. For complete code, please refer to: https://github.com/jiaobuchong/grpc-learning/tree/master/grpc-error-handling
Reference resources:
https://github.com/grpc/grpc-java/tree/master/examples
Introduction to gRPC
https://grpc.github.io/grpc/core/md_doc_statuscodes.html
https://stackoverflow.com/questions/48748745/pattern-for-rich-error-handling-in-grpc