1 /* 2 * Copyright 2023 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.errordetails; 18 19 import static com.google.common.util.concurrent.MoreExecutors.directExecutor; 20 21 import com.google.common.base.Verify; 22 import com.google.common.util.concurrent.FutureCallback; 23 import com.google.common.util.concurrent.Futures; 24 import com.google.common.util.concurrent.ListenableFuture; 25 import com.google.common.util.concurrent.Uninterruptibles; 26 import com.google.protobuf.Any; 27 import com.google.protobuf.InvalidProtocolBufferException; 28 import com.google.rpc.Code; 29 import com.google.rpc.DebugInfo; 30 import com.google.rpc.Status; 31 import io.grpc.Channel; 32 import io.grpc.Grpc; 33 import io.grpc.InsecureChannelCredentials; 34 import io.grpc.InsecureServerCredentials; 35 import io.grpc.ManagedChannel; 36 import io.grpc.Server; 37 import io.grpc.examples.helloworld.GreeterGrpc; 38 import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub; 39 import io.grpc.examples.helloworld.GreeterGrpc.GreeterFutureStub; 40 import io.grpc.examples.helloworld.GreeterGrpc.GreeterStub; 41 import io.grpc.examples.helloworld.HelloReply; 42 import io.grpc.examples.helloworld.HelloRequest; 43 import io.grpc.protobuf.StatusProto; 44 import io.grpc.stub.StreamObserver; 45 import java.util.concurrent.CountDownLatch; 46 import java.util.concurrent.ExecutionException; 47 import java.util.concurrent.TimeUnit; 48 import javax.annotation.Nullable; 49 50 /** 51 * Shows how to set and read com.google.rpc.Status objects as google.rpc.Status error details. 52 */ 53 public class ErrorDetailsExample { 54 private static final DebugInfo DEBUG_INFO = 55 DebugInfo.newBuilder() 56 .addStackEntries("stack_entry_1") 57 .addStackEntries("stack_entry_2") 58 .addStackEntries("stack_entry_3") 59 .setDetail("detailed error info.").build(); 60 main(String[] args)61 public static void main(String[] args) throws Exception { 62 Server server = null; 63 ManagedChannel channel = null; 64 65 try { 66 server = launchServer(); 67 channel = Grpc.newChannelBuilderForAddress( 68 "localhost", server.getPort(), InsecureChannelCredentials.create()).build(); 69 70 runClientTests(channel); 71 } finally { 72 cleanup(channel, server); 73 } 74 } 75 76 77 /** 78 * Create server and start it 79 */ launchServer()80 static Server launchServer() throws Exception { 81 return Grpc.newServerBuilderForPort(0, InsecureServerCredentials.create()) 82 .addService(new GreeterGrpc.GreeterImplBase() { 83 @Override 84 public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) { 85 // This is com.google.rpc.Status, not io.grpc.Status 86 Status status = Status.newBuilder() 87 .setCode(Code.INVALID_ARGUMENT.getNumber()) 88 .setMessage("Email or password malformed") 89 .addDetails(Any.pack(DEBUG_INFO)) 90 .build(); 91 responseObserver.onError(StatusProto.toStatusRuntimeException(status)); 92 } 93 }) 94 .build() 95 .start(); 96 } 97 98 private static void runClientTests(Channel channel) { 99 blockingCall(channel); 100 futureCallDirect(channel); 101 futureCallCallback(channel); 102 asyncCall(channel); 103 } 104 105 private static void cleanup(ManagedChannel channel, Server server) throws InterruptedException { 106 107 // Shutdown client and server for resources to be cleanly released 108 if (channel != null) { 109 channel.shutdown(); 110 } 111 if (server != null) { 112 server.shutdown(); 113 } 114 115 // Wait for cleanup to complete 116 if (channel != null) { 117 channel.awaitTermination(1, TimeUnit.SECONDS); 118 } 119 if (server != null) { 120 server.awaitTermination(1, TimeUnit.SECONDS); 121 } 122 } 123 124 static void verifyErrorReply(Throwable t) { 125 Status status = StatusProto.fromThrowable(t); 126 Verify.verify(status.getCode() == Code.INVALID_ARGUMENT.getNumber()); 127 Verify.verify(status.getMessage().equals("Email or password malformed")); 128 try { 129 DebugInfo unpackedDetail = status.getDetails(0).unpack(DebugInfo.class); 130 Verify.verify(unpackedDetail.equals(DEBUG_INFO)); 131 } catch (InvalidProtocolBufferException e) { 132 Verify.verify(false, "Message was a different type than expected"); 133 } 134 } 135 136 static void blockingCall(Channel channel) { 137 GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel); 138 try { 139 stub.sayHello(HelloRequest.newBuilder().build()); 140 } catch (Exception e) { 141 verifyErrorReply(e); 142 System.out.println("Blocking call received expected error details"); 143 } 144 } 145 146 static void futureCallDirect(Channel channel) { 147 GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel); 148 ListenableFuture<HelloReply> response = 149 stub.sayHello(HelloRequest.newBuilder().build()); 150 151 try { 152 response.get(); 153 } catch (InterruptedException e) { 154 Thread.currentThread().interrupt(); 155 throw new RuntimeException(e); 156 } catch (ExecutionException e) { 157 verifyErrorReply(e.getCause()); 158 System.out.println("Future call direct received expected error details"); 159 } 160 } 161 162 static void futureCallCallback(Channel channel) { 163 GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel); 164 ListenableFuture<HelloReply> response = 165 stub.sayHello(HelloRequest.newBuilder().build()); 166 167 final CountDownLatch latch = new CountDownLatch(1); 168 169 Futures.addCallback( 170 response, 171 new FutureCallback<HelloReply>() { 172 @Override 173 public void onSuccess(@Nullable HelloReply result) { 174 // Won't be called, since the server in this example always fails. 175 } 176 177 @Override 178 public void onFailure(Throwable t) { 179 verifyErrorReply(t); 180 System.out.println("Future callback received expected error details"); 181 latch.countDown(); 182 } 183 }, 184 directExecutor()); 185 186 if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) { 187 throw new RuntimeException("timeout!"); 188 } 189 } 190 191 static void asyncCall(Channel channel) { 192 GreeterStub stub = GreeterGrpc.newStub(channel); 193 HelloRequest request = HelloRequest.newBuilder().build(); 194 final CountDownLatch latch = new CountDownLatch(1); 195 StreamObserver<HelloReply> responseObserver = new StreamObserver<HelloReply>() { 196 197 @Override 198 public void onNext(HelloReply value) { 199 // Won't be called. 200 } 201 202 @Override 203 public void onError(Throwable t) { 204 verifyErrorReply(t); 205 System.out.println("Async call received expected error details"); 206 latch.countDown(); 207 } 208 209 @Override 210 public void onCompleted() { 211 // Won't be called, since the server in this example always fails. 212 } 213 }; 214 stub.sayHello(request, responseObserver); 215 216 if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) { 217 throw new RuntimeException("timeout!"); 218 } 219 } 220 } 221 222