web-dev-qa-db-fra.com

Comment ajouter un intercepteur d'exception global dans le serveur gRPC?

Dans gRPC, comment ajouter un intercepteur d'exception global qui intercepte tout RuntimeException et propager des informations significatives au client?

par exemple, une méthode divide peut lancer ArithmeticException avec / by zero message . Côté serveur, je peux écrire:

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

Si le client passe le dénominateur = 0, il obtiendra:

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

Et les sorties du serveur

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

Le client ne sait pas ce qui se passe.

Si je veux passer / by zero message au client, je dois modifier le serveur pour: (comme décrit dans ce 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())));
  }

Et si le client envoie un dénominateur = 0, il obtiendra:

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

Bien , / by zero est transmis au client.

Mais le problème est que, dans un environnement véritablement d'entreprise, il y aura beaucoup de RuntimeExceptions, et si je veux transmettre ces messages d'exception au client, je devrai essayer d'attraper chaque méthode, ce qui est très lourd.

Existe-t-il un intercepteur global qui intercepte chaque méthode, intercepte RuntimeException et déclenche onError et propage le message d'erreur au client? Pour ne pas avoir à gérer les RuntimeExceptions dans mon code serveur.

Merci beaucoup !

Remarque :

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

Le code ci-dessous interceptera toutes les exceptions d'exécution, reportez-vous également au lien 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());
            }
         }
      };
   }
}
5
mahesh

TransmitStatusRuntimeExceptionInterceptor est très similaire à ce que vous voulez, sauf qu'il n'attrape que StatusRuntimeException. Vous pouvez le bifurquer et le faire intercepter toutes les exceptions.

Pour installer un intercepteur pour tous les services sur un serveur, vous pouvez utiliser ServerBuilder.intercept() , qui a été ajouté dans gRPC 1.5.0

1
Kun Zhang

Avez-vous lu le grpc Java exemples pour interceptor?

Donc, dans mon cas, nous utilisons le code et le message comme standard pour définir le type d'erreur que le serveur a envoyé au client.

Exemples: serveur envoyer une réponse comme

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

Ainsi, dans le client, vous pouvez configurer l'intercepteur client pour obtenir ce code et cette réponse avec Reflection. Pour info, nous utilisons Lognet Spring Boot starter for grpc comme serveur, et Spring boot pour le client.

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

}

Exemple d'intercepteur ci-dessus.

Vous devez bootstrap bien sûr, avant d'en attendre quoi que ce soit;

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

MISE À JOUR

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

Malheureusement, un contexte de thread différent ne signifie aucune portée de gestion des exceptions, donc ma réponse n'est pas la solution que vous recherchez.

0
Gladmir