1 /*
2  * Copyright 2016 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.examples.errorhandling;
18 
19 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
20 
21 import com.google.common.base.Verify;
22 import com.google.common.base.VerifyException;
23 import com.google.common.util.concurrent.FutureCallback;
24 import com.google.common.util.concurrent.Futures;
25 import com.google.common.util.concurrent.ListenableFuture;
26 import com.google.common.util.concurrent.Uninterruptibles;
27 import com.google.rpc.DebugInfo;
28 import io.grpc.CallOptions;
29 import io.grpc.ClientCall;
30 import io.grpc.ManagedChannel;
31 import io.grpc.ManagedChannelBuilder;
32 import io.grpc.Metadata;
33 import io.grpc.Server;
34 import io.grpc.ServerBuilder;
35 import io.grpc.Status;
36 import io.grpc.examples.helloworld.GreeterGrpc;
37 import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub;
38 import io.grpc.examples.helloworld.GreeterGrpc.GreeterFutureStub;
39 import io.grpc.examples.helloworld.GreeterGrpc.GreeterStub;
40 import io.grpc.examples.helloworld.HelloReply;
41 import io.grpc.examples.helloworld.HelloRequest;
42 import io.grpc.protobuf.ProtoUtils;
43 import io.grpc.stub.StreamObserver;
44 import java.util.concurrent.CountDownLatch;
45 import java.util.concurrent.ExecutionException;
46 import java.util.concurrent.TimeUnit;
47 import javax.annotation.Nullable;
48 
49 /**
50  * Shows how to setting and reading RPC error details.
51  * Proto used here is just an example proto, but the pattern sending
52  * application error information as an application-specific binary protos
53  * in the response trailers is the recommended way to return application
54  * level error.
55  */
56 public class DetailErrorSample {
57   private static final Metadata.Key<DebugInfo> DEBUG_INFO_TRAILER_KEY =
58       ProtoUtils.keyForProto(DebugInfo.getDefaultInstance());
59 
60   private static final DebugInfo DEBUG_INFO =
61       DebugInfo.newBuilder()
62           .addStackEntries("stack_entry_1")
63           .addStackEntries("stack_entry_2")
64           .addStackEntries("stack_entry_3")
65           .setDetail("detailed error info.").build();
66 
67   private static final String DEBUG_DESC = "detailed error description";
68 
main(String[] args)69   public static void main(String[] args) throws Exception {
70     new DetailErrorSample().run();
71   }
72 
73   private ManagedChannel channel;
74 
run()75   void run() throws Exception {
76     Server server = ServerBuilder.forPort(0).addService(new GreeterGrpc.GreeterImplBase() {
77       @Override
78       public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
79         Metadata trailers = new Metadata();
80         trailers.put(DEBUG_INFO_TRAILER_KEY, DEBUG_INFO);
81         responseObserver.onError(Status.INTERNAL.withDescription(DEBUG_DESC)
82             .asRuntimeException(trailers));
83       }
84     }).build().start();
85     channel =
86         ManagedChannelBuilder.forAddress("localhost", server.getPort()).usePlaintext().build();
87 
88     blockingCall();
89     futureCallDirect();
90     futureCallCallback();
91     asyncCall();
92     advancedAsyncCall();
93 
94     channel.shutdown();
95     server.shutdown();
96     channel.awaitTermination(1, TimeUnit.SECONDS);
97     server.awaitTermination();
98   }
99 
verifyErrorReply(Throwable t)100   static void verifyErrorReply(Throwable t) {
101     Status status = Status.fromThrowable(t);
102     Metadata trailers = Status.trailersFromThrowable(t);
103     Verify.verify(status.getCode() == Status.Code.INTERNAL);
104     Verify.verify(trailers.containsKey(DEBUG_INFO_TRAILER_KEY));
105     Verify.verify(status.getDescription().equals(DEBUG_DESC));
106     try {
107       Verify.verify(trailers.get(DEBUG_INFO_TRAILER_KEY).equals(DEBUG_INFO));
108     } catch (IllegalArgumentException e) {
109       throw new VerifyException(e);
110     }
111   }
112 
blockingCall()113   void blockingCall() {
114     GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
115     try {
116       stub.sayHello(HelloRequest.newBuilder().build());
117     } catch (Exception e) {
118       verifyErrorReply(e);
119     }
120   }
121 
futureCallDirect()122   void futureCallDirect() {
123     GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
124     ListenableFuture<HelloReply> response =
125         stub.sayHello(HelloRequest.newBuilder().build());
126 
127     try {
128       response.get();
129     } catch (InterruptedException e) {
130       Thread.currentThread().interrupt();
131       throw new RuntimeException(e);
132     } catch (ExecutionException e) {
133       verifyErrorReply(e.getCause());
134     }
135   }
136 
futureCallCallback()137   void futureCallCallback() {
138     GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
139     ListenableFuture<HelloReply> response =
140         stub.sayHello(HelloRequest.newBuilder().build());
141 
142     final CountDownLatch latch = new CountDownLatch(1);
143 
144     Futures.addCallback(
145         response,
146         new FutureCallback<HelloReply>() {
147           @Override
148           public void onSuccess(@Nullable HelloReply result) {
149             // Won't be called, since the server in this example always fails.
150           }
151 
152           @Override
153           public void onFailure(Throwable t) {
154             verifyErrorReply(t);
155             latch.countDown();
156           }
157         },
158         directExecutor());
159 
160     if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
161       throw new RuntimeException("timeout!");
162     }
163   }
164 
asyncCall()165   void asyncCall() {
166     GreeterStub stub = GreeterGrpc.newStub(channel);
167     HelloRequest request = HelloRequest.newBuilder().build();
168     final CountDownLatch latch = new CountDownLatch(1);
169     StreamObserver<HelloReply> responseObserver = new StreamObserver<HelloReply>() {
170 
171       @Override
172       public void onNext(HelloReply value) {
173         // Won't be called.
174       }
175 
176       @Override
177       public void onError(Throwable t) {
178         verifyErrorReply(t);
179         latch.countDown();
180       }
181 
182       @Override
183       public void onCompleted() {
184         // Won't be called, since the server in this example always fails.
185       }
186     };
187     stub.sayHello(request, responseObserver);
188 
189     if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
190       throw new RuntimeException("timeout!");
191     }
192   }
193 
194 
195   /**
196    * This is more advanced and does not make use of the stub.  You should not normally need to do
197    * this, but here is how you would.
198    */
advancedAsyncCall()199   void advancedAsyncCall() {
200     ClientCall<HelloRequest, HelloReply> call =
201         channel.newCall(GreeterGrpc.getSayHelloMethod(), CallOptions.DEFAULT);
202 
203     final CountDownLatch latch = new CountDownLatch(1);
204 
205     call.start(new ClientCall.Listener<HelloReply>() {
206 
207       @Override
208       public void onClose(Status status, Metadata trailers) {
209         Verify.verify(status.getCode() == Status.Code.INTERNAL);
210         Verify.verify(trailers.containsKey(DEBUG_INFO_TRAILER_KEY));
211         try {
212           Verify.verify(trailers.get(DEBUG_INFO_TRAILER_KEY).equals(DEBUG_INFO));
213         } catch (IllegalArgumentException e) {
214           throw new VerifyException(e);
215         }
216 
217         latch.countDown();
218       }
219     }, new Metadata());
220 
221     call.sendMessage(HelloRequest.newBuilder().build());
222     call.halfClose();
223 
224     if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
225       throw new RuntimeException("timeout!");
226     }
227   }
228 }
229 
230