From 998eb9bcaf8990a9c7ec2709550fb70c72c430dc Mon Sep 17 00:00:00 2001 From: Jan Tattermusch <jtattermusch@google.com> Date: Mon, 20 Jul 2015 22:12:53 -0700 Subject: [PATCH] populate server context --- .../Grpc.Core.Tests/ClientServerTest.cs | 20 ++++ src/csharp/Grpc.Core.Tests/TimespecTest.cs | 13 +++ .../Grpc.Core/Internal/ServerCallHandler.cs | 28 ++++-- src/csharp/Grpc.Core/Internal/Timespec.cs | 14 +++ src/csharp/Grpc.Core/Metadata.cs | 6 ++ src/csharp/Grpc.Core/ServerCallContext.cs | 91 ++++++++++++++++++- 6 files changed, 159 insertions(+), 13 deletions(-) diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs index e797dd82f2..05e33f1589 100644 --- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs +++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs @@ -182,6 +182,19 @@ namespace Grpc.Core.Tests }).Wait(); } + [Test] + public void AsyncUnaryCall_EchoMetadata() + { + var metadata = new Metadata + { + new Metadata.Entry("asciiHeader", "abcdefg"), + new Metadata.Entry("binaryHeader-bin", new byte[] { 1, 2, 3, 0, 0xff } ), + }; + var call = new Call<string, string>(ServiceName, EchoMethod, channel, metadata); + var result = Calls.AsyncUnaryCall(call, "ABC", CancellationToken.None).Result; + Assert.AreEqual("ABC", result); + } + [Test] public void UnaryCall_DisposedChannel() { @@ -216,10 +229,17 @@ namespace Grpc.Core.Tests private static async Task<string> EchoHandler(ServerCallContext context, string request) { + foreach (Metadata.Entry metadataEntry in context.RequestHeaders) + { + Console.WriteLine("Echoing header " + metadataEntry.Key + " as trailer"); + context.ResponseTrailers.Add(metadataEntry); + } + if (request == "THROW") { throw new Exception("This was thrown on purpose by a test"); } + return request; } diff --git a/src/csharp/Grpc.Core.Tests/TimespecTest.cs b/src/csharp/Grpc.Core.Tests/TimespecTest.cs index 5831121add..a34b407a01 100644 --- a/src/csharp/Grpc.Core.Tests/TimespecTest.cs +++ b/src/csharp/Grpc.Core.Tests/TimespecTest.cs @@ -58,6 +58,19 @@ namespace Grpc.Core.Internal.Tests Assert.AreEqual(Timespec.NativeSize, Marshal.SizeOf(typeof(Timespec))); } + [Test] + public void ToDateTime() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), + new Timespec(IntPtr.Zero, 0).ToDateTime()); + + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 10, DateTimeKind.Utc).AddTicks(50), + new Timespec(new IntPtr(10), 5000).ToDateTime()); + + Assert.AreEqual(new DateTime(2015, 7, 21, 4, 21, 48, DateTimeKind.Utc), + new Timespec(new IntPtr(1437452508), 0).ToDateTime()); + } + [Test] public void Add() { diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs index 880005ea40..f3d3c629bc 100644 --- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs +++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs @@ -34,6 +34,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Grpc.Core.Internal; using Grpc.Core.Utils; @@ -70,15 +71,16 @@ namespace Grpc.Core.Internal var requestStream = new ServerRequestStream<TRequest, TResponse>(asyncCall); var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall); - Status status = Status.DefaultSuccess; + Status status; try { Preconditions.CheckArgument(await requestStream.MoveNext()); var request = requestStream.Current; // TODO(jtattermusch): we need to read the full stream so that native callhandle gets deallocated. Preconditions.CheckArgument(!await requestStream.MoveNext()); - var context = new ServerCallContext(); // TODO(jtattermusch): initialize the context + var context = HandlerUtils.NewContext(newRpc); var result = await handler(context, request); + status = context.Status; await responseStream.WriteAsync(result); } catch (Exception e) @@ -123,7 +125,7 @@ namespace Grpc.Core.Internal var requestStream = new ServerRequestStream<TRequest, TResponse>(asyncCall); var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall); - Status status = Status.DefaultSuccess; + Status status; try { Preconditions.CheckArgument(await requestStream.MoveNext()); @@ -131,8 +133,9 @@ namespace Grpc.Core.Internal // TODO(jtattermusch): we need to read the full stream so that native callhandle gets deallocated. Preconditions.CheckArgument(!await requestStream.MoveNext()); - var context = new ServerCallContext(); // TODO(jtattermusch): initialize the context + var context = HandlerUtils.NewContext(newRpc); await handler(context, request, responseStream); + status = context.Status; } catch (Exception e) { @@ -176,12 +179,13 @@ namespace Grpc.Core.Internal var finishedTask = asyncCall.ServerSideCallAsync(); var requestStream = new ServerRequestStream<TRequest, TResponse>(asyncCall); var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall); - var context = new ServerCallContext(); // TODO(jtattermusch): initialize the context + var context = HandlerUtils.NewContext(newRpc); - Status status = Status.DefaultSuccess; + Status status; try { var result = await handler(context, requestStream); + status = context.Status; try { await responseStream.WriteAsync(result); @@ -233,12 +237,13 @@ namespace Grpc.Core.Internal var finishedTask = asyncCall.ServerSideCallAsync(); var requestStream = new ServerRequestStream<TRequest, TResponse>(asyncCall); var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall); - var context = new ServerCallContext(); // TODO(jtattermusch): initialize the context + var context = HandlerUtils.NewContext(newRpc); - Status status = Status.DefaultSuccess; + Status status; try { await handler(context, requestStream, responseStream); + status = context.Status; } catch (Exception e) { @@ -284,5 +289,12 @@ namespace Grpc.Core.Internal // TODO(jtattermusch): what is the right status code here? return new Status(StatusCode.Unknown, "Exception was thrown by handler."); } + + public static ServerCallContext NewContext(ServerRpcNew newRpc) + { + return new ServerCallContext( + newRpc.Method, newRpc.Host, newRpc.Deadline.ToDateTime(), + newRpc.RequestMetadata, CancellationToken.None); + } } } diff --git a/src/csharp/Grpc.Core/Internal/Timespec.cs b/src/csharp/Grpc.Core/Internal/Timespec.cs index de783f5a4b..da2819f14d 100644 --- a/src/csharp/Grpc.Core/Internal/Timespec.cs +++ b/src/csharp/Grpc.Core/Internal/Timespec.cs @@ -43,6 +43,8 @@ namespace Grpc.Core.Internal const int NanosPerSecond = 1000 * 1000 * 1000; const int NanosPerTick = 100; + static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + [DllImport("grpc_csharp_ext.dll")] static extern Timespec gprsharp_now(); @@ -52,6 +54,13 @@ namespace Grpc.Core.Internal [DllImport("grpc_csharp_ext.dll")] static extern int gprsharp_sizeof_timespec(); + public Timespec(IntPtr tv_sec, int tv_nsec) + { + this.tv_sec = tv_sec; + this.tv_nsec = tv_nsec; + this.clock_type = GPRClockType.Realtime; + } + // NOTE: on linux 64bit sizeof(gpr_timespec) = 16, on windows 32bit sizeof(gpr_timespec) = 8 // so IntPtr seems to have the right size to work on both. public System.IntPtr tv_sec; @@ -76,6 +85,11 @@ namespace Grpc.Core.Internal return gprsharp_now(); } } + + public DateTime ToDateTime() + { + return UnixEpoch.AddTicks(tv_sec.ToInt64() * (NanosPerSecond / NanosPerTick) + tv_nsec / NanosPerTick); + } internal static int NativeSize { diff --git a/src/csharp/Grpc.Core/Metadata.cs b/src/csharp/Grpc.Core/Metadata.cs index 4552d39d88..0c6fcbc0f8 100644 --- a/src/csharp/Grpc.Core/Metadata.cs +++ b/src/csharp/Grpc.Core/Metadata.cs @@ -220,6 +220,12 @@ namespace Grpc.Core return value; } } + + public override string ToString() + { + return string.Format("[Entry: key={0}, value={1}]", Key, Value); + } + } } } diff --git a/src/csharp/Grpc.Core/ServerCallContext.cs b/src/csharp/Grpc.Core/ServerCallContext.cs index bc9a499c51..4fec3dc676 100644 --- a/src/csharp/Grpc.Core/ServerCallContext.cs +++ b/src/csharp/Grpc.Core/ServerCallContext.cs @@ -33,6 +33,7 @@ using System; using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; namespace Grpc.Core @@ -42,14 +43,94 @@ namespace Grpc.Core /// </summary> public sealed class ServerCallContext { - // TODO(jtattermusch): add cancellationToken + // TODO(jtattermusch): expose method to send initial metadata back to client - // TODO(jtattermusch): add deadline info + // TODO(jtattermusch): allow setting status and trailing metadata to send after handler completes. - // TODO(jtattermusch): expose initial metadata sent by client for reading + private readonly string method; + private readonly string host; + private readonly DateTime deadline; + private readonly Metadata requestHeaders; + private readonly CancellationToken cancellationToken; - // TODO(jtattermusch): expose method to send initial metadata back to client + private Status status = Status.DefaultSuccess; + private readonly Metadata responseTrailers = new Metadata(); - // TODO(jtattermusch): allow setting status and trailing metadata to send after handler completes. + public ServerCallContext(string method, string host, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken) + { + this.method = method; + this.host = host; + this.deadline = deadline; + this.requestHeaders = requestHeaders; + this.cancellationToken = cancellationToken; + } + + /// <summary> Name of method called in this RPC. </summary> + public string Method + { + get + { + return this.method; + } + } + + /// <summary> Name of host called in this RPC. </summary> + public string Host + { + get + { + return this.host; + } + } + + /// <summary> Deadline for this RPC. </summary> + public DateTime Deadline + { + get + { + return this.deadline; + } + } + + /// <summary> Initial metadata sent by client. </summary> + public Metadata RequestHeaders + { + get + { + return this.requestHeaders; + } + } + + // TODO(jtattermusch): support signalling cancellation. + /// <summary> Cancellation token signals when call is cancelled. </summary> + public CancellationToken CancellationToken + { + get + { + return this.cancellationToken; + } + } + + /// <summary> Trailers to send back to client after RPC finishes.</summary> + public Metadata ResponseTrailers + { + get + { + return this.responseTrailers; + } + } + + /// <summary> Status to send back to client after RPC finishes.</summary> + public Status Status + { + get + { + return this.status; + } + set + { + status = value; + } + } } } -- GitLab