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