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 RuntimeException
s, 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 RuntimeException
s 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
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());
}
}
};
}
}
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
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.
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.