Как добавить глобальный перехватчик исключений в сервер gRPC?

В 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();
}

Если клиент проходит знаменатель = 0, он получит:

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

И сервер выводит

Exception while executing runnable io.grpc.in[email protected]62e95ade
java.lang.ArithmeticException: / by zero

Клиент не знает, что происходит.

Если я хочу передать сообщение / by zero клиенту, мне нужно изменить сервер на: (как описано в этом 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())));
  }

И если клиент отправляет знаменатель = 0, он получит:

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

Хорошо, / by zero передается клиенту.

Но проблема в том, что в действительно корпоративной среде будет много RuntimeException, и если я хочу передать эти сообщения об исключениях клиенту, мне придется попробовать поймать каждый метод, который очень громоздкий.

Существует ли какой-либо глобальный перехватчик, который перехватывает каждый метод, ловит RuntimeException и запускает onError и распространяет сообщение об ошибке клиенту? Так что мне не нужно иметь дело с RuntimeException в моем коде сервера.

Спасибо большое!

Примечание:

<grpc.version>1.0.1</grpc.version>
com.google.protobuf:proton:3.1.0
io.grpc:protoc-gen-grpc-java:1.0.1

Ответ 1

Приведенный ниже код перехватит все исключения во время выполнения. Также см. ссылку 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

Вы читали grpc java examples для перехватчика?

Итак, в моем случае мы используем код и сообщение как стандарт для определения того, какую ошибку сервер отправил на клиент.

Примеры: ответ на отправку сервера, например

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

Итак, в клиенте вы можете настроить клиентский перехватчик для получения этого кода и ответа с помощью Reflection. Fyi мы используем Lognet Spring Загрузочный стартер для grpc в качестве сервера и Spring для клиента.

Ответ 3

TransmitStatusRuntimeExceptionInterceptor очень похож на то, что вы хотите, за исключением того, что он только перехватывает StatusRuntimeException. Вы можете раскошелиться и заставить его перехватывать все исключения.

Чтобы установить перехватчик для всех служб на сервере, вы можете использовать ServerBuilder.intercept(), который был добавлен в gRPC 1.5.0

Ответ 4

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;
}

}

Пример перехватчика выше.

Вам нужно сначала загрузить его, прежде чем что-то ожидать от него;

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);
}

К сожалению, другой контекст потока означает отсутствие области обработки исключений, поэтому мой ответ не является решением, которое вы ищете.

Ответ 5

Я использовал AOP для устранения ошибок rpc во всем мире, и я нахожу это удобным. Я использую АОП в хитрости, и способ использовать его spring должен быть похожим

  1. определить метод-перехватчик

'' '

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. добавить @WrapError ко всем методам RPC @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. привязать перехватчик в модуле Guice

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

4.Testing