aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar Jan Tattermusch <jtattermusch@users.noreply.github.com>2017-08-10 11:23:59 +0200
committerGravatar GitHub <noreply@github.com>2017-08-10 11:23:59 +0200
commita48f879aef6d33b69b544ed971c9f45b9cae3658 (patch)
treefc73f29cccac0064834d4c0bc0552cf06ddb9d0d /src
parent0fcd99e1682b15699af43b4ed4390e8be78aafb0 (diff)
parent9dc182161dfb35ed8f7bb45150bb53e097fcebe1 (diff)
Merge pull request #12120 from jtattermusch/csharp_rpcexception
Add Trailers property to RpcException
Diffstat (limited to 'src')
-rw-r--r--src/csharp/Grpc.Core.Tests/ClientServerTest.cs74
-rw-r--r--src/csharp/Grpc.Core/Internal/AsyncCall.cs8
-rw-r--r--src/csharp/Grpc.Core/Internal/ServerCallHandler.cs19
-rw-r--r--src/csharp/Grpc.Core/RpcException.cs28
-rw-r--r--src/csharp/Grpc.IntegrationTesting/CustomErrorDetailsTest.cs21
5 files changed, 137 insertions, 13 deletions
diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
index c74d04c829..0cb9288131 100644
--- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
@@ -92,9 +92,33 @@ namespace Grpc.Core.Tests
var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
+ Assert.AreEqual(0, ex.Trailers.Count);
var ex2 = Assert.ThrowsAsync<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
+ Assert.AreEqual(0, ex.Trailers.Count);
+ }
+
+ [Test]
+ public void UnaryCall_ServerHandlerThrowsRpcExceptionWithTrailers()
+ {
+ helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
+ {
+ var trailers = new Metadata { {"xyz", "xyz-value"} };
+ throw new RpcException(new Status(StatusCode.Unauthenticated, ""), trailers);
+ });
+
+ var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+ Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
+ Assert.AreEqual(1, ex.Trailers.Count);
+ Assert.AreEqual("xyz", ex.Trailers[0].Key);
+ Assert.AreEqual("xyz-value", ex.Trailers[0].Value);
+
+ var ex2 = Assert.ThrowsAsync<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+ Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
+ Assert.AreEqual(1, ex2.Trailers.Count);
+ Assert.AreEqual("xyz", ex2.Trailers[0].Key);
+ Assert.AreEqual("xyz-value", ex2.Trailers[0].Value);
}
[Test]
@@ -108,9 +132,34 @@ namespace Grpc.Core.Tests
var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
+ Assert.AreEqual(0, ex.Trailers.Count);
+
+ var ex2 = Assert.ThrowsAsync<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+ Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
+ Assert.AreEqual(0, ex2.Trailers.Count);
+ }
+
+ [Test]
+ public void UnaryCall_ServerHandlerSetsStatusAndTrailers()
+ {
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ context.Status = new Status(StatusCode.Unauthenticated, "");
+ context.ResponseTrailers.Add("xyz", "xyz-value");
+ return "";
+ });
+
+ var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+ Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
+ Assert.AreEqual(1, ex.Trailers.Count);
+ Assert.AreEqual("xyz", ex.Trailers[0].Key);
+ Assert.AreEqual("xyz-value", ex.Trailers[0].Value);
var ex2 = Assert.ThrowsAsync<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
+ Assert.AreEqual(1, ex2.Trailers.Count);
+ Assert.AreEqual("xyz", ex2.Trailers[0].Key);
+ Assert.AreEqual("xyz-value", ex2.Trailers[0].Value);
}
[Test]
@@ -148,7 +197,7 @@ namespace Grpc.Core.Tests
CollectionAssert.AreEqual(new string[] { "A", "B", "C" }, await call.ResponseStream.ToListAsync());
Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
- Assert.IsNotNull("xyz", call.GetTrailers()[0].Key);
+ Assert.AreEqual("xyz", call.GetTrailers()[0].Key);
}
[Test]
@@ -183,6 +232,27 @@ namespace Grpc.Core.Tests
}
[Test]
+ public async Task ServerStreamingCall_TrailersFromMultipleSourcesGetConcatenated()
+ {
+ helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
+ {
+ context.ResponseTrailers.Add("xyz", "xyz-value");
+ throw new RpcException(new Status(StatusCode.InvalidArgument, ""), new Metadata { {"abc", "abc-value"} });
+ });
+
+ var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "");
+
+ var ex = Assert.ThrowsAsync<RpcException>(async () => await call.ResponseStream.MoveNext());
+ Assert.AreEqual(StatusCode.InvalidArgument, ex.Status.StatusCode);
+ Assert.AreEqual(2, call.GetTrailers().Count);
+ Assert.AreEqual(2, ex.Trailers.Count);
+ Assert.AreEqual("xyz", ex.Trailers[0].Key);
+ Assert.AreEqual("xyz-value", ex.Trailers[0].Value);
+ Assert.AreEqual("abc", ex.Trailers[1].Key);
+ Assert.AreEqual("abc-value", ex.Trailers[1].Value);
+ }
+
+ [Test]
public async Task DuplexStreamingCall()
{
helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
@@ -199,7 +269,7 @@ namespace Grpc.Core.Tests
CollectionAssert.AreEqual(new string[] { "A", "B", "C" }, await call.ResponseStream.ToListAsync());
Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
- Assert.IsNotNull("xyz-value", call.GetTrailers()[0].Value);
+ Assert.AreEqual("xyz-value", call.GetTrailers()[0].Value);
}
[Test]
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
index 6e6ca7cd53..17109de587 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
@@ -329,7 +329,7 @@ namespace Grpc.Core.Internal
protected override Exception GetRpcExceptionClientOnly()
{
- return new RpcException(finishedStatus.Value.Status);
+ return new RpcException(finishedStatus.Value.Status, finishedStatus.Value.Trailers);
}
protected override Task CheckSendAllowedOrEarlyResult()
@@ -348,7 +348,7 @@ namespace Grpc.Core.Internal
// Writing after the call has finished is not a programming error because server can close
// the call anytime, so don't throw directly, but let the write task finish with an error.
var tcs = new TaskCompletionSource<object>();
- tcs.SetException(new RpcException(finishedStatus.Value.Status));
+ tcs.SetException(new RpcException(finishedStatus.Value.Status, finishedStatus.Value.Trailers));
return tcs.Task;
}
@@ -468,7 +468,7 @@ namespace Grpc.Core.Internal
var status = receivedStatus.Status;
if (status.StatusCode != StatusCode.OK)
{
- unaryResponseTcs.SetException(new RpcException(status));
+ unaryResponseTcs.SetException(new RpcException(status, receivedStatus.Trailers));
return;
}
@@ -506,7 +506,7 @@ namespace Grpc.Core.Internal
var status = receivedStatus.Status;
if (status.StatusCode != StatusCode.OK)
{
- streamingResponseCallFinishedTcs.SetException(new RpcException(status));
+ streamingResponseCallFinishedTcs.SetException(new RpcException(status, receivedStatus.Trailers));
return;
}
diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
index 36702a3fab..6019f8e793 100644
--- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
@@ -76,7 +76,7 @@ namespace Grpc.Core.Internal
{
Logger.Warning(e, "Exception occured in handler.");
}
- status = HandlerUtils.StatusFromException(e);
+ status = HandlerUtils.GetStatusFromExceptionAndMergeTrailers(e, context.ResponseTrailers);
}
try
{
@@ -133,7 +133,7 @@ namespace Grpc.Core.Internal
{
Logger.Warning(e, "Exception occured in handler.");
}
- status = HandlerUtils.StatusFromException(e);
+ status = HandlerUtils.GetStatusFromExceptionAndMergeTrailers(e, context.ResponseTrailers);
}
try
@@ -191,7 +191,7 @@ namespace Grpc.Core.Internal
{
Logger.Warning(e, "Exception occured in handler.");
}
- status = HandlerUtils.StatusFromException(e);
+ status = HandlerUtils.GetStatusFromExceptionAndMergeTrailers(e, context.ResponseTrailers);
}
try
@@ -247,7 +247,7 @@ namespace Grpc.Core.Internal
{
Logger.Warning(e, "Exception occured in handler.");
}
- status = HandlerUtils.StatusFromException(e);
+ status = HandlerUtils.GetStatusFromExceptionAndMergeTrailers(e, context.ResponseTrailers);
}
try
{
@@ -292,11 +292,20 @@ namespace Grpc.Core.Internal
internal static class HandlerUtils
{
- public static Status StatusFromException(Exception e)
+ public static Status GetStatusFromExceptionAndMergeTrailers(Exception e, Metadata callContextResponseTrailers)
{
var rpcException = e as RpcException;
if (rpcException != null)
{
+ // There are two sources of metadata entries on the server-side:
+ // 1. serverCallContext.ResponseTrailers
+ // 2. trailers in RpcException thrown by user code in server side handler.
+ // As metadata allows duplicate keys, the logical thing to do is
+ // to just merge trailers from RpcException into serverCallContext.ResponseTrailers.
+ foreach (var entry in rpcException.Trailers)
+ {
+ callContextResponseTrailers.Add(entry);
+ }
// use the status thrown by handler.
return rpcException.Status;
}
diff --git a/src/csharp/Grpc.Core/RpcException.cs b/src/csharp/Grpc.Core/RpcException.cs
index 01b9e4fb1a..d2c912e73a 100644
--- a/src/csharp/Grpc.Core/RpcException.cs
+++ b/src/csharp/Grpc.Core/RpcException.cs
@@ -17,6 +17,7 @@
#endregion
using System;
+using Grpc.Core.Utils;
namespace Grpc.Core
{
@@ -26,6 +27,7 @@ namespace Grpc.Core
public class RpcException : Exception
{
private readonly Status status;
+ private readonly Metadata trailers;
/// <summary>
/// Creates a new <c>RpcException</c> associated with given status.
@@ -34,6 +36,7 @@ namespace Grpc.Core
public RpcException(Status status) : base(status.ToString())
{
this.status = status;
+ this.trailers = Metadata.Empty;
}
/// <summary>
@@ -44,6 +47,18 @@ namespace Grpc.Core
public RpcException(Status status, string message) : base(message)
{
this.status = status;
+ this.trailers = Metadata.Empty;
+ }
+
+ /// <summary>
+ /// Creates a new <c>RpcException</c> associated with given status and trailing response metadata.
+ /// </summary>
+ /// <param name="status">Resulting status of a call.</param>
+ /// <param name="trailers">Response trailing metadata.</param>
+ public RpcException(Status status, Metadata trailers) : base(status.ToString())
+ {
+ this.status = status;
+ this.trailers = GrpcPreconditions.CheckNotNull(trailers);
}
/// <summary>
@@ -56,5 +71,18 @@ namespace Grpc.Core
return status;
}
}
+
+ /// <summary>
+ /// Gets the call trailing metadata.
+ /// Trailers only have meaningful content for client-side calls (in which case they represent the trailing metadata sent by the server when closing the call).
+ /// Instances of <c>RpcException</c> thrown by the server-side part of the stack will have trailers always set to empty.
+ /// </summary>
+ public Metadata Trailers
+ {
+ get
+ {
+ return trailers;
+ }
+ }
}
}
diff --git a/src/csharp/Grpc.IntegrationTesting/CustomErrorDetailsTest.cs b/src/csharp/Grpc.IntegrationTesting/CustomErrorDetailsTest.cs
index be996f91e0..374c6fc23f 100644
--- a/src/csharp/Grpc.IntegrationTesting/CustomErrorDetailsTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/CustomErrorDetailsTest.cs
@@ -65,7 +65,7 @@ namespace Grpc.IntegrationTesting
}
[Test]
- public async Task UnaryCall()
+ public async Task ErrorDetailsFromCallObject()
{
var call = client.UnaryCallAsync(new SimpleRequest { ResponseSize = 10 });
@@ -83,7 +83,24 @@ namespace Grpc.IntegrationTesting
}
}
- private DebugInfo GetDebugInfo(Metadata trailers)
+ [Test]
+ public async Task ErrorDetailsFromRpcException()
+ {
+ try
+ {
+ await client.UnaryCallAsync(new SimpleRequest { ResponseSize = 10 });
+ Assert.Fail();
+ }
+ catch (RpcException e)
+ {
+ Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
+ var debugInfo = GetDebugInfo(e.Trailers);
+ Assert.AreEqual(debugInfo.Detail, ExceptionDetail);
+ Assert.IsNotEmpty(debugInfo.StackEntries);
+ }
+ }
+
+ private static DebugInfo GetDebugInfo(Metadata trailers)
{
var entry = trailers.First((e) => e.Key == DebugInfoTrailerName);
return DebugInfo.Parser.ParseFrom(entry.ValueBytes);