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

时间:2016-09-30 17:41:07

标签: 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();
}

如果客户端传递的分母= 0,则会得到:

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

服务器输出

Exception while executing runnable io.grpc.internal.ServerImpl$JumpToApplicationThreadServerStreamListener$2@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())));
  }

如果客户端发送denominator = 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

9 个答案:

答案 0 :(得分:2)

下面的代码将捕获所有运行时异常,另请参阅链接https://github.com/grpc/grpc-java/issues/1552

string properties = ((IJavaScriptExecutor)driver).ExecuteScript("return window.getComputedStyle(arguments[0],null).cssText", domElement);
strArr = properties.split(";")

for (count = 0; count <= strArr.Length - 1; count++)
{
    console.log(strArr[count]);
}

答案 1 :(得分:2)

如果您想在所有 gRPC 端点(包括处理客户端的那些)和拦截器中捕获异常,您可能需要类似的东西到以下:

import io.grpc.ForwardingServerCallListener;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.Status;

public class GlobalGrpcExceptionHandler implements ServerInterceptor {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall,
                                                                 Metadata requestHeaders,
                                                                 ServerCallHandler<ReqT, RespT> serverCallHandler) {
        try {
            ServerCall.Listener<ReqT> delegate = serverCallHandler.startCall(serverCall, requestHeaders);
            return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(delegate) {
                @Override
                public void onMessage(ReqT message) {
                    try {
                        super.onMessage(message); // Here onNext is called (in case of client streams)
                    } catch (Throwable e) {
                        handleEndpointException(e, serverCall);
                    }
                }

                @Override
                public void onHalfClose() {
                    try {
                        super.onHalfClose(); // Here onCompleted is called (in case of client streams)
                    } catch (Throwable e) {
                        handleEndpointException(e, serverCall);
                    }
                }
            };
        } catch (Throwable t) {
            return handleInterceptorException(t, serverCall);
        }
    }

    private <ReqT, RespT> void handleEndpointException(Throwable t, ServerCall<ReqT, RespT> serverCall) {
        serverCall.close(Status.INTERNAL
                         .withCause(t)
                         .withDescription("An exception occurred in the endpoint implementation"), new Metadata());
    }

    private <ReqT, RespT> ServerCall.Listener<ReqT> handleInterceptorException(Throwable t, ServerCall<ReqT, RespT> serverCall) {
        serverCall.close(Status.INTERNAL
                         .withCause(t)
                         .withDescription("An exception occurred in a **subsequent** interceptor"), new Metadata());

        return new ServerCall.Listener<ReqT>() {
            // no-op
        };
    }
}

免责声明:我通过检查实现收集了这一点,我没有在文档中阅读它,我不确定它是否会改变。作为参考,我指的是 io.grpc 版本 1.30

答案 2 :(得分:1)

你有没有读过grpc java examples的拦截器?

所以在我的情况下,我们使用代码和消息作为标准来定义服务器发送到客户端的错误类型。

示例:服务器发送响应,如

simplexml_load_file('C://xampp/htdocs/instantSupportXml.xml');
print_r($file);

因此,在客户端中,您可以使用Reflection设置客户端拦截器以获取该代码和响应。我们使用Lognet Spring Boot starter for grpc作为服务器,为客户端使用Spring启动。

答案 3 :(得分:1)

TransmitStatusRuntimeExceptionInterceptor与您想要的非常相似,只是它只捕获StatusRuntimeException。你可以将它分叉并使其捕获所有异常。

要为服务器上的所有服务安装拦截器,可以使用gRPC 1.5.0中添加的ServerBuilder.intercept()

答案 4 :(得分: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;
}

}

上面的示例拦截器。

当然,你需要先引导它,然后才能从中得到任何东西;

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

<强>更新

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 :(得分:0)

在Kotlin中,您必须structure your ServerInterceptor differently。我在Micronaut中使用的是grpc-kotlin,而这些异常从未出现在SimpleForwardingServerCallListener onHalfClose或其他处理程序中。

答案 6 :(得分:0)

如果您可以使用 yidongnan/grpc-spring-boot-starter 将您的 gRPC 服务器应用程序转换为 Spring Boot,那么您可以编写 @GrpcAdvice,类似于 Spring Boot @ControllerAdvice

    @GrpcAdvice
    public class ExceptionHandler {
    
      @GrpcExceptionHandler(ValidationErrorException.class)
      public StatusRuntimeException handleValidationError(ValidationErrorException cause) {
    
         Status.INVALID_ARGUMENT.withDescription("Invalid Argument")
            .asRuntimeException()
      }
    }

答案 7 :(得分:0)

在 Kotlin 上,在侦听器的方法上添加 try/catch 不起作用,由于某种原因,异常被吞下。

按照@markficket 发布的链接,我已经实现了一个解决方案,创建了一个 SimpleForwardingServerCall 的实现。

class ErrorHandlerServerInterceptor : ServerInterceptor {

    private inner class StatusExceptionHandlingServerCall<ReqT, RespT>(delegate: ServerCall<ReqT, RespT>) 
        : ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT>(delegate) {

        override fun close(status: Status, trailers: Metadata) {
            status.run {
                when {
                    isOk -> status
                    cause is MyException -> myExceptionHandler(cause as MyException)
                    else -> defaultExceptionHandler(cause)
                }
            }
                .let { super.close(it, trailers) }
        }

        private fun myExceptionHandler(cause: MyException): Status = cause.run { ... }

        private fun defaultExceptionHandler(cause: Throwable?): Status = cause?.run { ... }

    }

    override fun <ReqT : Any, RespT : Any> interceptCall(
        call: ServerCall<ReqT, RespT>,
        metadata: Metadata,
        next: ServerCallHandler<ReqT, RespT>
    ): ServerCall.Listener<ReqT> =
        next.startCall(StatusExceptionHandlingServerCall(call), metadata)

}

那么当然需要在创建服务器时添加拦截器

ServerBuilder
  .forPort(port)
  .executor(Dispatchers.IO.asExecutor())
  .addService(service)
  .intercept(ErrorHandlerServerInterceptor())
  .build()

然后你可以简单地在你的 gRPC 方法上抛出异常

override suspend fun someGrpcCall(request: Request): Response {
  ... code ...
  throw NotFoundMyException("Cannot find entity")
}

答案 8 :(得分:-1)

我已经使用AOP全局处理rpc错误,我发现它很方便。我在guice中使用AOP,在春天使用它的方式应该类似

  1. 定义方法拦截器
  2. ```

    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模块中的拦截器
    2. ServerExceptionInterceptor interceptor = new ServerExceptionInterceptor(); requestInjection(interceptor); bindInterceptor(Matchers.any(), Matchers.annotatedWith(WrapError.class), interceptor);

      4.testing