From 191218ff84ea7e2ec8b9d66d2cae03129bdddf6c Mon Sep 17 00:00:00 2001 From: larry-safran Date: Thu, 30 Mar 2023 15:06:31 -0700 Subject: [PATCH 1/5] examples: Detail Error example (google.rpc.Status) --- .../errordetails/DetailErrorSample.java | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 examples/src/main/java/io/grpc/examples/errordetails/DetailErrorSample.java diff --git a/examples/src/main/java/io/grpc/examples/errordetails/DetailErrorSample.java b/examples/src/main/java/io/grpc/examples/errordetails/DetailErrorSample.java new file mode 100644 index 00000000000..ce1345e6777 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/errordetails/DetailErrorSample.java @@ -0,0 +1,188 @@ +/* + * Copyright 2016 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.examples.errordetails; + +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; + +import com.google.common.base.Verify; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.Uninterruptibles; +import com.google.protobuf.Any; +import com.google.rpc.Code; +import com.google.rpc.DebugInfo; +import com.google.rpc.Status; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.InsecureServerCredentials; +import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.Server; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub; +import io.grpc.examples.helloworld.GreeterGrpc.GreeterFutureStub; +import io.grpc.examples.helloworld.GreeterGrpc.GreeterStub; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.protobuf.ProtoUtils; +import io.grpc.protobuf.StatusProto; +import io.grpc.stub.StreamObserver; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + +/** + * Shows how to set and read com.google.rpc.Status objects as google.rpc.Status error details. + */ +public class DetailErrorSample { + private static final Metadata.Key DEBUG_INFO_TRAILER_KEY = + ProtoUtils.keyForProto(DebugInfo.getDefaultInstance()); + + private static final DebugInfo DEBUG_INFO = + DebugInfo.newBuilder() + .addStackEntries("stack_entry_1") + .addStackEntries("stack_entry_2") + .addStackEntries("stack_entry_3") + .setDetail("detailed error info.").build(); + + private static final String DEBUG_DESC = "detailed error description"; + + public static void main(String[] args) throws Exception { + new DetailErrorSample().run(); + } + + private ManagedChannel channel; + + void run() throws Exception { + Server server = Grpc.newServerBuilderForPort(0, InsecureServerCredentials.create()) + .addService(new GreeterGrpc.GreeterImplBase() { + @Override + public void sayHello(HelloRequest request, StreamObserver responseObserver) { + // This is com.google.rpc.Status, not io.grpc.Status + Status status = Status.newBuilder() + .setCode(Code.INVALID_ARGUMENT.getNumber()) + .setMessage("Email or password malformed") + .addDetails(Any.pack(DEBUG_INFO)) + .build(); + responseObserver.onError(StatusProto.toStatusRuntimeException(status)); + } + }).build().start(); + channel = Grpc.newChannelBuilderForAddress( + "localhost", server.getPort(), InsecureChannelCredentials.create()).build(); + + blockingCall(); + futureCallDirect(); + futureCallCallback(); + asyncCall(); + + channel.shutdown(); + server.shutdown(); + channel.awaitTermination(1, TimeUnit.SECONDS); + server.awaitTermination(); + } + + static void verifyErrorReply(Throwable t) { + Status status = StatusProto.fromThrowable(t); + Verify.verify(status.getCode() == Code.INVALID_ARGUMENT.getNumber()); + Verify.verify(status.getMessage().equals("Email or password malformed")); + Verify.verify(status.getDetails(0).equals(DEBUG_DESC)); + } + + void blockingCall() { + GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel); + try { + stub.sayHello(HelloRequest.newBuilder().build()); + } catch (Exception e) { + verifyErrorReply(e); + } + } + + void futureCallDirect() { + GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel); + ListenableFuture response = + stub.sayHello(HelloRequest.newBuilder().build()); + + try { + response.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } catch (ExecutionException e) { + verifyErrorReply(e.getCause()); + } + } + + void futureCallCallback() { + GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel); + ListenableFuture response = + stub.sayHello(HelloRequest.newBuilder().build()); + + final CountDownLatch latch = new CountDownLatch(1); + + Futures.addCallback( + response, + new FutureCallback() { + @Override + public void onSuccess(@Nullable HelloReply result) { + // Won't be called, since the server in this example always fails. + } + + @Override + public void onFailure(Throwable t) { + verifyErrorReply(t); + latch.countDown(); + } + }, + directExecutor()); + + if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) { + throw new RuntimeException("timeout!"); + } + } + + void asyncCall() { + GreeterStub stub = GreeterGrpc.newStub(channel); + HelloRequest request = HelloRequest.newBuilder().build(); + final CountDownLatch latch = new CountDownLatch(1); + StreamObserver responseObserver = new StreamObserver() { + + @Override + public void onNext(HelloReply value) { + // Won't be called. + } + + @Override + public void onError(Throwable t) { + verifyErrorReply(t); + latch.countDown(); + } + + @Override + public void onCompleted() { + // Won't be called, since the server in this example always fails. + } + }; + stub.sayHello(request, responseObserver); + + if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) { + throw new RuntimeException("timeout!"); + } + } +} + From 272d253b3bb9097b5d1b6b22bf49cfeeab493434 Mon Sep 17 00:00:00 2001 From: larry-safran Date: Thu, 30 Mar 2023 22:46:31 +0000 Subject: [PATCH 2/5] fixed --- examples/build.gradle | 8 ++++++++ .../examples/errordetails/DetailErrorSample.java | 13 ++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/examples/build.gradle b/examples/build.gradle index a2980b28afa..d5ad28b8e2d 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -252,6 +252,13 @@ task sharingClient(type: CreateStartScripts) { classpath = startScripts.classpath } +task errorDetails(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.errordetails.DetailErrorSample' + applicationName = 'error-details' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + applicationDistribution.into('bin') { from(routeGuideServer) from(routeGuideClient) @@ -280,5 +287,6 @@ applicationDistribution.into('bin') { from(cancellationServer) from(multiplexingServer) from(sharingClient) + from(errorDetails) fileMode = 0755 } diff --git a/examples/src/main/java/io/grpc/examples/errordetails/DetailErrorSample.java b/examples/src/main/java/io/grpc/examples/errordetails/DetailErrorSample.java index ce1345e6777..e791e8e1163 100644 --- a/examples/src/main/java/io/grpc/examples/errordetails/DetailErrorSample.java +++ b/examples/src/main/java/io/grpc/examples/errordetails/DetailErrorSample.java @@ -24,6 +24,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.Uninterruptibles; import com.google.protobuf.Any; +import com.google.protobuf.InvalidProtocolBufferException; import com.google.rpc.Code; import com.google.rpc.DebugInfo; import com.google.rpc.Status; @@ -101,7 +102,13 @@ static void verifyErrorReply(Throwable t) { Status status = StatusProto.fromThrowable(t); Verify.verify(status.getCode() == Code.INVALID_ARGUMENT.getNumber()); Verify.verify(status.getMessage().equals("Email or password malformed")); - Verify.verify(status.getDetails(0).equals(DEBUG_DESC)); + try { + DebugInfo unpackedDetail = status.getDetails(0).unpack(DebugInfo.class); + Verify.verify(unpackedDetail.equals(DEBUG_INFO)); + } catch (InvalidProtocolBufferException e) { + Verify.verify(false, "Message was a different type than expected"); + } + } void blockingCall() { @@ -110,6 +117,7 @@ void blockingCall() { stub.sayHello(HelloRequest.newBuilder().build()); } catch (Exception e) { verifyErrorReply(e); + System.out.println("blocking call received expected error"); } } @@ -125,6 +133,7 @@ void futureCallDirect() { throw new RuntimeException(e); } catch (ExecutionException e) { verifyErrorReply(e.getCause()); + System.out.println("future call direct received expected error"); } } @@ -146,6 +155,7 @@ public void onSuccess(@Nullable HelloReply result) { @Override public void onFailure(Throwable t) { verifyErrorReply(t); + System.out.println("future callback received expected error"); latch.countDown(); } }, @@ -170,6 +180,7 @@ public void onNext(HelloReply value) { @Override public void onError(Throwable t) { verifyErrorReply(t); + System.out.println("Async call received expected error"); latch.countDown(); } From 537c42b587c3ed705f24eb2ee1e735fde15d6eeb Mon Sep 17 00:00:00 2001 From: larry-safran Date: Fri, 31 Mar 2023 13:37:34 -0700 Subject: [PATCH 3/5] address code review comments --- ...orSample.java => ErrorDetailsExample.java} | 101 +++++++++++------- 1 file changed, 62 insertions(+), 39 deletions(-) rename examples/src/main/java/io/grpc/examples/errordetails/{DetailErrorSample.java => ErrorDetailsExample.java} (73%) diff --git a/examples/src/main/java/io/grpc/examples/errordetails/DetailErrorSample.java b/examples/src/main/java/io/grpc/examples/errordetails/ErrorDetailsExample.java similarity index 73% rename from examples/src/main/java/io/grpc/examples/errordetails/DetailErrorSample.java rename to examples/src/main/java/io/grpc/examples/errordetails/ErrorDetailsExample.java index e791e8e1163..013cf3521e4 100644 --- a/examples/src/main/java/io/grpc/examples/errordetails/DetailErrorSample.java +++ b/examples/src/main/java/io/grpc/examples/errordetails/ErrorDetailsExample.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 The gRPC Authors + * Copyright 2023 The gRPC Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,11 +28,11 @@ import com.google.rpc.Code; import com.google.rpc.DebugInfo; import com.google.rpc.Status; +import io.grpc.Channel; import io.grpc.Grpc; import io.grpc.InsecureChannelCredentials; import io.grpc.InsecureServerCredentials; import io.grpc.ManagedChannel; -import io.grpc.Metadata; import io.grpc.Server; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub; @@ -40,7 +40,6 @@ import io.grpc.examples.helloworld.GreeterGrpc.GreeterStub; import io.grpc.examples.helloworld.HelloReply; import io.grpc.examples.helloworld.HelloRequest; -import io.grpc.protobuf.ProtoUtils; import io.grpc.protobuf.StatusProto; import io.grpc.stub.StreamObserver; import java.util.concurrent.CountDownLatch; @@ -51,10 +50,7 @@ /** * Shows how to set and read com.google.rpc.Status objects as google.rpc.Status error details. */ -public class DetailErrorSample { - private static final Metadata.Key DEBUG_INFO_TRAILER_KEY = - ProtoUtils.keyForProto(DebugInfo.getDefaultInstance()); - +public class ErrorDetailsExample { private static final DebugInfo DEBUG_INFO = DebugInfo.newBuilder() .addStackEntries("stack_entry_1") @@ -62,40 +58,68 @@ public class DetailErrorSample { .addStackEntries("stack_entry_3") .setDetail("detailed error info.").build(); - private static final String DEBUG_DESC = "detailed error description"; - public static void main(String[] args) throws Exception { - new DetailErrorSample().run(); + Server server = null; + ManagedChannel channel = null; + ErrorDetailsExample errorDetailsExample = new ErrorDetailsExample(); + + try { + server = errorDetailsExample.launchServer(); + channel = Grpc.newChannelBuilderForAddress( + "localhost", server.getPort(), InsecureChannelCredentials.create()).build(); + + errorDetailsExample.runClientTests(channel); + } finally { + errorDetailsExample.cleanup(channel, server); + } } - private ManagedChannel channel; - void run() throws Exception { - Server server = Grpc.newServerBuilderForPort(0, InsecureServerCredentials.create()) + /** + * Create server and start it + */ + Server launchServer() throws Exception { + return Grpc.newServerBuilderForPort(0, InsecureServerCredentials.create()) .addService(new GreeterGrpc.GreeterImplBase() { - @Override - public void sayHello(HelloRequest request, StreamObserver responseObserver) { - // This is com.google.rpc.Status, not io.grpc.Status - Status status = Status.newBuilder() - .setCode(Code.INVALID_ARGUMENT.getNumber()) - .setMessage("Email or password malformed") - .addDetails(Any.pack(DEBUG_INFO)) - .build(); - responseObserver.onError(StatusProto.toStatusRuntimeException(status)); - } - }).build().start(); - channel = Grpc.newChannelBuilderForAddress( - "localhost", server.getPort(), InsecureChannelCredentials.create()).build(); + @Override + public void sayHello(HelloRequest request, StreamObserver responseObserver) { + // This is com.google.rpc.Status, not io.grpc.Status + Status status = Status.newBuilder() + .setCode(Code.INVALID_ARGUMENT.getNumber()) + .setMessage("Email or password malformed") + .addDetails(Any.pack(DEBUG_INFO)) + .build(); + responseObserver.onError(StatusProto.toStatusRuntimeException(status)); + } + }) + .build() + .start(); + } + + private void runClientTests(Channel channel) { + blockingCall(channel); + futureCallDirect(channel); + futureCallCallback(channel); + asyncCall(channel); + } + + private void cleanup(ManagedChannel channel, Server server) throws InterruptedException { - blockingCall(); - futureCallDirect(); - futureCallCallback(); - asyncCall(); + // Shutdown client and server for resources to be cleanly released + if (channel != null) { + channel.shutdown(); + } + if (server != null) { + server.shutdown(); + } - channel.shutdown(); - server.shutdown(); - channel.awaitTermination(1, TimeUnit.SECONDS); - server.awaitTermination(); + // Wait for cleanup to complete + if (channel != null) { + channel.awaitTermination(1, TimeUnit.SECONDS); + } + if (server != null) { + server.awaitTermination(1, TimeUnit.SECONDS); + } } static void verifyErrorReply(Throwable t) { @@ -108,10 +132,9 @@ static void verifyErrorReply(Throwable t) { } catch (InvalidProtocolBufferException e) { Verify.verify(false, "Message was a different type than expected"); } - } - void blockingCall() { + void blockingCall(Channel channel) { GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel); try { stub.sayHello(HelloRequest.newBuilder().build()); @@ -121,7 +144,7 @@ void blockingCall() { } } - void futureCallDirect() { + void futureCallDirect(Channel channel) { GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel); ListenableFuture response = stub.sayHello(HelloRequest.newBuilder().build()); @@ -137,7 +160,7 @@ void futureCallDirect() { } } - void futureCallCallback() { + void futureCallCallback(Channel channel) { GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel); ListenableFuture response = stub.sayHello(HelloRequest.newBuilder().build()); @@ -166,7 +189,7 @@ public void onFailure(Throwable t) { } } - void asyncCall() { + void asyncCall(Channel channel) { GreeterStub stub = GreeterGrpc.newStub(channel); HelloRequest request = HelloRequest.newBuilder().build(); final CountDownLatch latch = new CountDownLatch(1); From a14bdac3743517f414caba19a15fe9022247abb2 Mon Sep 17 00:00:00 2001 From: larry-safran Date: Fri, 31 Mar 2023 20:47:48 +0000 Subject: [PATCH 4/5] minor --- examples/build.gradle | 2 +- .../grpc/examples/errordetails/ErrorDetailsExample.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index d5ad28b8e2d..333e6ec4f0a 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -253,7 +253,7 @@ task sharingClient(type: CreateStartScripts) { } task errorDetails(type: CreateStartScripts) { - mainClass = 'io.grpc.examples.errordetails.DetailErrorSample' + mainClass = 'io.grpc.examples.errordetails.ErrorDetailsExample' applicationName = 'error-details' outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath diff --git a/examples/src/main/java/io/grpc/examples/errordetails/ErrorDetailsExample.java b/examples/src/main/java/io/grpc/examples/errordetails/ErrorDetailsExample.java index 013cf3521e4..a1eeb5dcc61 100644 --- a/examples/src/main/java/io/grpc/examples/errordetails/ErrorDetailsExample.java +++ b/examples/src/main/java/io/grpc/examples/errordetails/ErrorDetailsExample.java @@ -140,7 +140,7 @@ void blockingCall(Channel channel) { stub.sayHello(HelloRequest.newBuilder().build()); } catch (Exception e) { verifyErrorReply(e); - System.out.println("blocking call received expected error"); + System.out.println("Blocking call received expected error details"); } } @@ -156,7 +156,7 @@ void futureCallDirect(Channel channel) { throw new RuntimeException(e); } catch (ExecutionException e) { verifyErrorReply(e.getCause()); - System.out.println("future call direct received expected error"); + System.out.println("Future call direct received expected error details"); } } @@ -178,7 +178,7 @@ public void onSuccess(@Nullable HelloReply result) { @Override public void onFailure(Throwable t) { verifyErrorReply(t); - System.out.println("future callback received expected error"); + System.out.println("Future callback received expected error details"); latch.countDown(); } }, @@ -203,7 +203,7 @@ public void onNext(HelloReply value) { @Override public void onError(Throwable t) { verifyErrorReply(t); - System.out.println("Async call received expected error"); + System.out.println("Async call received expected error details"); latch.countDown(); } From 8ee6f3aca228d8db064e1bec4ef461342db55de6 Mon Sep 17 00:00:00 2001 From: larry-safran Date: Fri, 31 Mar 2023 22:07:27 +0000 Subject: [PATCH 5/5] make everything static --- .../errordetails/ErrorDetailsExample.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/examples/src/main/java/io/grpc/examples/errordetails/ErrorDetailsExample.java b/examples/src/main/java/io/grpc/examples/errordetails/ErrorDetailsExample.java index a1eeb5dcc61..1d1d72ed837 100644 --- a/examples/src/main/java/io/grpc/examples/errordetails/ErrorDetailsExample.java +++ b/examples/src/main/java/io/grpc/examples/errordetails/ErrorDetailsExample.java @@ -61,16 +61,15 @@ public class ErrorDetailsExample { public static void main(String[] args) throws Exception { Server server = null; ManagedChannel channel = null; - ErrorDetailsExample errorDetailsExample = new ErrorDetailsExample(); try { - server = errorDetailsExample.launchServer(); + server = launchServer(); channel = Grpc.newChannelBuilderForAddress( "localhost", server.getPort(), InsecureChannelCredentials.create()).build(); - errorDetailsExample.runClientTests(channel); + runClientTests(channel); } finally { - errorDetailsExample.cleanup(channel, server); + cleanup(channel, server); } } @@ -78,7 +77,7 @@ public static void main(String[] args) throws Exception { /** * Create server and start it */ - Server launchServer() throws Exception { + static Server launchServer() throws Exception { return Grpc.newServerBuilderForPort(0, InsecureServerCredentials.create()) .addService(new GreeterGrpc.GreeterImplBase() { @Override @@ -96,14 +95,14 @@ public void sayHello(HelloRequest request, StreamObserver responseOb .start(); } - private void runClientTests(Channel channel) { + private static void runClientTests(Channel channel) { blockingCall(channel); futureCallDirect(channel); futureCallCallback(channel); asyncCall(channel); } - private void cleanup(ManagedChannel channel, Server server) throws InterruptedException { + private static void cleanup(ManagedChannel channel, Server server) throws InterruptedException { // Shutdown client and server for resources to be cleanly released if (channel != null) { @@ -134,7 +133,7 @@ static void verifyErrorReply(Throwable t) { } } - void blockingCall(Channel channel) { + static void blockingCall(Channel channel) { GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel); try { stub.sayHello(HelloRequest.newBuilder().build()); @@ -144,7 +143,7 @@ void blockingCall(Channel channel) { } } - void futureCallDirect(Channel channel) { + static void futureCallDirect(Channel channel) { GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel); ListenableFuture response = stub.sayHello(HelloRequest.newBuilder().build()); @@ -160,7 +159,7 @@ void futureCallDirect(Channel channel) { } } - void futureCallCallback(Channel channel) { + static void futureCallCallback(Channel channel) { GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel); ListenableFuture response = stub.sayHello(HelloRequest.newBuilder().build()); @@ -189,7 +188,7 @@ public void onFailure(Throwable t) { } } - void asyncCall(Channel channel) { + static void asyncCall(Channel channel) { GreeterStub stub = GreeterGrpc.newStub(channel); HelloRequest request = HelloRequest.newBuilder().build(); final CountDownLatch latch = new CountDownLatch(1);