1 #region Copyright notice and license 2 3 // Copyright 2015-2016 gRPC authors. 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 #endregion 18 19 using System; 20 using System.Collections.Generic; 21 using System.IO; 22 using System.Linq; 23 using System.Threading; 24 using System.Threading.Tasks; 25 using Google.Protobuf; 26 using Grpc.Core; 27 using Grpc.Core.Utils; 28 using Grpc.Testing; 29 using NUnit.Framework; 30 31 namespace Grpc.IntegrationTesting 32 { 33 /// <summary> 34 /// Shows how to attach custom error details as a binary trailer. 35 /// </summary> 36 public class CustomErrorDetailsTest 37 { 38 const string DebugInfoTrailerName = "debug-info-bin"; 39 const string ExceptionDetail = "Exception thrown on purpose."; 40 const string Host = "localhost"; 41 Server server; 42 Channel channel; 43 TestService.TestServiceClient client; 44 45 [OneTimeSetUp] Init()46 public void Init() 47 { 48 // Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755 49 server = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) 50 { 51 Services = { TestService.BindService(new CustomErrorDetailsTestServiceImpl()) }, 52 Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } } 53 }; 54 server.Start(); 55 56 channel = new Channel(Host, server.Ports.Single().BoundPort, ChannelCredentials.Insecure); 57 client = new TestService.TestServiceClient(channel); 58 } 59 60 [OneTimeTearDown] Cleanup()61 public void Cleanup() 62 { 63 channel.ShutdownAsync().Wait(); 64 server.ShutdownAsync().Wait(); 65 } 66 67 [Test] ErrorDetailsFromCallObject()68 public async Task ErrorDetailsFromCallObject() 69 { 70 var call = client.UnaryCallAsync(new SimpleRequest { ResponseSize = 10 }); 71 72 try 73 { 74 await call.ResponseAsync; 75 Assert.Fail(); 76 } 77 catch (RpcException e) 78 { 79 Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode); 80 var debugInfo = GetDebugInfo(call.GetTrailers()); 81 Assert.AreEqual(debugInfo.Detail, ExceptionDetail); 82 Assert.IsNotEmpty(debugInfo.StackEntries); 83 } 84 } 85 86 [Test] ErrorDetailsFromRpcException()87 public async Task ErrorDetailsFromRpcException() 88 { 89 try 90 { 91 await client.UnaryCallAsync(new SimpleRequest { ResponseSize = 10 }); 92 Assert.Fail(); 93 } 94 catch (RpcException e) 95 { 96 Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode); 97 var debugInfo = GetDebugInfo(e.Trailers); 98 Assert.AreEqual(debugInfo.Detail, ExceptionDetail); 99 Assert.IsNotEmpty(debugInfo.StackEntries); 100 } 101 } 102 GetDebugInfo(Metadata trailers)103 private static DebugInfo GetDebugInfo(Metadata trailers) 104 { 105 var entry = trailers.First((e) => e.Key == DebugInfoTrailerName); 106 return DebugInfo.Parser.ParseFrom(entry.ValueBytes); 107 } 108 109 private class CustomErrorDetailsTestServiceImpl : TestService.TestServiceBase 110 { UnaryCall(SimpleRequest request, ServerCallContext context)111 public override Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context) 112 { 113 try 114 { 115 throw new ArgumentException(ExceptionDetail); 116 } 117 catch (Exception e) 118 { 119 // Fill debug info with some structured details about the failure. 120 var debugInfo = new DebugInfo(); 121 debugInfo.Detail = e.Message; 122 debugInfo.StackEntries.AddRange(e.StackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None)); 123 context.ResponseTrailers.Add(DebugInfoTrailerName, debugInfo.ToByteArray()); 124 throw new RpcException(new Status(StatusCode.Unknown, "The handler threw exception.")); 125 } 126 } 127 } 128 } 129 } 130