Как добавить глобальный перехватчик исключений в сервер 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 должен быть похожим
- определить метод-перехватчик
'' '
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 "";
}
}
'' '
добавить @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();
}
- привязать перехватчик в модуле Guice
ServerExceptionInterceptor interceptor = new ServerExceptionInterceptor();
requestInjection(interceptor);
bindInterceptor(Matchers.any(), Matchers.annotatedWith(WrapError.class), interceptor);
4.Testing