diff options
author | Hongyu Chen <hongyu@google.com> | 2015-12-11 11:08:35 -0800 |
---|---|---|
committer | Hongyu Chen <hongyu@google.com> | 2015-12-11 11:08:35 -0800 |
commit | aa28ccc92291acc2ff527db72a9389ac2bd2034a (patch) | |
tree | fd7992b402380fdb01d5e193a7d3d8993876565b /src/csharp | |
parent | 0504a4443fb973f8cb3bc43f05bc1a73680fab59 (diff) | |
parent | 12fa8c83aff22c84ee92ea00c79b2f6236c93d26 (diff) |
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'src/csharp')
37 files changed, 771 insertions, 282 deletions
diff --git a/src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs b/src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs index 1c14c5bb5b..f77e9c6573 100644 --- a/src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs +++ b/src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs @@ -57,9 +57,9 @@ namespace Grpc.Auth /// <returns>The interceptor.</returns> public static AsyncAuthInterceptor FromCredential(ITokenAccess credential) { - return new AsyncAuthInterceptor(async (authUri, metadata) => + return new AsyncAuthInterceptor(async (context, metadata) => { - var accessToken = await credential.GetAccessTokenForRequestAsync(authUri, CancellationToken.None).ConfigureAwait(false); + var accessToken = await credential.GetAccessTokenForRequestAsync(context.ServiceUrl, CancellationToken.None).ConfigureAwait(false); metadata.Add(CreateBearerTokenHeader(accessToken)); }); } @@ -72,7 +72,7 @@ namespace Grpc.Auth public static AsyncAuthInterceptor FromAccessToken(string accessToken) { Preconditions.CheckNotNull(accessToken); - return new AsyncAuthInterceptor(async (authUri, metadata) => + return new AsyncAuthInterceptor(async (context, metadata) => { metadata.Add(CreateBearerTokenHeader(accessToken)); }); diff --git a/src/csharp/Grpc.Core.Tests/CallOptionsTest.cs b/src/csharp/Grpc.Core.Tests/CallOptionsTest.cs new file mode 100644 index 0000000000..a3a613be74 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/CallOptionsTest.cs @@ -0,0 +1,88 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Collections.Generic; +using System.Threading; +using Grpc.Core; +using Grpc.Core.Internal; +using Grpc.Core.Utils; +using NUnit.Framework; + +namespace Grpc.Core.Tests +{ + public class CallOptionsTest + { + [Test] + public void WithMethods() + { + var options = new CallOptions(); + + var metadata = new Metadata(); + Assert.AreSame(metadata, options.WithHeaders(metadata).Headers); + + var deadline = DateTime.UtcNow; + Assert.AreEqual(deadline, options.WithDeadline(deadline).Deadline.Value); + + var token = new CancellationTokenSource().Token; + Assert.AreEqual(token, options.WithCancellationToken(token).CancellationToken); + + // Change original instance is unchanged. + Assert.IsNull(options.Headers); + Assert.IsNull(options.Deadline); + Assert.AreEqual(CancellationToken.None, options.CancellationToken); + Assert.IsNull(options.WriteOptions); + Assert.IsNull(options.PropagationToken); + Assert.IsNull(options.Credentials); + } + + [Test] + public void Normalize() + { + Assert.AreSame(Metadata.Empty, new CallOptions().Normalize().Headers); + Assert.AreEqual(DateTime.MaxValue, new CallOptions().Normalize().Deadline.Value); + + var deadline = DateTime.UtcNow; + var propagationToken1 = new ContextPropagationToken(CallSafeHandle.NullInstance, deadline, CancellationToken.None, + new ContextPropagationOptions(propagateDeadline: true, propagateCancellation: false)); + Assert.AreEqual(deadline, new CallOptions(propagationToken: propagationToken1).Normalize().Deadline.Value); + Assert.Throws(typeof(ArgumentException), () => new CallOptions(deadline: deadline, propagationToken: propagationToken1).Normalize()); + + var token = new CancellationTokenSource().Token; + var propagationToken2 = new ContextPropagationToken(CallSafeHandle.NullInstance, deadline, token, + new ContextPropagationOptions(propagateDeadline: false, propagateCancellation: true)); + Assert.AreEqual(token, new CallOptions(propagationToken: propagationToken2).Normalize().CancellationToken); + Assert.Throws(typeof(ArgumentException), () => new CallOptions(cancellationToken: token, propagationToken: propagationToken2).Normalize()); + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs b/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs index 52be77c846..d2b5a436fd 100644 --- a/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs +++ b/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs @@ -38,7 +38,7 @@ using Grpc.Core.Internal; using Grpc.Core.Utils; using NUnit.Framework; -namespace Grpc.Core.Internal.Tests +namespace Grpc.Core.Tests { public class ChannelOptionsTest { diff --git a/src/csharp/Grpc.Core.Tests/ChannelTest.cs b/src/csharp/Grpc.Core.Tests/ChannelTest.cs index f4ae9abefd..ed0ec14df5 100644 --- a/src/csharp/Grpc.Core.Tests/ChannelTest.cs +++ b/src/csharp/Grpc.Core.Tests/ChannelTest.cs @@ -48,6 +48,17 @@ namespace Grpc.Core.Tests } [Test] + public void Constructor_RejectsDuplicateOptions() + { + var options = new ChannelOption[] + { + new ChannelOption(ChannelOptions.PrimaryUserAgentString, "ABC"), + new ChannelOption(ChannelOptions.PrimaryUserAgentString, "XYZ") + }; + Assert.Throws(typeof(ArgumentException), () => new Channel("127.0.0.1", ChannelCredentials.Insecure, options)); + } + + [Test] public void State_IdleAfterCreation() { var channel = new Channel("localhost", ChannelCredentials.Insecure); diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs index 25a5a27c8e..e324471120 100644 --- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs +++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs @@ -32,6 +32,7 @@ #endregion using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; @@ -144,6 +145,48 @@ namespace Grpc.Core.Tests var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall()); await call.RequestStream.WriteAllAsync(new string[] { "A", "B", "C" }); Assert.AreEqual("ABC", await call.ResponseAsync); + + Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode); + Assert.IsNotNull(call.GetTrailers()); + } + + [Test] + public async Task ServerStreamingCall() + { + helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) => + { + foreach (string response in request.Split(new []{' '})) + { + await responseStream.WriteAsync(response); + } + context.ResponseTrailers.Add("xyz", ""); + }); + + var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "A B C"); + 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); + } + + [Test] + public async Task DuplexStreamingCall() + { + helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) => + { + while (await requestStream.MoveNext()) + { + await responseStream.WriteAsync(requestStream.Current); + } + context.ResponseTrailers.Add("xyz", "xyz-value"); + }); + + var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall()); + await call.RequestStream.WriteAllAsync(new string[] { "A", "B", "C" }); + 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); } [Test] @@ -201,7 +244,7 @@ namespace Grpc.Core.Tests Assert.AreEqual(headers[1].Key, trailers[1].Key); CollectionAssert.AreEqual(headers[1].ValueBytes, trailers[1].ValueBytes); } - + [Test] public void UnknownMethodHandler() { @@ -219,27 +262,27 @@ namespace Grpc.Core.Tests } [Test] - public void UserAgentStringPresent() + public void ServerCallContext_PeerInfoPresent() { helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => { - return context.RequestHeaders.Where(entry => entry.Key == "user-agent").Single().Value; + return context.Peer; }); - string userAgent = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"); - Assert.IsTrue(userAgent.StartsWith("grpc-csharp/")); + string peer = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"); + Assert.IsTrue(peer.Contains(Host)); } [Test] - public void PeerInfoPresent() + public void ServerCallContext_HostAndMethodPresent() { helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => { - return context.Peer; + Assert.IsTrue(context.Host.Contains(Host)); + Assert.AreEqual("/tests.Test/Unary", context.Method); + return "PASS"; }); - - string peer = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"); - Assert.IsTrue(peer.Contains(Host)); + Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc")); } [Test] diff --git a/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs index 2db3f286f7..90c510ec61 100644 --- a/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs +++ b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs @@ -69,11 +69,19 @@ namespace Grpc.Core.Tests [Test] public async Task PropagateCancellation() { + var readyToCancelTcs = new TaskCompletionSource<object>(); + var successTcs = new TaskCompletionSource<string>(); + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => { - // check that we didn't obtain the default cancellation token. - Assert.IsTrue(context.CancellationToken.CanBeCanceled); - return "PASS"; + readyToCancelTcs.SetResult(null); // child call running, ready to parent call + + while (!context.CancellationToken.IsCancellationRequested) + { + await Task.Delay(10); + } + successTcs.SetResult("CHILD_CALL_CANCELLED"); + return ""; }); helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) => @@ -82,13 +90,23 @@ namespace Grpc.Core.Tests Assert.IsNotNull(propagationToken.ParentCall); var callOptions = new CallOptions(propagationToken: propagationToken); - return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz"); + try + { + await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz"); + } + catch(RpcException) + { + // Child call will get cancelled, eat the exception. + } + return ""; }); var cts = new CancellationTokenSource(); - var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token))); - await call.RequestStream.CompleteAsync(); - Assert.AreEqual("PASS", await call); + var parentCall = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token))); + await readyToCancelTcs.Task; + cts.Cancel(); + Assert.Throws(typeof(RpcException), async () => await parentCall); + Assert.AreEqual("CHILD_CALL_CANCELLED", await successTcs.Task); } [Test] diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj index e5ffa31989..a171855ee0 100644 --- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj +++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj @@ -64,6 +64,8 @@ <Link>Version.cs</Link> </Compile> <Compile Include="CallCredentialsTest.cs" /> + <Compile Include="CallOptionsTest.cs" /> + <Compile Include="UserAgentStringTest.cs" /> <Compile Include="FakeCredentials.cs" /> <Compile Include="MarshallingErrorsTest.cs" /> <Compile Include="ChannelCredentialsTest.cs" /> diff --git a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs index 567e04eddc..3047314345 100644 --- a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs +++ b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs @@ -32,6 +32,7 @@ #endregion using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; @@ -52,6 +53,7 @@ namespace Grpc.Core.Tests readonly string host; readonly ServerServiceDefinition serviceDefinition; + readonly IEnumerable<ChannelOption> channelOptions; readonly Method<string, string> unaryMethod; readonly Method<string, string> clientStreamingMethod; @@ -66,9 +68,10 @@ namespace Grpc.Core.Tests Server server; Channel channel; - public MockServiceHelper(string host = null, Marshaller<string> marshaller = null) + public MockServiceHelper(string host = null, Marshaller<string> marshaller = null, IEnumerable<ChannelOption> channelOptions = null) { this.host = host ?? "localhost"; + this.channelOptions = channelOptions; marshaller = marshaller ?? Marshallers.StringMarshaller; unaryMethod = new Method<string, string>( @@ -154,7 +157,7 @@ namespace Grpc.Core.Tests { if (channel == null) { - channel = new Channel(Host, GetServer().Ports.Single().BoundPort, ChannelCredentials.Insecure); + channel = new Channel(Host, GetServer().Ports.Single().BoundPort, ChannelCredentials.Insecure, channelOptions); } return channel; } diff --git a/src/csharp/Grpc.Core.Tests/UserAgentStringTest.cs b/src/csharp/Grpc.Core.Tests/UserAgentStringTest.cs new file mode 100644 index 0000000000..cc830086a6 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/UserAgentStringTest.cs @@ -0,0 +1,101 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.Core.Internal; +using Grpc.Core.Profiling; +using Grpc.Core.Utils; +using NUnit.Framework; + +namespace Grpc.Core.Tests +{ + public class UserAgentStringTest + { + const string Host = "127.0.0.1"; + + MockServiceHelper helper; + Server server; + Channel channel; + + [TearDown] + public void Cleanup() + { + channel.ShutdownAsync().Wait(); + server.ShutdownAsync().Wait(); + } + + [Test] + public void DefaultUserAgentString() + { + helper = new MockServiceHelper(Host); + server = helper.GetServer(); + server.Start(); + channel = helper.GetChannel(); + + helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => + { + var userAgentString = context.RequestHeaders.First(m => (m.Key == "user-agent")).Value; + var parts = userAgentString.Split(new [] {' '}, 2); + Assert.AreEqual(string.Format("grpc-csharp/{0}", VersionInfo.CurrentVersion), parts[0]); + Assert.IsTrue(parts[1].StartsWith("grpc-c/")); + return Task.FromResult("PASS"); + }); + Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "")); + } + + [Test] + public void ApplicationUserAgentString() + { + helper = new MockServiceHelper(Host, + channelOptions: new[] { new ChannelOption(ChannelOptions.PrimaryUserAgentString, "XYZ") }); + server = helper.GetServer(); + server.Start(); + channel = helper.GetChannel(); + + channel = helper.GetChannel(); + helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => + { + var userAgentString = context.RequestHeaders.First(m => (m.Key == "user-agent")).Value; + var parts = userAgentString.Split(new[] { ' ' }, 3); + Assert.AreEqual("XYZ", parts[0]); + return Task.FromResult("PASS"); + }); + Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "")); + } + } +} diff --git a/src/csharp/Grpc.Core/AsyncAuthInterceptor.cs b/src/csharp/Grpc.Core/AsyncAuthInterceptor.cs new file mode 100644 index 0000000000..5c9ab04812 --- /dev/null +++ b/src/csharp/Grpc.Core/AsyncAuthInterceptor.cs @@ -0,0 +1,84 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using Grpc.Core.Internal; +using Grpc.Core.Utils; + +namespace Grpc.Core +{ + /// <summary> + /// Asynchronous authentication interceptor for <see cref="CallCredentials"/>. + /// </summary> + /// <param name="context">The interceptor context.</param> + /// <param name="metadata">Metadata to populate with entries that will be added to outgoing call's headers.</param> + /// <returns></returns> + public delegate Task AsyncAuthInterceptor(AuthInterceptorContext context, Metadata metadata); + + /// <summary> + /// Context for an RPC being intercepted by <see cref="AsyncAuthInterceptor"/>. + /// </summary> + public class AuthInterceptorContext + { + readonly string serviceUrl; + readonly string methodName; + + /// <summary> + /// Initializes a new instance of <c>AuthInterceptorContext</c>. + /// </summary> + public AuthInterceptorContext(string serviceUrl, string methodName) + { + this.serviceUrl = Preconditions.CheckNotNull(serviceUrl); + this.methodName = Preconditions.CheckNotNull(methodName); + } + + /// <summary> + /// The fully qualified service URL for the RPC being called. + /// </summary> + public string ServiceUrl + { + get { return serviceUrl; } + } + + /// <summary> + /// The method name of the RPC being called. + /// </summary> + public string MethodName + { + get { return methodName; } + } + } +} diff --git a/src/csharp/Grpc.Core/CallCredentials.cs b/src/csharp/Grpc.Core/CallCredentials.cs index 5ea179dfea..a71c8904fe 100644 --- a/src/csharp/Grpc.Core/CallCredentials.cs +++ b/src/csharp/Grpc.Core/CallCredentials.cs @@ -41,14 +41,6 @@ using Grpc.Core.Utils; namespace Grpc.Core { /// <summary> - /// Asynchronous authentication interceptor for <see cref="CallCredentials"/>. - /// </summary> - /// <param name="authUri">URL of a service to which current remote call needs to authenticate</param> - /// <param name="metadata">Metadata to populate with entries that will be added to outgoing call's headers.</param> - /// <returns></returns> - public delegate Task AsyncAuthInterceptor(string authUri, Metadata metadata); - - /// <summary> /// Client-side call credentials. Provide authorization with per-call granularity. /// </summary> public abstract class CallCredentials diff --git a/src/csharp/Grpc.Core/CallOptions.cs b/src/csharp/Grpc.Core/CallOptions.cs index c0f94c63c2..1fda80cb90 100644 --- a/src/csharp/Grpc.Core/CallOptions.cs +++ b/src/csharp/Grpc.Core/CallOptions.cs @@ -184,6 +184,7 @@ namespace Grpc.Core { Preconditions.CheckArgument(!newOptions.cancellationToken.CanBeCanceled, "Cannot propagate cancellation token from parent call. The cancellation token has already been set to a non-default value."); + newOptions.cancellationToken = propagationToken.ParentCancellationToken; } } diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs index f5eec969f5..d8d43c7998 100644 --- a/src/csharp/Grpc.Core/Channel.cs +++ b/src/csharp/Grpc.Core/Channel.cs @@ -32,8 +32,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; -using System.Threading; using System.Threading.Tasks; using Grpc.Core.Internal; @@ -57,7 +55,7 @@ namespace Grpc.Core readonly string target; readonly GrpcEnvironment environment; readonly ChannelSafeHandle handle; - readonly List<ChannelOption> options; + readonly Dictionary<string, ChannelOption> options; bool shutdownRequested; @@ -71,12 +69,12 @@ namespace Grpc.Core public Channel(string target, ChannelCredentials credentials, IEnumerable<ChannelOption> options = null) { this.target = Preconditions.CheckNotNull(target, "target"); + this.options = CreateOptionsDictionary(options); + EnsureUserAgentChannelOption(this.options); this.environment = GrpcEnvironment.AddRef(); - this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>(); - EnsureUserAgentChannelOption(this.options); using (var nativeCredentials = credentials.ToNativeCredentials()) - using (var nativeChannelArgs = ChannelOptions.CreateChannelArgs(this.options)) + using (var nativeChannelArgs = ChannelOptions.CreateChannelArgs(this.options.Values)) { if (nativeCredentials != null) { @@ -173,7 +171,7 @@ namespace Grpc.Core { throw new OperationCanceledException("Channel has reached FatalFailure state."); } - await WaitForStateChangedAsync(currentState, deadline); + await WaitForStateChangedAsync(currentState, deadline).ConfigureAwait(false); currentState = handle.CheckConnectivityState(false); } } @@ -198,7 +196,7 @@ namespace Grpc.Core handle.Dispose(); - await Task.Run(() => GrpcEnvironment.Release()); + await Task.Run(() => GrpcEnvironment.Release()).ConfigureAwait(false); } internal ChannelSafeHandle Handle @@ -233,18 +231,36 @@ namespace Grpc.Core activeCallCounter.Decrement(); } - private static void EnsureUserAgentChannelOption(List<ChannelOption> options) + private static void EnsureUserAgentChannelOption(Dictionary<string, ChannelOption> options) { - if (!options.Any((option) => option.Name == ChannelOptions.PrimaryUserAgentString)) + var key = ChannelOptions.PrimaryUserAgentString; + var userAgentString = ""; + + ChannelOption option; + if (options.TryGetValue(key, out option)) { - options.Add(new ChannelOption(ChannelOptions.PrimaryUserAgentString, GetUserAgentString())); - } + // user-provided userAgentString needs to be at the beginning + userAgentString = option.StringValue + " "; + }; + + // TODO(jtattermusch): it would be useful to also provide .NET/mono version. + userAgentString += string.Format("grpc-csharp/{0}", VersionInfo.CurrentVersion); + + options[ChannelOptions.PrimaryUserAgentString] = new ChannelOption(key, userAgentString); } - private static string GetUserAgentString() + private static Dictionary<string, ChannelOption> CreateOptionsDictionary(IEnumerable<ChannelOption> options) { - // TODO(jtattermusch): it would be useful to also provide .NET/mono version. - return string.Format("grpc-csharp/{0}", VersionInfo.CurrentVersion); + var dict = new Dictionary<string, ChannelOption>(); + if (options == null) + { + return dict; + } + foreach (var option in options) + { + dict.Add(option.Name, option); + } + return dict; } } } diff --git a/src/csharp/Grpc.Core/ChannelOptions.cs b/src/csharp/Grpc.Core/ChannelOptions.cs index f5ef63af54..d70673cf78 100644 --- a/src/csharp/Grpc.Core/ChannelOptions.cs +++ b/src/csharp/Grpc.Core/ChannelOptions.cs @@ -169,7 +169,7 @@ namespace Grpc.Core /// Creates native object for a collection of channel options. /// </summary> /// <returns>The native channel arguments.</returns> - internal static ChannelArgsSafeHandle CreateChannelArgs(List<ChannelOption> options) + internal static ChannelArgsSafeHandle CreateChannelArgs(ICollection<ChannelOption> options) { if (options == null || options.Count == 0) { @@ -179,9 +179,9 @@ namespace Grpc.Core try { nativeArgs = ChannelArgsSafeHandle.Create(options.Count); - for (int i = 0; i < options.Count; i++) + int i = 0; + foreach (var option in options) { - var option = options[i]; if (option.Type == ChannelOption.OptionType.Integer) { nativeArgs.SetInteger(i, option.Name, option.IntValue); @@ -194,6 +194,7 @@ namespace Grpc.Core { throw new InvalidOperationException("Unknown option type"); } + i++; } return nativeArgs; } diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj index c4f799297d..5b3da7c6c9 100644 --- a/src/csharp/Grpc.Core/Grpc.Core.csproj +++ b/src/csharp/Grpc.Core/Grpc.Core.csproj @@ -1,7 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="..\packages\grpc.dependencies.zlib.redist.1.2.8.9\build\portable-net45\grpc.dependencies.zlib.redist.props" Condition="Exists('..\packages\grpc.dependencies.zlib.redist.1.2.8.9\build\portable-net45\grpc.dependencies.zlib.redist.props')" /> - <Import Project="..\packages\grpc.dependencies.openssl.redist.1.0.2.2\build\portable-net45\grpc.dependencies.openssl.redist.props" Condition="Exists('..\packages\grpc.dependencies.openssl.redist.1.0.2.2\build\portable-net45\grpc.dependencies.openssl.redist.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> @@ -10,7 +8,7 @@ <RootNamespace>Grpc.Core</RootNamespace> <AssemblyName>Grpc.Core</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> - <NuGetPackageImportStamp>8bb563fb</NuGetPackageImportStamp> + <NuGetPackageImportStamp>be3e9d03</NuGetPackageImportStamp> <DocumentationFile>bin\$(Configuration)\Grpc.Core.Xml</DocumentationFile> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> @@ -48,6 +46,7 @@ <ItemGroup> <Compile Include="AsyncDuplexStreamingCall.cs" /> <Compile Include="AsyncServerStreamingCall.cs" /> + <Compile Include="AsyncAuthInterceptor.cs" /> <Compile Include="CallCredentials.cs" /> <Compile Include="IClientStreamWriter.cs" /> <Compile Include="Internal\NativeMetadataCredentialsPlugin.cs" /> @@ -147,15 +146,11 @@ <PropertyGroup> <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> </PropertyGroup> - <Error Condition="!Exists('..\packages\grpc.dependencies.openssl.redist.1.0.2.2\build\portable-net45\grpc.dependencies.openssl.redist.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\grpc.dependencies.openssl.redist.1.0.2.2\build\portable-net45\grpc.dependencies.openssl.redist.props'))" /> - <Error Condition="!Exists('..\packages\grpc.dependencies.openssl.redist.1.0.2.2\build\portable-net45\grpc.dependencies.openssl.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\grpc.dependencies.openssl.redist.1.0.2.2\build\portable-net45\grpc.dependencies.openssl.redist.targets'))" /> - <Error Condition="!Exists('..\packages\grpc.dependencies.zlib.redist.1.2.8.9\build\portable-net45\grpc.dependencies.zlib.redist.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\grpc.dependencies.zlib.redist.1.2.8.9\build\portable-net45\grpc.dependencies.zlib.redist.props'))" /> - <Error Condition="!Exists('..\packages\grpc.dependencies.zlib.redist.1.2.8.9\build\portable-net45\grpc.dependencies.zlib.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\grpc.dependencies.zlib.redist.1.2.8.9\build\portable-net45\grpc.dependencies.zlib.redist.targets'))" /> + <Error Condition="!Exists('..\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.openssl.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.openssl.redist.targets'))" /> + <Error Condition="!Exists('..\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.zlib.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.zlib.redist.targets'))" /> </Target> - <Import Project="..\packages\grpc.dependencies.openssl.redist.1.0.2.2\build\portable-net45\grpc.dependencies.openssl.redist.targets" Condition="Exists('..\packages\grpc.dependencies.openssl.redist.1.0.2.2\build\portable-net45\grpc.dependencies.openssl.redist.targets')" /> - <Import Project="..\packages\grpc.dependencies.zlib.redist.1.2.8.9\build\portable-net45\grpc.dependencies.zlib.redist.targets" Condition="Exists('..\packages\grpc.dependencies.zlib.redist.1.2.8.9\build\portable-net45\grpc.dependencies.zlib.redist.targets')" /> + <Import Project="..\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.openssl.redist.targets" Condition="Exists('..\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.openssl.redist.targets')" /> + <Import Project="..\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.zlib.redist.targets" Condition="Exists('..\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.zlib.redist.targets')" /> + <ItemGroup /> <ItemGroup /> - <ItemGroup> - <Folder Include="Profiling\" /> - </ItemGroup> </Project> diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs index 953f61aa1e..92f8d77e85 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs @@ -238,20 +238,6 @@ namespace Grpc.Core.Internal } } - protected Exception TrySerialize(TWrite msg, out byte[] payload) - { - try - { - payload = serializer(msg); - return null; - } - catch (Exception e) - { - payload = null; - return e; - } - } - protected Exception TryDeserialize(byte[] payload, out TRead msg) { using (Profilers.ForCurrentThread().NewScope("AsyncCallBase.TryDeserialize")) diff --git a/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs b/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs index b4a7335c7c..d6e34a0f04 100644 --- a/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs +++ b/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs @@ -70,12 +70,12 @@ namespace Grpc.Core.Internal } var taskSource = new AsyncCompletionTaskSource<TResponse>(); call.StartReadMessage(taskSource.CompletionDelegate); - var result = await taskSource.Task; + var result = await taskSource.Task.ConfigureAwait(false); this.current = result; if (result == null) { - await call.StreamingCallFinishedTask; + await call.StreamingCallFinishedTask.ConfigureAwait(false); return false; } return true; diff --git a/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs b/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs index 2a571cd6c9..8bb646d303 100644 --- a/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs +++ b/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs @@ -38,7 +38,7 @@ using Grpc.Core.Utils; namespace Grpc.Core.Internal { - internal delegate void NativeMetadataInterceptor(IntPtr statePtr, IntPtr serviceUrlPtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy); + internal delegate void NativeMetadataInterceptor(IntPtr statePtr, IntPtr serviceUrlPtr, IntPtr methodNamePtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy); internal class NativeMetadataCredentialsPlugin { @@ -71,7 +71,7 @@ namespace Grpc.Core.Internal get { return credentials; } } - private void NativeMetadataInterceptorHandler(IntPtr statePtr, IntPtr serviceUrlPtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy) + private void NativeMetadataInterceptorHandler(IntPtr statePtr, IntPtr serviceUrlPtr, IntPtr methodNamePtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy) { if (isDestroy) { @@ -81,8 +81,9 @@ namespace Grpc.Core.Internal try { - string serviceUrl = Marshal.PtrToStringAnsi(serviceUrlPtr); - StartGetMetadata(serviceUrl, callbackPtr, userDataPtr); + var context = new AuthInterceptorContext(Marshal.PtrToStringAnsi(serviceUrlPtr), + Marshal.PtrToStringAnsi(methodNamePtr)); + StartGetMetadata(context, callbackPtr, userDataPtr); } catch (Exception e) { @@ -91,12 +92,12 @@ namespace Grpc.Core.Internal } } - private async void StartGetMetadata(string serviceUrl, IntPtr callbackPtr, IntPtr userDataPtr) + private async void StartGetMetadata(AuthInterceptorContext context, IntPtr callbackPtr, IntPtr userDataPtr) { try { var metadata = new Metadata(); - await interceptor(serviceUrl, metadata); + await interceptor(context, metadata).ConfigureAwait(false); using (var metadataArray = MetadataArraySafeHandle.Create(metadata)) { diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs index 59f4c5727c..de66759b94 100644 --- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs +++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs @@ -78,13 +78,13 @@ namespace Grpc.Core.Internal var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken); try { - Preconditions.CheckArgument(await requestStream.MoveNext()); + Preconditions.CheckArgument(await requestStream.MoveNext().ConfigureAwait(false)); 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 result = await handler(request, context); + Preconditions.CheckArgument(!await requestStream.MoveNext().ConfigureAwait(false)); + var result = await handler(request, context).ConfigureAwait(false); status = context.Status; - await responseStream.WriteAsync(result); + await responseStream.WriteAsync(result).ConfigureAwait(false); } catch (Exception e) { @@ -93,13 +93,13 @@ namespace Grpc.Core.Internal } try { - await responseStream.WriteStatusAsync(status, context.ResponseTrailers); + await responseStream.WriteStatusAsync(status, context.ResponseTrailers).ConfigureAwait(false); } catch (OperationCanceledException) { // Call has been already cancelled. } - await finishedTask; + await finishedTask.ConfigureAwait(false); } } @@ -134,11 +134,11 @@ namespace Grpc.Core.Internal var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken); try { - Preconditions.CheckArgument(await requestStream.MoveNext()); + Preconditions.CheckArgument(await requestStream.MoveNext().ConfigureAwait(false)); var request = requestStream.Current; // TODO(jtattermusch): we need to read the full stream so that native callhandle gets deallocated. - Preconditions.CheckArgument(!await requestStream.MoveNext()); - await handler(request, responseStream, context); + Preconditions.CheckArgument(!await requestStream.MoveNext().ConfigureAwait(false)); + await handler(request, responseStream, context).ConfigureAwait(false); status = context.Status; } catch (Exception e) @@ -149,13 +149,13 @@ namespace Grpc.Core.Internal try { - await responseStream.WriteStatusAsync(status, context.ResponseTrailers); + await responseStream.WriteStatusAsync(status, context.ResponseTrailers).ConfigureAwait(false); } catch (OperationCanceledException) { // Call has been already cancelled. } - await finishedTask; + await finishedTask.ConfigureAwait(false); } } @@ -190,11 +190,11 @@ namespace Grpc.Core.Internal var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken); try { - var result = await handler(requestStream, context); + var result = await handler(requestStream, context).ConfigureAwait(false); status = context.Status; try { - await responseStream.WriteAsync(result); + await responseStream.WriteAsync(result).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -209,13 +209,13 @@ namespace Grpc.Core.Internal try { - await responseStream.WriteStatusAsync(status, context.ResponseTrailers); + await responseStream.WriteStatusAsync(status, context.ResponseTrailers).ConfigureAwait(false); } catch (OperationCanceledException) { // Call has been already cancelled. } - await finishedTask; + await finishedTask.ConfigureAwait(false); } } @@ -250,7 +250,7 @@ namespace Grpc.Core.Internal var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken); try { - await handler(requestStream, responseStream, context); + await handler(requestStream, responseStream, context).ConfigureAwait(false); status = context.Status; } catch (Exception e) @@ -260,13 +260,13 @@ namespace Grpc.Core.Internal } try { - await responseStream.WriteStatusAsync(status, context.ResponseTrailers); + await responseStream.WriteStatusAsync(status, context.ResponseTrailers).ConfigureAwait(false); } catch (OperationCanceledException) { // Call has been already cancelled. } - await finishedTask; + await finishedTask.ConfigureAwait(false); } } @@ -284,8 +284,8 @@ namespace Grpc.Core.Internal var finishedTask = asyncCall.ServerSideCallAsync(); var responseStream = new ServerResponseStream<byte[], byte[]>(asyncCall); - await responseStream.WriteStatusAsync(new Status(StatusCode.Unimplemented, "No such method."), Metadata.Empty); - await finishedTask; + await responseStream.WriteStatusAsync(new Status(StatusCode.Unimplemented, ""), Metadata.Empty).ConfigureAwait(false); + await finishedTask.ConfigureAwait(false); } } diff --git a/src/csharp/Grpc.Core/Internal/ServerRequestStream.cs b/src/csharp/Grpc.Core/Internal/ServerRequestStream.cs index 3fccb88abb..e7be82c318 100644 --- a/src/csharp/Grpc.Core/Internal/ServerRequestStream.cs +++ b/src/csharp/Grpc.Core/Internal/ServerRequestStream.cs @@ -70,7 +70,7 @@ namespace Grpc.Core.Internal } var taskSource = new AsyncCompletionTaskSource<TRequest>(); call.StartReadMessage(taskSource.CompletionDelegate); - var result = await taskSource.Task; + var result = await taskSource.Task.ConfigureAwait(false); this.current = result; return result != null; } diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs index 7c94d21561..0fadabe554 100644 --- a/src/csharp/Grpc.Core/Server.cs +++ b/src/csharp/Grpc.Core/Server.cs @@ -148,10 +148,10 @@ namespace Grpc.Core } handle.ShutdownAndNotify(HandleServerShutdown, environment); - await shutdownTcs.Task; + await shutdownTcs.Task.ConfigureAwait(false); DisposeHandle(); - await Task.Run(() => GrpcEnvironment.Release()); + await Task.Run(() => GrpcEnvironment.Release()).ConfigureAwait(false); } /// <summary> @@ -169,7 +169,7 @@ namespace Grpc.Core handle.ShutdownAndNotify(HandleServerShutdown, environment); handle.CancelAllCalls(); - await shutdownTcs.Task; + await shutdownTcs.Task.ConfigureAwait(false); DisposeHandle(); } @@ -268,7 +268,7 @@ namespace Grpc.Core { callHandler = NoSuchMethodCallHandler.Instance; } - await callHandler.HandleCall(newRpc, environment); + await callHandler.HandleCall(newRpc, environment).ConfigureAwait(false); } catch (Exception e) { @@ -288,7 +288,7 @@ namespace Grpc.Core // after server shutdown, the callback returns with null call if (!newRpc.Call.IsInvalid) { - Task.Run(async () => await HandleCallAsync(newRpc)); + Task.Run(async () => await HandleCallAsync(newRpc)).ConfigureAwait(false); } } diff --git a/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs b/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs index cdf1e51026..02a47568e7 100644 --- a/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs +++ b/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs @@ -48,9 +48,9 @@ namespace Grpc.Core.Utils public static async Task ForEachAsync<T>(this IAsyncStreamReader<T> streamReader, Func<T, Task> asyncAction) where T : class { - while (await streamReader.MoveNext()) + while (await streamReader.MoveNext().ConfigureAwait(false)) { - await asyncAction(streamReader.Current); + await asyncAction(streamReader.Current).ConfigureAwait(false); } } @@ -61,7 +61,7 @@ namespace Grpc.Core.Utils where T : class { var result = new List<T>(); - while (await streamReader.MoveNext()) + while (await streamReader.MoveNext().ConfigureAwait(false)) { result.Add(streamReader.Current); } @@ -77,11 +77,11 @@ namespace Grpc.Core.Utils { foreach (var element in elements) { - await streamWriter.WriteAsync(element); + await streamWriter.WriteAsync(element).ConfigureAwait(false); } if (complete) { - await streamWriter.CompleteAsync(); + await streamWriter.CompleteAsync().ConfigureAwait(false); } } @@ -93,7 +93,7 @@ namespace Grpc.Core.Utils { foreach (var element in elements) { - await streamWriter.WriteAsync(element); + await streamWriter.WriteAsync(element).ConfigureAwait(false); } } } diff --git a/src/csharp/Grpc.Core/packages.config b/src/csharp/Grpc.Core/packages.config index 9b12b9b096..89600744a7 100644 --- a/src/csharp/Grpc.Core/packages.config +++ b/src/csharp/Grpc.Core/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="grpc.dependencies.openssl.redist" version="1.0.2.2" targetFramework="net45" /> - <package id="grpc.dependencies.zlib.redist" version="1.2.8.9" targetFramework="net45" /> + <package id="grpc.dependencies.openssl.redist" version="1.0.204.1" targetFramework="net45" /> + <package id="grpc.dependencies.zlib.redist" version="1.2.8.10" targetFramework="net45" /> <package id="Ix-Async" version="1.2.3" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/src/csharp/Grpc.Examples/Grpc.Examples.csproj b/src/csharp/Grpc.Examples/Grpc.Examples.csproj index 55462e02fd..53b2bb78c4 100644 --- a/src/csharp/Grpc.Examples/Grpc.Examples.csproj +++ b/src/csharp/Grpc.Examples/Grpc.Examples.csproj @@ -66,6 +66,5 @@ </ItemGroup> <ItemGroup> <None Include="packages.config" /> - <None Include="proto\math.proto" /> </ItemGroup> -</Project>
\ No newline at end of file +</Project> diff --git a/src/csharp/Grpc.Examples/proto/math.proto b/src/csharp/Grpc.Examples/proto/math.proto deleted file mode 100644 index 311e148c02..0000000000 --- a/src/csharp/Grpc.Examples/proto/math.proto +++ /dev/null @@ -1,80 +0,0 @@ - -// Copyright 2015, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -syntax = "proto3"; - -package math; - -message DivArgs { - int64 dividend = 1; - int64 divisor = 2; -} - -message DivReply { - int64 quotient = 1; - int64 remainder = 2; -} - -message FibArgs { - int64 limit = 1; -} - -message Num { - int64 num = 1; -} - -message FibReply { - int64 count = 1; -} - -service Math { - // Div divides args.dividend by args.divisor and returns the quotient and - // remainder. - rpc Div (DivArgs) returns (DivReply) { - } - - // DivMany accepts an arbitrary number of division args from the client stream - // and sends back the results in the reply stream. The stream continues until - // the client closes its end; the server does the same after sending all the - // replies. The stream ends immediately if either end aborts. - rpc DivMany (stream DivArgs) returns (stream DivReply) { - } - - // Fib generates numbers in the Fibonacci sequence. If args.limit > 0, Fib - // generates up to limit numbers; otherwise it continues until the call is - // canceled. Unlike Fib above, Fib has no final FibReply. - rpc Fib (FibArgs) returns (stream Num) { - } - - // Sum sums a stream of numbers, returning the final result once the stream - // is closed. - rpc Sum (stream Num) returns (Num) { - } -} diff --git a/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj index 8fce5d39aa..4e775a7a0c 100644 --- a/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj +++ b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj @@ -65,7 +65,6 @@ <ItemGroup> <None Include="Grpc.HealthCheck.nuspec" /> <None Include="packages.config" /> - <None Include="proto\health.proto" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\Grpc.Core\Grpc.Core.csproj"> @@ -81,4 +80,4 @@ <Target Name="AfterBuild"> </Target> --> -</Project>
\ No newline at end of file +</Project> diff --git a/src/csharp/Grpc.HealthCheck/proto/health.proto b/src/csharp/Grpc.HealthCheck/proto/health.proto deleted file mode 100644 index 01aa3fcf57..0000000000 --- a/src/csharp/Grpc.HealthCheck/proto/health.proto +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2015, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// TODO(jtattermusch): switch to proto3 once C# supports that. -syntax = "proto3"; - -package grpc.health.v1alpha; -option csharp_namespace = "Grpc.Health.V1Alpha"; - -message HealthCheckRequest { - string host = 1; - string service = 2; -} - -message HealthCheckResponse { - enum ServingStatus { - UNKNOWN = 0; - SERVING = 1; - NOT_SERVING = 2; - } - ServingStatus status = 1; -} - -service Health { - rpc Check(HealthCheckRequest) returns (HealthCheckResponse); -}
\ No newline at end of file diff --git a/src/csharp/Grpc.IntegrationTesting.QpsWorker/Grpc.IntegrationTesting.QpsWorker.csproj b/src/csharp/Grpc.IntegrationTesting.QpsWorker/Grpc.IntegrationTesting.QpsWorker.csproj index 8b245ade2e..342eead1a3 100644 --- a/src/csharp/Grpc.IntegrationTesting.QpsWorker/Grpc.IntegrationTesting.QpsWorker.csproj +++ b/src/csharp/Grpc.IntegrationTesting.QpsWorker/Grpc.IntegrationTesting.QpsWorker.csproj @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> @@ -57,4 +57,7 @@ <Name>Grpc.IntegrationTesting</Name> </ProjectReference> </ItemGroup> + <ItemGroup> + <None Include="app.config" /> + </ItemGroup> </Project>
\ No newline at end of file diff --git a/src/csharp/Grpc.IntegrationTesting.QpsWorker/app.config b/src/csharp/Grpc.IntegrationTesting.QpsWorker/app.config new file mode 100644 index 0000000000..940d25cae3 --- /dev/null +++ b/src/csharp/Grpc.IntegrationTesting.QpsWorker/app.config @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<configuration> + <runtime> + <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> + <dependentAssembly> + <assemblyIdentity name="System.Net.Http.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> + <bindingRedirect oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0" /> + </dependentAssembly> + </assemblyBinding> + </runtime> +</configuration>
\ No newline at end of file diff --git a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj index 012de45524..c48ac71630 100644 --- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj +++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj @@ -83,6 +83,7 @@ <Compile Include="..\Grpc.Core\Version.cs"> <Link>Version.cs</Link> </Compile> + <Compile Include="HeaderInterceptorTest.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Empty.cs" /> <Compile Include="Messages.cs" /> diff --git a/src/csharp/Grpc.IntegrationTesting/HeaderInterceptorTest.cs b/src/csharp/Grpc.IntegrationTesting/HeaderInterceptorTest.cs new file mode 100644 index 0000000000..1d758b7540 --- /dev/null +++ b/src/csharp/Grpc.IntegrationTesting/HeaderInterceptorTest.cs @@ -0,0 +1,113 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.Core.Utils; +using Grpc.Testing; +using NUnit.Framework; + +namespace Grpc.IntegrationTesting +{ + public class HeaderInterceptorTest + { + const string Host = "localhost"; + Server server; + Channel channel; + TestService.TestServiceClient client; + + [TestFixtureSetUp] + public void Init() + { + server = new Server + { + Services = { TestService.BindService(new TestServiceImpl()) }, + Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } } + }; + server.Start(); + + channel = new Channel(Host, server.Ports.Single().BoundPort, ChannelCredentials.Insecure); + client = TestService.NewClient(channel); + } + + [TestFixtureTearDown] + public void Cleanup() + { + channel.ShutdownAsync().Wait(); + server.ShutdownAsync().Wait(); + } + + [Test] + public async Task HeaderInterceptor_CreateMetadata() + { + var key = "x-grpc-test-echo-initial"; + client.HeaderInterceptor = new HeaderInterceptor((method, metadata) => + { + metadata.Add(key, "ABC"); + }); + + var call = client.UnaryCallAsync(new SimpleRequest()); + await call; + + var responseHeaders = await call.ResponseHeadersAsync; + Assert.AreEqual("ABC", responseHeaders.First((entry) => entry.Key == key).Value); + } + + [Test] + public async Task HeaderInterceptor_AppendMetadata() + { + var initialKey = "x-grpc-test-echo-initial"; + var trailingKey = "x-grpc-test-echo-trailing-bin"; + + client.HeaderInterceptor = new HeaderInterceptor((method, metadata) => + { + metadata.Add(initialKey, "ABC"); + }); + + var headers = new Metadata + { + { trailingKey, new byte[] {0xaa} } + }; + var call = client.UnaryCallAsync(new SimpleRequest(), headers: headers); + await call; + + var responseHeaders = await call.ResponseHeadersAsync; + Assert.AreEqual("ABC", responseHeaders.First((entry) => entry.Key == initialKey).Value); + CollectionAssert.AreEqual(new byte[] {0xaa}, call.GetTrailers().First((entry) => entry.Key == trailingKey).ValueBytes); + } + } +} diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs index 5eec11abf7..b0e33e49f7 100644 --- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs +++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs @@ -34,6 +34,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -130,8 +131,7 @@ namespace Grpc.IntegrationTesting }; } var channel = new Channel(options.ServerHost, options.ServerPort, credentials, channelOptions); - TestService.TestServiceClient client = new TestService.TestServiceClient(channel); - await RunTestCaseAsync(client, options); + await RunTestCaseAsync(channel, options); await channel.ShutdownAsync(); } @@ -159,8 +159,9 @@ namespace Grpc.IntegrationTesting return credentials; } - private async Task RunTestCaseAsync(TestService.TestServiceClient client, ClientOptions options) + private async Task RunTestCaseAsync(Channel channel, ClientOptions options) { + var client = new TestService.TestServiceClient(channel); switch (options.TestCase) { case "empty_unary": @@ -202,8 +203,14 @@ namespace Grpc.IntegrationTesting case "timeout_on_sleeping_server": await RunTimeoutOnSleepingServerAsync(client); break; - case "benchmark_empty_unary": - RunBenchmarkEmptyUnary(client); + case "custom_metadata": + await RunCustomMetadataAsync(client); + break; + case "status_code_and_message": + await RunStatusCodeAndMessageAsync(client); + break; + case "unimplemented_method": + RunUnimplementedMethod(new UnimplementedService.UnimplementedServiceClient(channel)); break; default: throw new ArgumentException("Unknown test case " + options.TestCase); @@ -227,7 +234,6 @@ namespace Grpc.IntegrationTesting ResponseSize = 314159, Payload = CreateZerosPayload(271828) }; - var response = client.UnaryCall(request); Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type); @@ -493,11 +499,95 @@ namespace Grpc.IntegrationTesting Console.WriteLine("Passed!"); } - // This is not an official interop test, but it's useful. - public static void RunBenchmarkEmptyUnary(TestService.ITestServiceClient client) + public static async Task RunCustomMetadataAsync(TestService.ITestServiceClient client) { - BenchmarkUtil.RunBenchmark(10000, 10000, - () => { client.EmptyCall(new Empty()); }); + Console.WriteLine("running custom_metadata"); + { + // step 1: test unary call + var request = new SimpleRequest + { + ResponseType = PayloadType.COMPRESSABLE, + ResponseSize = 314159, + Payload = CreateZerosPayload(271828) + }; + + var call = client.UnaryCallAsync(request, headers: CreateTestMetadata()); + await call.ResponseAsync; + + var responseHeaders = await call.ResponseHeadersAsync; + var responseTrailers = call.GetTrailers(); + + Assert.AreEqual("test_initial_metadata_value", responseHeaders.First((entry) => entry.Key == "x-grpc-test-echo-initial").Value); + CollectionAssert.AreEqual(new byte[] { 0xab, 0xab, 0xab }, responseTrailers.First((entry) => entry.Key == "x-grpc-test-echo-trailing-bin").ValueBytes); + } + + { + // step 2: test full duplex call + var request = new StreamingOutputCallRequest + { + ResponseType = PayloadType.COMPRESSABLE, + ResponseParameters = { new ResponseParameters { Size = 31415 } }, + Payload = CreateZerosPayload(27182) + }; + + var call = client.FullDuplexCall(headers: CreateTestMetadata()); + var responseHeaders = await call.ResponseHeadersAsync; + + await call.RequestStream.WriteAsync(request); + await call.RequestStream.CompleteAsync(); + await call.ResponseStream.ToListAsync(); + + var responseTrailers = call.GetTrailers(); + + Assert.AreEqual("test_initial_metadata_value", responseHeaders.First((entry) => entry.Key == "x-grpc-test-echo-initial").Value); + CollectionAssert.AreEqual(new byte[] { 0xab, 0xab, 0xab }, responseTrailers.First((entry) => entry.Key == "x-grpc-test-echo-trailing-bin").ValueBytes); + } + + Console.WriteLine("Passed!"); + } + + public static async Task RunStatusCodeAndMessageAsync(TestService.ITestServiceClient client) + { + Console.WriteLine("running status_code_and_message"); + var echoStatus = new EchoStatus + { + Code = 2, + Message = "test status message" + }; + + { + // step 1: test unary call + var request = new SimpleRequest { ResponseStatus = echoStatus }; + + var e = Assert.Throws<RpcException>(() => client.UnaryCall(request)); + Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode); + Assert.AreEqual(echoStatus.Message, e.Status.Detail); + } + + { + // step 2: test full duplex call + var request = new StreamingOutputCallRequest { ResponseStatus = echoStatus }; + + var call = client.FullDuplexCall(); + await call.RequestStream.WriteAsync(request); + await call.RequestStream.CompleteAsync(); + + var e = Assert.Throws<RpcException>(async () => await call.ResponseStream.ToListAsync()); + Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode); + Assert.AreEqual(echoStatus.Message, e.Status.Detail); + } + + Console.WriteLine("Passed!"); + } + + public static void RunUnimplementedMethod(UnimplementedService.IUnimplementedServiceClient client) + { + Console.WriteLine("running unimplemented_method"); + var e = Assert.Throws<RpcException>(() => client.UnimplementedCall(new Empty())); + + Assert.AreEqual(StatusCode.Unimplemented, e.Status.StatusCode); + Assert.AreEqual("", e.Status.Detail); + Console.WriteLine("Passed!"); } private static Payload CreateZerosPayload(int size) @@ -516,5 +606,14 @@ namespace Grpc.IntegrationTesting Assert.IsTrue(email.Length > 0); // spec requires nonempty client email. return email; } + + private static Metadata CreateTestMetadata() + { + return new Metadata + { + {"x-grpc-test-echo-initial", "test_initial_metadata_value"}, + {"x-grpc-test-echo-trailing-bin", new byte[] {0xab, 0xab, 0xab}} + }; + } } } diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs index 837ae74c45..5facb87971 100644 --- a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs +++ b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs @@ -128,9 +128,27 @@ namespace Grpc.IntegrationTesting } [Test] - public async Task TimeoutOnSleepingServerAsync() + public async Task TimeoutOnSleepingServer() { await InteropClient.RunTimeoutOnSleepingServerAsync(client); } + + [Test] + public async Task CustomMetadata() + { + await InteropClient.RunCustomMetadataAsync(client); + } + + [Test] + public async Task StatusCodeAndMessage() + { + await InteropClient.RunStatusCodeAndMessageAsync(client); + } + + [Test] + public void UnimplementedMethod() + { + InteropClient.RunUnimplementedMethod(UnimplementedService.NewClient(channel)); + } } } diff --git a/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs index 3d56678b99..35230f48c1 100644 --- a/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs +++ b/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs @@ -67,7 +67,7 @@ namespace Grpc.IntegrationTesting new ChannelOption(ChannelOptions.SslTargetNameOverride, TestCredentials.DefaultHostOverride) }; - var asyncAuthInterceptor = new AsyncAuthInterceptor(async (authUri, metadata) => + var asyncAuthInterceptor = new AsyncAuthInterceptor(async (context, metadata) => { await Task.Delay(100); // make sure the operation is asynchronous. metadata.Add("authorization", "SECRET_TOKEN"); diff --git a/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs b/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs index c5bfcf08c0..5a1b4cf319 100644 --- a/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs +++ b/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs @@ -33,6 +33,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Google.Protobuf; @@ -51,14 +52,20 @@ namespace Grpc.Testing return Task.FromResult(new Empty()); } - public Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context) + public async Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context) { + await EnsureEchoMetadataAsync(context); + EnsureEchoStatus(request.ResponseStatus, context); + var response = new SimpleResponse { Payload = CreateZerosPayload(request.ResponseSize) }; - return Task.FromResult(response); + return response; } public async Task StreamingOutputCall(StreamingOutputCallRequest request, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context) { + await EnsureEchoMetadataAsync(context); + EnsureEchoStatus(request.ResponseStatus, context); + foreach (var responseParam in request.ResponseParameters) { var response = new StreamingOutputCallResponse { Payload = CreateZerosPayload(responseParam.Size) }; @@ -68,6 +75,8 @@ namespace Grpc.Testing public async Task<StreamingInputCallResponse> StreamingInputCall(IAsyncStreamReader<StreamingInputCallRequest> requestStream, ServerCallContext context) { + await EnsureEchoMetadataAsync(context); + int sum = 0; await requestStream.ForEachAsync(async request => { @@ -78,8 +87,11 @@ namespace Grpc.Testing public async Task FullDuplexCall(IAsyncStreamReader<StreamingOutputCallRequest> requestStream, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context) { + await EnsureEchoMetadataAsync(context); + await requestStream.ForEachAsync(async request => { + EnsureEchoStatus(request.ResponseStatus, context); foreach (var responseParam in request.ResponseParameters) { var response = new StreamingOutputCallResponse { Payload = CreateZerosPayload(responseParam.Size) }; @@ -97,5 +109,28 @@ namespace Grpc.Testing { return new Payload { Body = ByteString.CopyFrom(new byte[size]) }; } + + private static async Task EnsureEchoMetadataAsync(ServerCallContext context) + { + var echoInitialList = context.RequestHeaders.Where((entry) => entry.Key == "x-grpc-test-echo-initial").ToList(); + if (echoInitialList.Any()) { + var entry = echoInitialList.Single(); + await context.WriteResponseHeadersAsync(new Metadata { entry }); + } + + var echoTrailingList = context.RequestHeaders.Where((entry) => entry.Key == "x-grpc-test-echo-trailing-bin").ToList(); + if (echoTrailingList.Any()) { + context.ResponseTrailers.Add(echoTrailingList.Single()); + } + } + + private static void EnsureEchoStatus(EchoStatus responseStatus, ServerCallContext context) + { + if (responseStatus != null) + { + var statusCode = (StatusCode)responseStatus.Code; + context.Status = new Status(statusCode, responseStatus.Message); + } + } } } diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c index b8705c49d3..0ef9be33a6 100644 --- a/src/csharp/ext/grpc_csharp_ext.c +++ b/src/csharp/ext/grpc_csharp_ext.c @@ -927,7 +927,8 @@ GPR_EXPORT void GPR_CALLTYPE grpcsharp_metadata_credentials_notify_from_plugin( } typedef void(GPR_CALLTYPE *grpcsharp_metadata_interceptor_func)( - void *state, const char *service_url, grpc_credentials_plugin_metadata_cb cb, + void *state, const char *service_url, const char *method_name, + grpc_credentials_plugin_metadata_cb cb, void *user_data, gpr_int32 is_destroy); static void grpcsharp_get_metadata_handler( @@ -935,13 +936,13 @@ static void grpcsharp_get_metadata_handler( grpc_credentials_plugin_metadata_cb cb, void *user_data) { grpcsharp_metadata_interceptor_func interceptor = (grpcsharp_metadata_interceptor_func)(gpr_intptr)state; - interceptor(state, context.service_url, cb, user_data, 0); + interceptor(state, context.service_url, context.method_name, cb, user_data, 0); } static void grpcsharp_metadata_credentials_destroy_handler(void *state) { grpcsharp_metadata_interceptor_func interceptor = (grpcsharp_metadata_interceptor_func)(gpr_intptr)state; - interceptor(state, NULL, NULL, NULL, 1); + interceptor(state, NULL, NULL, NULL, NULL, 1); } GPR_EXPORT grpc_call_credentials *GPR_CALLTYPE grpcsharp_metadata_credentials_create_from_plugin( diff --git a/src/csharp/generate_proto_csharp.sh b/src/csharp/generate_proto_csharp.sh index 92348d1394..4dbd9c3308 100755 --- a/src/csharp/generate_proto_csharp.sh +++ b/src/csharp/generate_proto_csharp.sh @@ -30,19 +30,19 @@ # Regenerates gRPC service stubs from proto files. set +e -cd $(dirname $0) +cd $(dirname $0)/../.. -PROTOC=../../bins/opt/protobuf/protoc -PLUGIN=protoc-gen-grpc=../../bins/opt/grpc_csharp_plugin -EXAMPLES_DIR=Grpc.Examples -TESTING_DIR=Grpc.IntegrationTesting -HEALTHCHECK_DIR=Grpc.HealthCheck +PROTOC=bins/opt/protobuf/protoc +PLUGIN=protoc-gen-grpc=bins/opt/grpc_csharp_plugin +EXAMPLES_DIR=src/csharp/Grpc.Examples +HEALTHCHECK_DIR=src/csharp/Grpc.HealthCheck +TESTING_DIR=src/csharp/Grpc.IntegrationTesting $PROTOC --plugin=$PLUGIN --csharp_out=$EXAMPLES_DIR --grpc_out=$EXAMPLES_DIR \ - -I $EXAMPLES_DIR/proto $EXAMPLES_DIR/proto/math.proto - -$PROTOC --plugin=$PLUGIN --csharp_out=$TESTING_DIR --grpc_out=$TESTING_DIR \ - -I ../.. ../../test/proto/*.proto ../../test/proto/benchmarks/*.proto + -I src/proto/math src/proto/math/math.proto $PROTOC --plugin=$PLUGIN --csharp_out=$HEALTHCHECK_DIR --grpc_out=$HEALTHCHECK_DIR \ - -I $HEALTHCHECK_DIR/proto $HEALTHCHECK_DIR/proto/health.proto + -I src/proto/grpc/health/v1alpha src/proto/grpc/health/v1alpha/health.proto + +$PROTOC --plugin=$PLUGIN --csharp_out=$TESTING_DIR --grpc_out=$TESTING_DIR \ + -I . test/proto/{empty,messages,test}.proto test/proto/benchmarks/*.proto |