grpc / grpc-java

The Java gRPC implementation. HTTP/2 based RPC
https://grpc.io/docs/languages/java/
Apache License 2.0
11.45k stars 3.85k forks source link

Status.withCause() is not effective #3054

Closed CrazyZfp closed 7 years ago

CrazyZfp commented 7 years ago

Please answer these questions before submitting your issue.

What version of gRPC are you using?

1.3.0

What JVM are you using (java -version)?

java version "1.8.0_51" Java(TM) SE Runtime Environment (build 1.8.0_51-b16) Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)

What did you do?

In server interceptor, I close ServerCall and set a Status with Throwable instance:

@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
    ServerCall<ReqT, RespT> call,
    Metadata headers,
    ServerCallHandler<ReqT, RespT> next) {

     try{
          ... 
     } catch(CustomException e){
         call.close(Status.UNAUTHENTICATED.withDescription("TOKEN_INCORRECT").withCause(e), headers);
     }
     return next.startCall(new ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT>(call) {}, headers);
}

In client, I use Status status = Status.fromThrowable(e); to get the Status returned from server.

status.getCode() and status.getDescription() can get the correct value , but status.getCause() is always null.

What did you expect to see?

In client, I can get the correct Throwable instance rather than null by status.getCause()

What did you see instead?

staus.getCause() is always null

carl-mastrangelo commented 7 years ago

As mentioned in the Status Javadoc:

Create a derived instance of {@link Status} with the given cause. However, the cause is not transmitted from server to client.

If you want to propagate the exception across the wire, you'll need to make your own custom header.

vegoutha commented 7 years ago

@carl-mastrangelo I am facing the same issue using scalapb.

override def healthCheck(request: HealthCheckRequest, streamObserver: StreamObserver[HealthCheckResponse]) = {
    log.info("Health check endpoint")
    //    streamObserver.onNext(HealthCheckResponse(status = "UP"))
    //    streamObserver.onCompleted()
    try {
      throw new CustomException("Custom exception!")
    }
    catch {
      case e: CustomException => {
        streamObserver.onError(Status.INTERNAL.withDescription(e.getMessage).augmentDescription("customException()").withCause(CustomException(e.getMessage)).asRuntimeException)
      }
    }
  }

status.getCode() and status.getDescription() can get the correct value , but status.getCause() is always null.

Could you please provide the steps to create headers and pass the exception object back to client?

carl-mastrangelo commented 7 years ago

@vegoutha

Here is a sample interceptor for putting it in. You can write a similar one for pulling it out.

  private static class ExceptionInterceptor implements ClientInterceptor {

    private static final Metadata.Key<byte[]> exceptionKey =
        Metadata.Key.of("my-metadata", Metadata.BINARY_BYTE_MARSHALLER);

    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
        CallOptions callOptions, Channel next) {
      final ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
      return new SimpleForwardingClientCall<ReqT, RespT>(call) {
        @Override
        public void start(Listener<RespT> responseListener, Metadata headers) {
          super.start(new SimpleForwardingClientCallListener<RespT>(responseListener) {
            @Override
            public void onClose(Status status, Metadata trailers) {
              if (status.getCause() != null) {
                trailers.put(
                    exceptionKey, status.getCause().toString().getBytes(StandardCharsets.UTF_8));
              }
              super.onClose(status, trailers);
            }
          }, headers);
        }
      };
    }
  }