diff --git a/src/Grpc.Net.Client/HttpClientCallInvoker.cs b/src/Grpc.Net.Client/HttpClientCallInvoker.cs index 27cc66b00..894f708ed 100644 --- a/src/Grpc.Net.Client/HttpClientCallInvoker.cs +++ b/src/Grpc.Net.Client/HttpClientCallInvoker.cs @@ -32,7 +32,7 @@ namespace Grpc.Net.Client public sealed class HttpClientCallInvoker : CallInvoker { private readonly HttpClient _client; - private readonly ILoggerFactory _loggerFactory; + internal ILoggerFactory LoggerFactory { get; } // Override the current time for unit testing internal ISystemClock Clock = SystemClock.Instance; @@ -50,7 +50,7 @@ public HttpClientCallInvoker(HttpClient client, ILoggerFactory? loggerFactory) } _client = client; - _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; + LoggerFactory = loggerFactory ?? NullLoggerFactory.Instance; Deadline = DateTime.MaxValue; } @@ -72,6 +72,16 @@ public HttpClientCallInvoker(HttpClient client, ILoggerFactory? loggerFactory) /// public DateTime Deadline { get; set; } + /// + /// Gets or sets the maximum message size in bytes that can be sent from the client. + /// + public int? SendMaxMessageSize { get; set; } + + /// + /// Gets or sets the maximum message size in bytes that can be received by the client. + /// + public int? ReceiveMaxMessageSize { get; set; } + /// /// Invokes a client streaming call asynchronously. /// In client streaming scenario, client sends a stream of requests and server responds with a single response. @@ -187,7 +197,7 @@ private GrpcCall CreateGrpcCall( } } - var call = new GrpcCall(method, options, Clock, _loggerFactory); + var call = new GrpcCall(method, options, this); // Clean up linked cancellation token disposeAction = linkedCts != null diff --git a/src/Grpc.Net.Client/Internal/GrpcCall.cs b/src/Grpc.Net.Client/Internal/GrpcCall.cs index 8c5ee9e0a..1a65d9d3f 100644 --- a/src/Grpc.Net.Client/Internal/GrpcCall.cs +++ b/src/Grpc.Net.Client/Internal/GrpcCall.cs @@ -37,7 +37,6 @@ internal partial class GrpcCall : IDisposable { private readonly CancellationTokenSource _callCts; private readonly CancellationTokenRegistration? _ctsRegistration; - private readonly ISystemClock _clock; private readonly TimeSpan? _timeout; private readonly Uri _uri; private readonly GrpcCallScope _logScope; @@ -54,13 +53,14 @@ internal partial class GrpcCall : IDisposable public HttpResponseMessage? HttpResponse { get; private set; } public CallOptions Options { get; } public Method Method { get; } + public HttpClientCallInvoker CallInvoker { get; } public ILogger Logger { get; } public Task? SendTask { get; private set; } public HttpContentClientStreamWriter? ClientStreamWriter { get; private set; } public HttpContentClientStreamReader? ClientStreamReader { get; private set; } - public GrpcCall(Method method, CallOptions options, ISystemClock clock, ILoggerFactory loggerFactory) + public GrpcCall(Method method, CallOptions options, HttpClientCallInvoker callInvoker) { // Validate deadline before creating any objects that require cleanup ValidateDeadline(options.Deadline); @@ -70,8 +70,8 @@ public GrpcCall(Method method, CallOptions options, ISystem _uri = new Uri(method.FullName, UriKind.Relative); _logScope = new GrpcCallScope(method.Type, _uri); Options = options; - _clock = clock; - Logger = loggerFactory.CreateLogger>(); + CallInvoker = callInvoker; + Logger = callInvoker.LoggerFactory.CreateLogger>(); if (options.CancellationToken.CanBeCanceled) { @@ -87,7 +87,7 @@ public GrpcCall(Method method, CallOptions options, ISystem if (options.Deadline != null && options.Deadline != DateTime.MaxValue) { - var timeout = options.Deadline.GetValueOrDefault() - _clock.UtcNow; + var timeout = options.Deadline.GetValueOrDefault() - CallInvoker.Clock.UtcNow; _timeout = (timeout > TimeSpan.Zero) ? timeout : TimeSpan.Zero; } } @@ -302,6 +302,7 @@ public async Task GetResponseAsync() Logger, Method.ResponseMarshaller.ContextualDeserializer, GrpcProtocolHelpers.GetGrpcEncoding(HttpResponse), + CallInvoker.ReceiveMaxMessageSize, _callCts.Token).ConfigureAwait(false); FinishResponse(); @@ -384,6 +385,7 @@ private void SetMessageContent(TRequest request, HttpRequestMessage message) request, Method.RequestMarshaller.ContextualSerializer, grpcEncoding, + CallInvoker.SendMaxMessageSize, Options.CancellationToken); }, GrpcProtocolConstants.GrpcContentTypeHeaderValue); diff --git a/src/Grpc.Net.Client/Internal/HttpContentClientStreamReader.cs b/src/Grpc.Net.Client/Internal/HttpContentClientStreamReader.cs index 8f28e56b9..56d8a2a4a 100644 --- a/src/Grpc.Net.Client/Internal/HttpContentClientStreamReader.cs +++ b/src/Grpc.Net.Client/Internal/HttpContentClientStreamReader.cs @@ -124,6 +124,7 @@ private async Task MoveNextCore(CancellationToken cancellationToken) _call.Logger, _call.Method.ResponseMarshaller.ContextualDeserializer, GrpcProtocolHelpers.GetGrpcEncoding(_httpResponse), + _call.CallInvoker.ReceiveMaxMessageSize, cancellationToken).ConfigureAwait(false); if (Current == null) { diff --git a/src/Grpc.Net.Client/Internal/HttpContentClientStreamWriter.cs b/src/Grpc.Net.Client/Internal/HttpContentClientStreamWriter.cs index 228bb9f21..69073267b 100644 --- a/src/Grpc.Net.Client/Internal/HttpContentClientStreamWriter.cs +++ b/src/Grpc.Net.Client/Internal/HttpContentClientStreamWriter.cs @@ -131,6 +131,7 @@ await writeStream.WriteMessage( message, _call.Method.RequestMarshaller.ContextualSerializer, _grpcEncoding, + _call.CallInvoker.SendMaxMessageSize, _call.CancellationToken).ConfigureAwait(false); } catch (TaskCanceledException) diff --git a/src/Grpc.Net.Client/Internal/StreamExtensions.cs b/src/Grpc.Net.Client/Internal/StreamExtensions.cs index 95c90603b..7e8d22161 100644 --- a/src/Grpc.Net.Client/Internal/StreamExtensions.cs +++ b/src/Grpc.Net.Client/Internal/StreamExtensions.cs @@ -37,6 +37,8 @@ internal static partial class StreamExtensions private const int MessageDelimiterSize = 4; // how many bytes it takes to encode "Message-Length" private const int HeaderSize = MessageDelimiterSize + 1; // message length + compression flag + private static readonly Status SendingMessageExceedsLimitStatus = new Status(StatusCode.ResourceExhausted, "Sending message exceeds the maximum configured message size."); + private static readonly Status ReceivedMessageExceedsLimitStatus = new Status(StatusCode.ResourceExhausted, "Received message exceeds the maximum configured message size."); private static readonly Status NoMessageEncodingMessageStatus = new Status(StatusCode.Internal, "Request did not include grpc-encoding value with compressed message."); private static readonly Status IdentityMessageEncodingMessageStatus = new Status(StatusCode.Internal, "Request sent 'identity' grpc-encoding value with compressed message."); private static Status CreateUnknownMessageEncodingMessageStatus(string unsupportedEncoding, IEnumerable supportedEncodings) @@ -49,10 +51,11 @@ private static Status CreateUnknownMessageEncodingMessageStatus(string unsupport ILogger logger, Func deserializer, string grpcEncoding, + int? maximumMessageSize, CancellationToken cancellationToken) where TResponse : class { - return responseStream.ReadMessageCoreAsync(logger, deserializer, grpcEncoding, cancellationToken, true, true); + return responseStream.ReadMessageCoreAsync(logger, deserializer, grpcEncoding, maximumMessageSize, cancellationToken, true, true); } public static Task ReadStreamedMessageAsync( @@ -60,10 +63,11 @@ private static Status CreateUnknownMessageEncodingMessageStatus(string unsupport ILogger logger, Func deserializer, string grpcEncoding, + int? maximumMessageSize, CancellationToken cancellationToken) where TResponse : class { - return responseStream.ReadMessageCoreAsync(logger, deserializer, grpcEncoding, cancellationToken, true, false); + return responseStream.ReadMessageCoreAsync(logger, deserializer, grpcEncoding, maximumMessageSize, cancellationToken, true, false); } private static async Task ReadMessageCoreAsync( @@ -71,6 +75,7 @@ private static Status CreateUnknownMessageEncodingMessageStatus(string unsupport ILogger logger, Func deserializer, string grpcEncoding, + int? maximumMessageSize, CancellationToken cancellationToken, bool canBeEmpty, bool singleMessage) @@ -117,6 +122,11 @@ private static Status CreateUnknownMessageEncodingMessageStatus(string unsupport throw new InvalidDataException("Message too large."); } + if (length > maximumMessageSize) + { + throw new RpcException(ReceivedMessageExceedsLimitStatus); + } + // Read message content until content length is reached byte[] messageData; if (length > 0) @@ -212,6 +222,7 @@ public static async Task WriteMessage( TMessage message, Action serializer, string grpcEncoding, + int? maximumMessageSize, CancellationToken cancellationToken) { try @@ -228,6 +239,11 @@ public static async Task WriteMessage( throw new InvalidOperationException("Serialization did not return a payload."); } + if (data.Length > maximumMessageSize) + { + throw new RpcException(SendingMessageExceedsLimitStatus); + } + var isCompressed = !string.Equals(grpcEncoding, GrpcProtocolConstants.IdentityGrpcEncoding, StringComparison.Ordinal); if (isCompressed) diff --git a/test/Grpc.Net.Client.Tests/AsyncClientStreamingCallTests.cs b/test/Grpc.Net.Client.Tests/AsyncClientStreamingCallTests.cs index f43aa3e26..4430699ce 100644 --- a/test/Grpc.Net.Client.Tests/AsyncClientStreamingCallTests.cs +++ b/test/Grpc.Net.Client.Tests/AsyncClientStreamingCallTests.cs @@ -112,9 +112,9 @@ public async Task AsyncClientStreamingCall_Success_RequestContentSent() await call.RequestStream.CompleteAsync().DefaultTimeout(); var requestContent = await streamTask.DefaultTimeout(); - var requestMessage = await requestContent.ReadStreamedMessageAsync(NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, GrpcProtocolConstants.IdentityGrpcEncoding, CancellationToken.None).DefaultTimeout(); + var requestMessage = await requestContent.ReadStreamedMessageAsync(NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, GrpcProtocolConstants.IdentityGrpcEncoding, maximumMessageSize: null, CancellationToken.None).DefaultTimeout(); Assert.AreEqual("1", requestMessage.Name); - requestMessage = await requestContent.ReadStreamedMessageAsync(NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, GrpcProtocolConstants.IdentityGrpcEncoding, CancellationToken.None).DefaultTimeout(); + requestMessage = await requestContent.ReadStreamedMessageAsync(NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, GrpcProtocolConstants.IdentityGrpcEncoding, maximumMessageSize: null, CancellationToken.None).DefaultTimeout(); Assert.AreEqual("2", requestMessage.Name); var responseMessage = await responseTask.DefaultTimeout(); diff --git a/test/Grpc.Net.Client.Tests/AsyncDuplexStreamingCallTests.cs b/test/Grpc.Net.Client.Tests/AsyncDuplexStreamingCallTests.cs index 51097abf1..4b4796585 100644 --- a/test/Grpc.Net.Client.Tests/AsyncDuplexStreamingCallTests.cs +++ b/test/Grpc.Net.Client.Tests/AsyncDuplexStreamingCallTests.cs @@ -117,9 +117,9 @@ public async Task AsyncDuplexStreamingCall_MessagesStreamed_MessagesReceived() Assert.IsNotNull(content); var requestContent = await content!.ReadAsStreamAsync().DefaultTimeout(); - var requestMessage = await requestContent.ReadStreamedMessageAsync(NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, GrpcProtocolConstants.IdentityGrpcEncoding, CancellationToken.None).DefaultTimeout(); + var requestMessage = await requestContent.ReadStreamedMessageAsync(NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, GrpcProtocolConstants.IdentityGrpcEncoding, maximumMessageSize: null, CancellationToken.None).DefaultTimeout(); Assert.AreEqual("1", requestMessage.Name); - requestMessage = await requestContent.ReadStreamedMessageAsync(NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, GrpcProtocolConstants.IdentityGrpcEncoding, CancellationToken.None).DefaultTimeout(); + requestMessage = await requestContent.ReadStreamedMessageAsync(NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, GrpcProtocolConstants.IdentityGrpcEncoding, maximumMessageSize: null, CancellationToken.None).DefaultTimeout(); Assert.AreEqual("2", requestMessage.Name); Assert.IsNull(responseStream.Current); diff --git a/test/Grpc.Net.Client.Tests/AsyncUnaryCallTests.cs b/test/Grpc.Net.Client.Tests/AsyncUnaryCallTests.cs index 1ccbddfbc..0c467221b 100644 --- a/test/Grpc.Net.Client.Tests/AsyncUnaryCallTests.cs +++ b/test/Grpc.Net.Client.Tests/AsyncUnaryCallTests.cs @@ -107,7 +107,7 @@ public async Task AsyncUnaryCall_Success_RequestContentSent() Assert.IsNotNull(content); var requestContent = await content!.ReadAsStreamAsync().DefaultTimeout(); - var requestMessage = await requestContent.ReadSingleMessageAsync(NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, GrpcProtocolConstants.IdentityGrpcEncoding, CancellationToken.None).DefaultTimeout(); + var requestMessage = await requestContent.ReadSingleMessageAsync(NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, GrpcProtocolConstants.IdentityGrpcEncoding, maximumMessageSize: null, CancellationToken.None).DefaultTimeout(); Assert.AreEqual("World", requestMessage.Name); } diff --git a/test/Grpc.Net.Client.Tests/CompressionTests.cs b/test/Grpc.Net.Client.Tests/CompressionTests.cs index f084b5ce6..bd77509cd 100644 --- a/test/Grpc.Net.Client.Tests/CompressionTests.cs +++ b/test/Grpc.Net.Client.Tests/CompressionTests.cs @@ -38,7 +38,7 @@ namespace Grpc.Net.Client.Tests public class CompressionTests { [Test] - public void AsyncUnaryCall_UnknownCompressMetadataSentWithRequest_ThrowsError() + public async Task AsyncUnaryCall_UnknownCompressMetadataSentWithRequest_ThrowsError() { // Arrange HttpRequestMessage? httpRequestMessage = null; @@ -55,6 +55,7 @@ public void AsyncUnaryCall_UnknownCompressMetadataSentWithRequest_ThrowsError() NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, "gzip", + maximumMessageSize: null, CancellationToken.None); HelloReply reply = new HelloReply @@ -76,7 +77,7 @@ public void AsyncUnaryCall_UnknownCompressMetadataSentWithRequest_ThrowsError() }); // Assert - var ex = Assert.ThrowsAsync(async () => await call.ResponseAsync.DefaultTimeout()); + var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); Assert.AreEqual("Could not find compression provider for 'not-supported'.", ex.Message); } @@ -98,6 +99,7 @@ public async Task AsyncUnaryCall_CompressMetadataSentWithRequest_RequestMessageC NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, "gzip", + maximumMessageSize: null, CancellationToken.None); HelloReply reply = new HelloReply @@ -149,6 +151,7 @@ public async Task AsyncUnaryCall_CompressedResponse_ResponseMessageDecompressed( NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, "gzip", + maximumMessageSize: null, CancellationToken.None); HelloReply reply = new HelloReply @@ -176,7 +179,7 @@ public async Task AsyncUnaryCall_CompressedResponse_ResponseMessageDecompressed( } [Test] - public void AsyncUnaryCall_CompressedResponseWithUnknownEncoding_ErrorThrown() + public async Task AsyncUnaryCall_CompressedResponseWithUnknownEncoding_ErrorThrown() { // Arrange HttpRequestMessage? httpRequestMessage = null; @@ -193,6 +196,7 @@ public void AsyncUnaryCall_CompressedResponseWithUnknownEncoding_ErrorThrown() NullLogger.Instance, ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, "gzip", + maximumMessageSize: null, CancellationToken.None); HelloReply reply = new HelloReply @@ -214,7 +218,7 @@ public void AsyncUnaryCall_CompressedResponseWithUnknownEncoding_ErrorThrown() }); // Assert - var ex = Assert.ThrowsAsync(async () => await call.ResponseAsync.DefaultTimeout()); + var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); Assert.AreEqual(StatusCode.Unimplemented, ex.StatusCode); Assert.AreEqual("Unsupported grpc-encoding value 'not-supported'. Supported encodings: gzip", ex.Status.Detail); } diff --git a/test/Grpc.Net.Client.Tests/HttpContentClientStreamReaderTests.cs b/test/Grpc.Net.Client.Tests/HttpContentClientStreamReaderTests.cs index d3bb91d73..06380958b 100644 --- a/test/Grpc.Net.Client.Tests/HttpContentClientStreamReaderTests.cs +++ b/test/Grpc.Net.Client.Tests/HttpContentClientStreamReaderTests.cs @@ -48,7 +48,8 @@ public async Task MoveNext_TokenCanceledBeforeCall_ThrowError() return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.OK, content)); }); - var call = new GrpcCall(ClientTestHelpers.ServiceMethod, new CallOptions(), SystemClock.Instance, NullLoggerFactory.Instance); + var httpClientCallInvoker = new HttpClientCallInvoker(httpClient, null); + var call = new GrpcCall(ClientTestHelpers.ServiceMethod, new CallOptions(), httpClientCallInvoker); call.StartServerStreaming(httpClient, new HelloRequest()); // Act @@ -73,7 +74,8 @@ public async Task MoveNext_TokenCanceledDuringCall_ThrowError() return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.OK, content)); }); - var call = new GrpcCall(ClientTestHelpers.ServiceMethod, new CallOptions(), SystemClock.Instance, NullLoggerFactory.Instance); + var httpClientCallInvoker = new HttpClientCallInvoker(httpClient, null); + var call = new GrpcCall(ClientTestHelpers.ServiceMethod, new CallOptions(), httpClientCallInvoker); call.StartServerStreaming(httpClient, new HelloRequest()); // Act @@ -99,7 +101,8 @@ public async Task MoveNext_MultipleCallsWithoutAwait_ThrowError() return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.OK, content)); }); - var call = new GrpcCall(ClientTestHelpers.ServiceMethod, new CallOptions(), SystemClock.Instance, NullLoggerFactory.Instance); + var httpClientCallInvoker = new HttpClientCallInvoker(httpClient, null); + var call = new GrpcCall(ClientTestHelpers.ServiceMethod, new CallOptions(), httpClientCallInvoker); call.StartServerStreaming(httpClient, new HelloRequest()); // Act diff --git a/test/Grpc.Net.Client.Tests/MaximumMessageSizeTests.cs b/test/Grpc.Net.Client.Tests/MaximumMessageSizeTests.cs new file mode 100644 index 000000000..656402d1d --- /dev/null +++ b/test/Grpc.Net.Client.Tests/MaximumMessageSizeTests.cs @@ -0,0 +1,225 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Greet; +using Grpc.Core; +using Grpc.Net.Client.Internal; +using Grpc.Net.Client.Internal.Compression; +using Grpc.Net.Client.Tests.Infrastructure; +using Grpc.Tests.Shared; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging.Abstractions; +using NUnit.Framework; + +namespace Grpc.Net.Client.Tests +{ + [TestFixture] + public class MaximumMessageSizeTests + { + private async Task HandleRequest(HttpRequestMessage request) + { + var requestStream = await request.Content.ReadAsStreamAsync(); + + var helloRequest = await StreamExtensions.ReadSingleMessageAsync( + requestStream, + NullLogger.Instance, + ClientTestHelpers.ServiceMethod.RequestMarshaller.ContextualDeserializer, + "gzip", + maximumMessageSize: null, + CancellationToken.None); + + HelloReply reply = new HelloReply + { + Message = "Hello " + helloRequest!.Name + }; + + var streamContent = await ClientTestHelpers.CreateResponseContent(reply).DefaultTimeout(); + + return ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent); + } + + [Test] + public async Task AsyncUnaryCall_MessageSmallerThanSendMaxMessageSize_Success() + { + // Arrange + var httpClient = ClientTestHelpers.CreateTestClient(HandleRequest); + var invoker = HttpClientCallInvokerFactory.Create(httpClient); + invoker.SendMaxMessageSize = 100; + + // Act + var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest + { + Name = "World" + }); + + // Assert + var response = await call.ResponseAsync.DefaultTimeout(); + Assert.AreEqual("Hello World", response.Message); + } + + [Test] + public async Task AsyncUnaryCall_MessageLargerThanSendMaxMessageSize_ThrowsError() + { + // Arrange + var httpClient = ClientTestHelpers.CreateTestClient(HandleRequest); + var invoker = HttpClientCallInvokerFactory.Create(httpClient); + invoker.SendMaxMessageSize = 1; + + // Act + var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest + { + Name = "World" + }); + + // Assert + var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); + Assert.AreEqual(StatusCode.ResourceExhausted, ex.StatusCode); + Assert.AreEqual("Sending message exceeds the maximum configured message size.", ex.Status.Detail); + } + + [Test] + public async Task AsyncUnaryCall_MessageSmallerThanReceiveMaxMessageSize_Success() + { + // Arrange + var httpClient = ClientTestHelpers.CreateTestClient(HandleRequest); + var invoker = HttpClientCallInvokerFactory.Create(httpClient); + invoker.ReceiveMaxMessageSize = 100; + + // Act + var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest + { + Name = "World" + }); + + // Assert + var response = await call.ResponseAsync.DefaultTimeout(); + Assert.AreEqual("Hello World", response.Message); + } + + [Test] + public async Task AsyncUnaryCall_MessageLargerThanReceiveMaxMessageSize_ThrowsError() + { + // Arrange + var httpClient = ClientTestHelpers.CreateTestClient(HandleRequest); + var invoker = HttpClientCallInvokerFactory.Create(httpClient); + invoker.ReceiveMaxMessageSize = 1; + + // Act + var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest + { + Name = "World" + }); + + // Assert + var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); + Assert.AreEqual(StatusCode.ResourceExhausted, ex.StatusCode); + Assert.AreEqual("Received message exceeds the maximum configured message size.", ex.Status.Detail); + } + + [Test] + public async Task AsyncDuplexStreamingCall_MessageSmallerThanSendMaxMessageSize_Success() + { + // Arrange + var httpClient = ClientTestHelpers.CreateTestClient(HandleRequest); + var invoker = HttpClientCallInvokerFactory.Create(httpClient); + invoker.SendMaxMessageSize = 100; + + // Act + var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + await call.RequestStream.WriteAsync(new HelloRequest + { + Name = "World" + }); + await call.RequestStream.CompleteAsync(); + + // Assert + await call.ResponseStream.MoveNext(CancellationToken.None).DefaultTimeout(); + Assert.AreEqual("Hello World", call.ResponseStream.Current.Message); + } + + [Test] + public async Task AsyncDuplexStreamingCall_MessageLargerThanSendMaxMessageSize_ThrowsError() + { + // Arrange + var httpClient = ClientTestHelpers.CreateTestClient(HandleRequest); + var invoker = HttpClientCallInvokerFactory.Create(httpClient); + invoker.SendMaxMessageSize = 1; + + // Act + var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + + // Assert + var ex = await ExceptionAssert.ThrowsAsync(() => call.RequestStream.WriteAsync(new HelloRequest + { + Name = "World" + })); + Assert.AreEqual(StatusCode.ResourceExhausted, ex.StatusCode); + Assert.AreEqual("Sending message exceeds the maximum configured message size.", ex.Status.Detail); + } + + [Test] + public async Task AsyncDuplexStreamingCall_MessageSmallerThanReceiveMaxMessageSize_Success() + { + // Arrange + var httpClient = ClientTestHelpers.CreateTestClient(HandleRequest); + var invoker = HttpClientCallInvokerFactory.Create(httpClient); + invoker.ReceiveMaxMessageSize = 100; + + // Act + var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + await call.RequestStream.WriteAsync(new HelloRequest + { + Name = "World" + }); + await call.RequestStream.CompleteAsync(); + + // Assert + await call.ResponseStream.MoveNext(CancellationToken.None).DefaultTimeout(); + Assert.AreEqual("Hello World", call.ResponseStream.Current.Message); + } + + [Test] + public async Task AsyncDuplexStreamingCall_MessageLargerThanReceiveMaxMessageSize_ThrowsError() + { + // Arrange + var httpClient = ClientTestHelpers.CreateTestClient(HandleRequest); + var invoker = HttpClientCallInvokerFactory.Create(httpClient); + invoker.ReceiveMaxMessageSize = 1; + + // Act + var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + await call.RequestStream.WriteAsync(new HelloRequest + { + Name = "World" + }); + await call.RequestStream.CompleteAsync(); + + // Assert + var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseStream.MoveNext(CancellationToken.None)); + Assert.AreEqual(StatusCode.ResourceExhausted, ex.StatusCode); + Assert.AreEqual("Received message exceeds the maximum configured message size.", ex.Status.Detail); + } + } +}