如何在gRPC服务器中添加全局异常拦截器?

sma*_*ufo 21 java protocol-buffers grpc grpc-java

在gRPC中,如何添加拦截任何RuntimeException并将有意义的信息传播给客户端的全局异常拦截器?

例如,一个divide方法可能引发ArithmeticException/ by zero消息.在服务器端,我可以写:

@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();
}
Run Code Online (Sandbox Code Playgroud)

如果客户端通过denominator = 0,它将得到:

Exception in thread "main" io.grpc.StatusRuntimeException: UNKNOWN
Run Code Online (Sandbox Code Playgroud)

并且服务器输出

Exception while executing runnable io.grpc.internal.ServerImpl$JumpToApplicationThreadServerStreamListener$2@62e95ade
java.lang.ArithmeticException: / by zero
Run Code Online (Sandbox Code Playgroud)

客户不知道发生了什么.

如果我想将/ by zero消息传递给客户端,我必须将服务器修改为:(如本问题所述)

  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())));
  }
Run Code Online (Sandbox Code Playgroud)

如果客户端发送denominator = 0,它将得到:

Exception in thread "main" io.grpc.StatusRuntimeException: INTERNAL: / by zero
Run Code Online (Sandbox Code Playgroud)

很好,/ by zero传递给客户.

但问题是,在真正的企业环境中,会有很多RuntimeExceptions,如果我想将这些异常的消息传递给客户端,我将不得不尝试捕获每个方法,这非常麻烦.

是否有任何全局拦截器拦截每个方法,捕获RuntimeException并触发onError并将错误消息传播给客户端?这样我就不必RuntimeException在服务器代码中处理s了.

非常感谢 !

注意 :

<grpc.version>1.0.1</grpc.version>
com.google.protobuf:proton:3.1.0
io.grpc:protoc-gen-grpc-java:1.0.1
Run Code Online (Sandbox Code Playgroud)

小智 7

下面的代码将捕获所有运行时异常,另请参阅链接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());
            }
         }
      };
   }
}
Run Code Online (Sandbox Code Playgroud)


Gla*_*mir 0

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;
}
Run Code Online (Sandbox Code Playgroud)

}

上面的示例拦截器。

当然,在期待它产生任何结果之前,您需要先引导它;

serverBuilder.addService (ServerInterceptors.intercept (bindableService, interceptor));
Run Code Online (Sandbox Code Playgroud)

更新

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);
}
Run Code Online (Sandbox Code Playgroud)

可悲的是,不同的线程上下文意味着没有异常处理范围,所以我的答案不是您正在寻找的解决方案。

  • 好吧,拦截确实发生了,但正如文档所述,“next.startCall(call, headers)”立即返回并在另一个线程中执行,最终我们失去了异常的堆栈范围。遗憾的是,我不知道目前是否有任何解决方法。 (3认同)