From 4cb62b461e3d7519c449e168c244a6492ef9014b Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 27 Nov 2019 13:05:34 +1300 Subject: [PATCH 1/4] Improve extensibility of gRPC invocation --- .../Client/CompressedUnaryClientBenchmark.cs | 4 +- .../Client/UnaryClientBenchmarkBase.cs | 4 +- ...mpressedUnaryServerCallHandlerBenchmark.cs | 4 +- .../UnaryServerCallHandlerBenchmarkBase.cs | 22 +-- .../Grpc.AspNetCore.Server.csproj | 8 + .../InterceptorCollection.cs | 4 - .../ClientStreamingServerCallHandler.cs | 74 +------- .../DuplexStreamingServerCallHandler.cs | 74 ++------ .../CallHandlers/ServerCallHandlerBase.cs | 25 +-- .../ServerStreamingServerCallHandler.cs | 68 +------- .../CallHandlers/UnaryServerCallHandler.cs | 64 +------ .../DefaultGrpcInterceptorActivator.cs | 2 +- .../Internal/DefaultGrpcServiceActivator.cs | 2 +- .../HttpContextSerializationContext.cs | 10 +- .../Internal/HttpContextServerCallContext.cs | 15 +- .../Internal/MethodContext.cs | 71 -------- .../Internal/PipeExtensions.cs | 8 +- .../Internal/ServerCallHandlerFactory.cs | 73 +++----- .../Internal/BinderServiceModelProvider.cs | 1 + .../Model/Internal/MethodModel.cs | 7 +- .../Model/Internal/ProviderServiceBinder.cs | 2 + .../Model/Internal/ServiceRouteBuilder.cs | 7 +- .../Model/ServiceMethodProviderContext.cs | 34 ++-- .../Server}/BindMethodFinder.cs | 2 +- .../ClientStreamingServerMethodInvoker.cs | 121 +++++++++++++ .../DuplexStreamingServerMethodInvoker.cs | 124 ++++++++++++++ .../Server}/InterceptorPipelineBuilder.cs | 35 ++-- src/Shared/Server/MethodOptions.cs | 160 ++++++++++++++++++ src/Shared/Server/ServerMethodInvokerBase.cs | 66 ++++++++ .../ServerStreamingServerMethodInvoker.cs | 124 ++++++++++++++ src/Shared/Server/UnaryServerMethodInvoker.cs | 118 +++++++++++++ .../BindMethodFinderTests.cs | 3 +- .../CallHandlerTests.cs | 55 +++--- .../HttpContextStreamReaderTests.cs | 4 +- .../HttpContextStreamWriterTests.cs | 4 +- .../Model/BinderServiceMethodProviderTests.cs | 9 +- .../PipeExtensionsTestsBase.cs | 10 +- .../TestObjects/TestGrpcServiceActivator.cs | 3 +- test/Shared/HttpContextHelpers.cs | 4 +- .../HttpContextServerCallContextHelpers.cs | 35 ++-- test/Shared/MessageHelpers.cs | 25 +-- 41 files changed, 959 insertions(+), 526 deletions(-) delete mode 100644 src/Grpc.AspNetCore.Server/Internal/MethodContext.cs rename src/{Grpc.AspNetCore.Server/Model/Internal => Shared/Server}/BindMethodFinder.cs (98%) create mode 100644 src/Shared/Server/ClientStreamingServerMethodInvoker.cs create mode 100644 src/Shared/Server/DuplexStreamingServerMethodInvoker.cs rename src/{Grpc.AspNetCore.Server/Internal/CallHandlers => Shared/Server}/InterceptorPipelineBuilder.cs (82%) create mode 100644 src/Shared/Server/MethodOptions.cs create mode 100644 src/Shared/Server/ServerMethodInvokerBase.cs create mode 100644 src/Shared/Server/ServerStreamingServerMethodInvoker.cs create mode 100644 src/Shared/Server/UnaryServerMethodInvoker.cs diff --git a/perf/Grpc.AspNetCore.Microbenchmarks/Client/CompressedUnaryClientBenchmark.cs b/perf/Grpc.AspNetCore.Microbenchmarks/Client/CompressedUnaryClientBenchmark.cs index 63c658a18..db7d65ba3 100644 --- a/perf/Grpc.AspNetCore.Microbenchmarks/Client/CompressedUnaryClientBenchmark.cs +++ b/perf/Grpc.AspNetCore.Microbenchmarks/Client/CompressedUnaryClientBenchmark.cs @@ -37,9 +37,9 @@ public class CompressedUnaryClientBenchmark : UnaryClientBenchmarkBase public CompressedUnaryClientBenchmark() { ResponseCompressionAlgorithm = TestCompressionProvider.Name; - CompressionProviders = new Dictionary + CompressionProviders = new List { - [TestCompressionProvider.Name] = new TestCompressionProvider() + new TestCompressionProvider() }; _compressionMetadata = new Metadata { diff --git a/perf/Grpc.AspNetCore.Microbenchmarks/Client/UnaryClientBenchmarkBase.cs b/perf/Grpc.AspNetCore.Microbenchmarks/Client/UnaryClientBenchmarkBase.cs index 451216ece..6c2e67f3c 100644 --- a/perf/Grpc.AspNetCore.Microbenchmarks/Client/UnaryClientBenchmarkBase.cs +++ b/perf/Grpc.AspNetCore.Microbenchmarks/Client/UnaryClientBenchmarkBase.cs @@ -36,7 +36,7 @@ namespace Grpc.AspNetCore.Microbenchmarks.Client { public class UnaryClientBenchmarkBase { - protected Dictionary? CompressionProviders { get; set; } + protected List? CompressionProviders { get; set; } protected string? ResponseCompressionAlgorithm { get; set; } private Greeter.GreeterClient? _client; @@ -69,7 +69,7 @@ public void GlobalSetup() var channel = GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions { HttpClient = httpClient, - CompressionProviders = CompressionProviders?.Values?.ToList() + CompressionProviders = CompressionProviders }); _client = new Greeter.GreeterClient(channel); diff --git a/perf/Grpc.AspNetCore.Microbenchmarks/Server/CompressedUnaryServerCallHandlerBenchmark.cs b/perf/Grpc.AspNetCore.Microbenchmarks/Server/CompressedUnaryServerCallHandlerBenchmark.cs index 5079f03f1..3dc9ea3eb 100644 --- a/perf/Grpc.AspNetCore.Microbenchmarks/Server/CompressedUnaryServerCallHandlerBenchmark.cs +++ b/perf/Grpc.AspNetCore.Microbenchmarks/Server/CompressedUnaryServerCallHandlerBenchmark.cs @@ -34,9 +34,9 @@ public class CompressedUnaryServerCallHandlerBenchmark : UnaryServerCallHandlerB public CompressedUnaryServerCallHandlerBenchmark() { ResponseCompressionAlgorithm = TestCompressionProvider.Name; - CompressionProviders = new Dictionary + CompressionProviders = new List { - [TestCompressionProvider.Name] = new TestCompressionProvider() + new TestCompressionProvider() }; } diff --git a/perf/Grpc.AspNetCore.Microbenchmarks/Server/UnaryServerCallHandlerBenchmarkBase.cs b/perf/Grpc.AspNetCore.Microbenchmarks/Server/UnaryServerCallHandlerBenchmarkBase.cs index ed41497a0..6299c0316 100644 --- a/perf/Grpc.AspNetCore.Microbenchmarks/Server/UnaryServerCallHandlerBenchmarkBase.cs +++ b/perf/Grpc.AspNetCore.Microbenchmarks/Server/UnaryServerCallHandlerBenchmarkBase.cs @@ -29,8 +29,10 @@ using Grpc.AspNetCore.Server; using Grpc.AspNetCore.Server.Internal; using Grpc.AspNetCore.Server.Internal.CallHandlers; +using Grpc.AspNetCore.Server.Model; using Grpc.Core; using Grpc.Net.Compression; +using Grpc.Shared.Server; using Grpc.Tests.Shared; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -52,7 +54,7 @@ public class UnaryServerCallHandlerBenchmarkBase private TestPipeReader? _requestPipe; protected InterceptorCollection? Interceptors { get; set; } - protected Dictionary? CompressionProviders { get; set; } + protected List? CompressionProviders { get; set; } protected string? ResponseCompressionAlgorithm { get; set; } [GlobalSetup] @@ -77,15 +79,15 @@ public void GlobalSetup() var method = new Method(MethodType.Unary, typeof(TestService).FullName, nameof(TestService.SayHello), marshaller, marshaller); var result = Task.FromResult(message); _callHandler = new UnaryServerCallHandler( - method, - (service, request, context) => result, - HttpContextServerCallContextHelper.CreateMethodContext( - compressionProviders: CompressionProviders, - responseCompressionAlgorithm: ResponseCompressionAlgorithm, - interceptors: Interceptors), - NullLoggerFactory.Instance, - new TestGrpcServiceActivator(new TestService()), - serviceProvider); + new UnaryServerMethodInvoker( + (service, request, context) => result, + method, + HttpContextServerCallContextHelper.CreateMethodOptions( + compressionProviders: CompressionProviders, + responseCompressionAlgorithm: ResponseCompressionAlgorithm, + interceptors: Interceptors), + new TestGrpcServiceActivator(new TestService())), + NullLoggerFactory.Instance); _trailers = new HeaderDictionary(); diff --git a/src/Grpc.AspNetCore.Server/Grpc.AspNetCore.Server.csproj b/src/Grpc.AspNetCore.Server/Grpc.AspNetCore.Server.csproj index 9e0f00aa8..e1cff8dde 100644 --- a/src/Grpc.AspNetCore.Server/Grpc.AspNetCore.Server.csproj +++ b/src/Grpc.AspNetCore.Server/Grpc.AspNetCore.Server.csproj @@ -15,6 +15,14 @@ + + + + + + + + diff --git a/src/Grpc.AspNetCore.Server/InterceptorCollection.cs b/src/Grpc.AspNetCore.Server/InterceptorCollection.cs index 6fc6156de..f204d63a3 100644 --- a/src/Grpc.AspNetCore.Server/InterceptorCollection.cs +++ b/src/Grpc.AspNetCore.Server/InterceptorCollection.cs @@ -28,10 +28,6 @@ namespace Grpc.AspNetCore.Server /// public class InterceptorCollection : Collection { - internal InterceptorCollection() - { - } - /// /// Add an interceptor to the end of the pipeline. /// diff --git a/src/Grpc.AspNetCore.Server/Internal/CallHandlers/ClientStreamingServerCallHandler.cs b/src/Grpc.AspNetCore.Server/Internal/CallHandlers/ClientStreamingServerCallHandler.cs index 148acb513..67a354da5 100644 --- a/src/Grpc.AspNetCore.Server/Internal/CallHandlers/ClientStreamingServerCallHandler.cs +++ b/src/Grpc.AspNetCore.Server/Internal/CallHandlers/ClientStreamingServerCallHandler.cs @@ -16,12 +16,10 @@ #endregion -using System; using System.Threading.Tasks; -using Grpc.AspNetCore.Server.Model; using Grpc.Core; +using Grpc.Shared.Server; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Grpc.AspNetCore.Server.Internal.CallHandlers @@ -31,45 +29,14 @@ internal class ClientStreamingServerCallHandler : where TResponse : class where TService : class { - private readonly ClientStreamingServerMethod _invoker; - private readonly ClientStreamingServerMethod? _pipelineInvoker; + private readonly ClientStreamingServerMethodInvoker _invoker; public ClientStreamingServerCallHandler( - Method method, - ClientStreamingServerMethod invoker, - MethodContext methodContext, - ILoggerFactory loggerFactory, - IGrpcServiceActivator serviceActivator, - IServiceProvider serviceProvider) - : base(method, methodContext, loggerFactory, serviceActivator, serviceProvider) + ClientStreamingServerMethodInvoker invoker, + ILoggerFactory loggerFactory) + : base(invoker, loggerFactory) { _invoker = invoker; - - if (MethodContext.HasInterceptors) - { - var interceptorPipeline = new InterceptorPipelineBuilder(MethodContext.Interceptors, ServiceProvider); - _pipelineInvoker = interceptorPipeline.ClientStreamingPipeline(ResolvedInterceptorInvoker); - } - } - - private async Task ResolvedInterceptorInvoker(IAsyncStreamReader resolvedRequestStream, ServerCallContext resolvedContext) - { - GrpcActivatorHandle serviceHandle = default; - try - { - serviceHandle = ServiceActivator.Create(resolvedContext.GetHttpContext().RequestServices); - return await _invoker( - serviceHandle.Instance, - resolvedRequestStream, - resolvedContext); - } - finally - { - if (serviceHandle.Instance != null) - { - await ServiceActivator.ReleaseAsync(serviceHandle); - } - } } protected override async Task HandleCallAsyncCore(HttpContext httpContext, HttpContextServerCallContext serverCallContext) @@ -77,33 +44,8 @@ protected override async Task HandleCallAsyncCore(HttpContext httpContext, HttpC // Disable request body data rate for client streaming DisableMinRequestBodyDataRateAndMaxRequestBodySize(httpContext); - TResponse? response = null; - - if (_pipelineInvoker == null) - { - GrpcActivatorHandle serviceHandle = default; - try - { - serviceHandle = ServiceActivator.Create(httpContext.RequestServices); - response = await _invoker( - serviceHandle.Instance, - new HttpContextStreamReader(serverCallContext, Method.RequestMarshaller.ContextualDeserializer), - serverCallContext); - } - finally - { - if (serviceHandle.Instance != null) - { - await ServiceActivator.ReleaseAsync(serviceHandle); - } - } - } - else - { - response = await _pipelineInvoker( - new HttpContextStreamReader(serverCallContext, Method.RequestMarshaller.ContextualDeserializer), - serverCallContext); - } + var streamReader = new HttpContextStreamReader(serverCallContext, MethodInvoker.Method.RequestMarshaller.ContextualDeserializer); + var response = await _invoker.Invoke(httpContext, serverCallContext, streamReader); if (response == null) { @@ -112,7 +54,7 @@ protected override async Task HandleCallAsyncCore(HttpContext httpContext, HttpC } var responseBodyWriter = httpContext.Response.BodyWriter; - await responseBodyWriter.WriteMessageAsync(response, serverCallContext, Method.ResponseMarshaller.ContextualSerializer, canFlush: false); + await responseBodyWriter.WriteMessageAsync(response, serverCallContext, MethodInvoker.Method.ResponseMarshaller.ContextualSerializer, canFlush: false); } } } diff --git a/src/Grpc.AspNetCore.Server/Internal/CallHandlers/DuplexStreamingServerCallHandler.cs b/src/Grpc.AspNetCore.Server/Internal/CallHandlers/DuplexStreamingServerCallHandler.cs index d31b1bc15..ff51f5494 100644 --- a/src/Grpc.AspNetCore.Server/Internal/CallHandlers/DuplexStreamingServerCallHandler.cs +++ b/src/Grpc.AspNetCore.Server/Internal/CallHandlers/DuplexStreamingServerCallHandler.cs @@ -20,6 +20,7 @@ using System.Threading.Tasks; using Grpc.AspNetCore.Server.Model; using Grpc.Core; +using Grpc.Shared.Server; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -31,80 +32,25 @@ internal class DuplexStreamingServerCallHandler : where TResponse : class where TService : class { - private readonly DuplexStreamingServerMethod _invoker; - private readonly DuplexStreamingServerMethod? _pipelineInvoker; + private readonly DuplexStreamingServerMethodInvoker _invoker; public DuplexStreamingServerCallHandler( - Method method, - DuplexStreamingServerMethod invoker, - MethodContext methodContext, - ILoggerFactory loggerFactory, - IGrpcServiceActivator serviceActivator, - IServiceProvider serviceProvider) - : base(method, methodContext, loggerFactory, serviceActivator, serviceProvider) + DuplexStreamingServerMethodInvoker invoker, + ILoggerFactory loggerFactory) + : base(invoker, loggerFactory) { _invoker = invoker; - - if (MethodContext.HasInterceptors) - { - var interceptorPipeline = new InterceptorPipelineBuilder(MethodContext.Interceptors, ServiceProvider); - _pipelineInvoker = interceptorPipeline.DuplexStreamingPipeline(ResolvedInterceptorInvoker); - } } - private async Task ResolvedInterceptorInvoker(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext resolvedContext) - { - GrpcActivatorHandle serviceHandle = default; - try - { - serviceHandle = ServiceActivator.Create(resolvedContext.GetHttpContext().RequestServices); - await _invoker( - serviceHandle.Instance, - requestStream, - responseStream, - resolvedContext); - } - finally - { - if (serviceHandle.Instance != null) - { - await ServiceActivator.ReleaseAsync(serviceHandle); - } - } - } - - protected override async Task HandleCallAsyncCore(HttpContext httpContext, HttpContextServerCallContext serverCallContext) + protected override Task HandleCallAsyncCore(HttpContext httpContext, HttpContextServerCallContext serverCallContext) { // Disable request body data rate for client streaming DisableMinRequestBodyDataRateAndMaxRequestBodySize(httpContext); - if (_pipelineInvoker == null) - { - GrpcActivatorHandle serviceHandle = default; - try - { - serviceHandle = ServiceActivator.Create(httpContext.RequestServices); - await _invoker( - serviceHandle.Instance, - new HttpContextStreamReader(serverCallContext, Method.RequestMarshaller.ContextualDeserializer), - new HttpContextStreamWriter(serverCallContext, Method.ResponseMarshaller.ContextualSerializer), - serverCallContext); - } - finally - { - if (serviceHandle.Instance != null) - { - await ServiceActivator.ReleaseAsync(serviceHandle); - } - } - } - else - { - await _pipelineInvoker( - new HttpContextStreamReader(serverCallContext, Method.RequestMarshaller.ContextualDeserializer), - new HttpContextStreamWriter(serverCallContext, Method.ResponseMarshaller.ContextualSerializer), - serverCallContext); - } + var streamReader = new HttpContextStreamReader(serverCallContext, MethodInvoker.Method.RequestMarshaller.ContextualDeserializer); + var streamWriter = new HttpContextStreamWriter(serverCallContext, MethodInvoker.Method.ResponseMarshaller.ContextualSerializer); + + return _invoker.Invoke(httpContext, serverCallContext, streamReader, streamWriter); } } } diff --git a/src/Grpc.AspNetCore.Server/Internal/CallHandlers/ServerCallHandlerBase.cs b/src/Grpc.AspNetCore.Server/Internal/CallHandlers/ServerCallHandlerBase.cs index d0d91347c..d6761b71e 100644 --- a/src/Grpc.AspNetCore.Server/Internal/CallHandlers/ServerCallHandlerBase.cs +++ b/src/Grpc.AspNetCore.Server/Internal/CallHandlers/ServerCallHandlerBase.cs @@ -18,7 +18,9 @@ using System; using System.Threading.Tasks; +using Grpc.AspNetCore.Server.Model; using Grpc.Core; +using Grpc.Shared.Server; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; @@ -34,23 +36,14 @@ internal abstract class ServerCallHandlerBase { private const string LoggerName = "Grpc.AspNetCore.Server.ServerCallHandler"; - protected Method Method { get; } - protected MethodContext MethodContext { get; } - protected IGrpcServiceActivator ServiceActivator { get; } - protected IServiceProvider ServiceProvider { get; } + protected ServerMethodInvokerBase MethodInvoker { get; } protected ILogger Logger { get; } protected ServerCallHandlerBase( - Method method, - MethodContext methodContext, - ILoggerFactory loggerFactory, - IGrpcServiceActivator serviceActivator, - IServiceProvider serviceProvider) + ServerMethodInvokerBase methodInvoker, + ILoggerFactory loggerFactory) { - Method = method; - MethodContext = methodContext; - ServiceActivator = serviceActivator; - ServiceProvider = serviceProvider; + MethodInvoker = methodInvoker; Logger = loggerFactory.CreateLogger(LoggerName); } @@ -74,7 +67,7 @@ public Task HandleCallAsync(HttpContext httpContext) return Task.CompletedTask; } - var serverCallContext = new HttpContextServerCallContext(httpContext, MethodContext, Logger); + var serverCallContext = new HttpContextServerCallContext(httpContext, MethodInvoker.Options, typeof(TRequest), typeof(TResponse), Logger); httpContext.Features.Set(serverCallContext); GrpcProtocolHelpers.AddProtocolHeaders(httpContext.Response); @@ -91,12 +84,12 @@ public Task HandleCallAsync(HttpContext httpContext) } else { - return AwaitHandleCall(serverCallContext, Method, handleCallTask); + return AwaitHandleCall(serverCallContext, MethodInvoker.Method, handleCallTask); } } catch (Exception ex) { - return serverCallContext.ProcessHandlerErrorAsync(ex, Method.Name); + return serverCallContext.ProcessHandlerErrorAsync(ex, MethodInvoker.Method.Name); } static async Task AwaitHandleCall(HttpContextServerCallContext serverCallContext, Method method, Task handleCall) diff --git a/src/Grpc.AspNetCore.Server/Internal/CallHandlers/ServerStreamingServerCallHandler.cs b/src/Grpc.AspNetCore.Server/Internal/CallHandlers/ServerStreamingServerCallHandler.cs index 80a8f0989..f01b8e0d3 100644 --- a/src/Grpc.AspNetCore.Server/Internal/CallHandlers/ServerStreamingServerCallHandler.cs +++ b/src/Grpc.AspNetCore.Server/Internal/CallHandlers/ServerStreamingServerCallHandler.cs @@ -20,6 +20,7 @@ using System.Threading.Tasks; using Grpc.AspNetCore.Server.Model; using Grpc.Core; +using Grpc.Shared.Server; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -31,76 +32,23 @@ internal class ServerStreamingServerCallHandler : where TResponse : class where TService : class { - private readonly ServerStreamingServerMethod _invoker; - private readonly ServerStreamingServerMethod? _pipelineInvoker; + private readonly ServerStreamingServerMethodInvoker _invoker; public ServerStreamingServerCallHandler( - Method method, - ServerStreamingServerMethod invoker, - MethodContext methodContext, - ILoggerFactory loggerFactory, - IGrpcServiceActivator serviceActivator, - IServiceProvider serviceProvider) - : base(method, methodContext, loggerFactory, serviceActivator, serviceProvider) + ServerStreamingServerMethodInvoker invoker, + ILoggerFactory loggerFactory) + : base(invoker, loggerFactory) { _invoker = invoker; - - if (MethodContext.HasInterceptors) - { - var interceptorPipeline = new InterceptorPipelineBuilder(MethodContext.Interceptors, ServiceProvider); - _pipelineInvoker = interceptorPipeline.ServerStreamingPipeline(ResolvedInterceptorInvoker); - } - } - - private async Task ResolvedInterceptorInvoker(TRequest resolvedRequest, IServerStreamWriter responseStream, ServerCallContext resolvedContext) - { - GrpcActivatorHandle serviceHandle = default; - try - { - serviceHandle = ServiceActivator.Create(resolvedContext.GetHttpContext().RequestServices); - await _invoker(serviceHandle.Instance, resolvedRequest, responseStream, resolvedContext); - } - finally - { - if (serviceHandle.Instance != null) - { - await ServiceActivator.ReleaseAsync(serviceHandle); - } - } } protected override async Task HandleCallAsyncCore(HttpContext httpContext, HttpContextServerCallContext serverCallContext) { // Decode request - var request = await httpContext.Request.BodyReader.ReadSingleMessageAsync(serverCallContext, Method.RequestMarshaller.ContextualDeserializer); + var request = await httpContext.Request.BodyReader.ReadSingleMessageAsync(serverCallContext, MethodInvoker.Method.RequestMarshaller.ContextualDeserializer); - if (_pipelineInvoker == null) - { - GrpcActivatorHandle serviceHandle = default; - try - { - serviceHandle = ServiceActivator.Create(httpContext.RequestServices); - await _invoker( - serviceHandle.Instance, - request, - new HttpContextStreamWriter(serverCallContext, Method.ResponseMarshaller.ContextualSerializer), - serverCallContext); - } - finally - { - if (serviceHandle.Instance != null) - { - await ServiceActivator.ReleaseAsync(serviceHandle); - } - } - } - else - { - await _pipelineInvoker( - request, - new HttpContextStreamWriter(serverCallContext, Method.ResponseMarshaller.ContextualSerializer), - serverCallContext); - } + var streamWriter = new HttpContextStreamWriter(serverCallContext, MethodInvoker.Method.ResponseMarshaller.ContextualSerializer); + await _invoker.Invoke(httpContext, serverCallContext, request, streamWriter); } } } diff --git a/src/Grpc.AspNetCore.Server/Internal/CallHandlers/UnaryServerCallHandler.cs b/src/Grpc.AspNetCore.Server/Internal/CallHandlers/UnaryServerCallHandler.cs index adc74f6f6..71e30c624 100644 --- a/src/Grpc.AspNetCore.Server/Internal/CallHandlers/UnaryServerCallHandler.cs +++ b/src/Grpc.AspNetCore.Server/Internal/CallHandlers/UnaryServerCallHandler.cs @@ -20,6 +20,7 @@ using System.Threading.Tasks; using Grpc.AspNetCore.Server.Model; using Grpc.Core; +using Grpc.Shared.Server; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -31,70 +32,21 @@ internal class UnaryServerCallHandler : ServerCal where TResponse : class where TService : class { - private readonly UnaryServerMethod _invoker; - private readonly UnaryServerMethod? _pipelineInvoker; + private readonly UnaryServerMethodInvoker _invoker; public UnaryServerCallHandler( - Method method, - UnaryServerMethod invoker, - MethodContext methodContext, - ILoggerFactory loggerFactory, - IGrpcServiceActivator serviceActivator, - IServiceProvider serviceProvider) - : base(method, methodContext, loggerFactory, serviceActivator, serviceProvider) + UnaryServerMethodInvoker invoker, + ILoggerFactory loggerFactory) + : base(invoker, loggerFactory) { _invoker = invoker; - - if (MethodContext.HasInterceptors) - { - var interceptorPipeline = new InterceptorPipelineBuilder(MethodContext.Interceptors, ServiceProvider); - _pipelineInvoker = interceptorPipeline.UnaryPipeline(ResolvedInterceptorInvoker); - } - } - - private async Task ResolvedInterceptorInvoker(TRequest resolvedRequest, ServerCallContext resolvedContext) - { - GrpcActivatorHandle serviceHandle = default; - try - { - serviceHandle = ServiceActivator.Create(resolvedContext.GetHttpContext().RequestServices); - return await _invoker(serviceHandle.Instance, resolvedRequest, resolvedContext); - } - finally - { - if (serviceHandle.Instance != null) - { - await ServiceActivator.ReleaseAsync(serviceHandle); - } - } } protected override async Task HandleCallAsyncCore(HttpContext httpContext, HttpContextServerCallContext serverCallContext) { - var request = await httpContext.Request.BodyReader.ReadSingleMessageAsync(serverCallContext, Method.RequestMarshaller.ContextualDeserializer); + var request = await httpContext.Request.BodyReader.ReadSingleMessageAsync(serverCallContext, MethodInvoker.Method.RequestMarshaller.ContextualDeserializer); - TResponse? response = null; - - if (_pipelineInvoker == null) - { - GrpcActivatorHandle serviceHandle = default; - try - { - serviceHandle = ServiceActivator.Create(httpContext.RequestServices); - response = await _invoker(serviceHandle.Instance, request, serverCallContext); - } - finally - { - if (serviceHandle.Instance != null) - { - await ServiceActivator.ReleaseAsync(serviceHandle); - } - } - } - else - { - response = await _pipelineInvoker(request, serverCallContext); - } + var response = await _invoker.Invoke(httpContext, serverCallContext, request); if (response == null) { @@ -103,7 +55,7 @@ protected override async Task HandleCallAsyncCore(HttpContext httpContext, HttpC } var responseBodyWriter = httpContext.Response.BodyWriter; - await responseBodyWriter.WriteMessageAsync(response, serverCallContext, Method.ResponseMarshaller.ContextualSerializer, canFlush: false); + await responseBodyWriter.WriteMessageAsync(response, serverCallContext, MethodInvoker.Method.ResponseMarshaller.ContextualSerializer, canFlush: false); } } } diff --git a/src/Grpc.AspNetCore.Server/Internal/DefaultGrpcInterceptorActivator.cs b/src/Grpc.AspNetCore.Server/Internal/DefaultGrpcInterceptorActivator.cs index 87fcca06a..8d213582c 100644 --- a/src/Grpc.AspNetCore.Server/Internal/DefaultGrpcInterceptorActivator.cs +++ b/src/Grpc.AspNetCore.Server/Internal/DefaultGrpcInterceptorActivator.cs @@ -24,7 +24,7 @@ namespace Grpc.AspNetCore.Server.Internal { - internal class DefaultGrpcInterceptorActivator : IGrpcInterceptorActivator where TInterceptor : Interceptor + internal sealed class DefaultGrpcInterceptorActivator : IGrpcInterceptorActivator where TInterceptor : Interceptor { public GrpcActivatorHandle Create(IServiceProvider serviceProvider, InterceptorRegistration interceptorRegistration) { diff --git a/src/Grpc.AspNetCore.Server/Internal/DefaultGrpcServiceActivator.cs b/src/Grpc.AspNetCore.Server/Internal/DefaultGrpcServiceActivator.cs index 1d2d12881..453cd4236 100644 --- a/src/Grpc.AspNetCore.Server/Internal/DefaultGrpcServiceActivator.cs +++ b/src/Grpc.AspNetCore.Server/Internal/DefaultGrpcServiceActivator.cs @@ -22,7 +22,7 @@ namespace Grpc.AspNetCore.Server.Internal { - internal class DefaultGrpcServiceActivator : IGrpcServiceActivator where TGrpcService : class + internal sealed class DefaultGrpcServiceActivator : IGrpcServiceActivator where TGrpcService : class { private static readonly Lazy _objectFactory = new Lazy(() => ActivatorUtilities.CreateFactory(typeof(TGrpcService), Type.EmptyTypes)); diff --git a/src/Grpc.AspNetCore.Server/Internal/HttpContextSerializationContext.cs b/src/Grpc.AspNetCore.Server/Internal/HttpContextSerializationContext.cs index 4f96fe885..9969fc680 100644 --- a/src/Grpc.AspNetCore.Server/Internal/HttpContextSerializationContext.cs +++ b/src/Grpc.AspNetCore.Server/Internal/HttpContextSerializationContext.cs @@ -80,7 +80,7 @@ public void Reset() if (canCompress) { - if (_serverCallContext.MethodContext.CompressionProviders.TryGetValue(_serverCallContext.ResponseGrpcEncoding, out var compressionProvider)) + if (_serverCallContext.Options.CompressionProviders.TryGetValue(_serverCallContext.ResponseGrpcEncoding, out var compressionProvider)) { return compressionProvider; } @@ -109,7 +109,7 @@ public override void Complete(byte[] payload) case InternalState.Initialized: _state = InternalState.CompleteArray; - GrpcServerLog.SerializedMessage(_serverCallContext.Logger, _serverCallContext.MethodContext.ResponseType, payload.Length); + GrpcServerLog.SerializedMessage(_serverCallContext.Logger, _serverCallContext.ResponseType, payload.Length); WriteMessage(payload); break; default: @@ -168,7 +168,7 @@ private IBufferWriter ResolveBufferWriter() private void EnsureMessageSizeAllowed(int payloadLength) { - if (payloadLength > _serverCallContext.MethodContext.MaxSendMessageSize) + if (payloadLength > _serverCallContext.Options.MaxSendMessageSize) { throw new RpcException(SendingMessageExceedsLimitStatus); } @@ -196,7 +196,7 @@ public override void Complete() } else { - GrpcServerLog.SerializedMessage(_serverCallContext.Logger, _serverCallContext.MethodContext.ResponseType, _payloadLength.GetValueOrDefault()); + GrpcServerLog.SerializedMessage(_serverCallContext.Logger, _serverCallContext.ResponseType, _payloadLength.GetValueOrDefault()); } break; default: @@ -228,7 +228,7 @@ private ReadOnlySpan CompressMessage(ReadOnlySpan messageData) // Compression stream must be disposed before its content is read. // GZipStream writes final Adler32 at the end of the stream on dispose. - using (var compressionStream = _compressionProvider.CreateCompressionStream(output, _serverCallContext.MethodContext.ResponseCompressionLevel)) + using (var compressionStream = _compressionProvider.CreateCompressionStream(output, _serverCallContext.Options.ResponseCompressionLevel)) { compressionStream.Write(messageData); } diff --git a/src/Grpc.AspNetCore.Server/Internal/HttpContextServerCallContext.cs b/src/Grpc.AspNetCore.Server/Internal/HttpContextServerCallContext.cs index db1ba8e6f..3a5ad01f0 100644 --- a/src/Grpc.AspNetCore.Server/Internal/HttpContextServerCallContext.cs +++ b/src/Grpc.AspNetCore.Server/Internal/HttpContextServerCallContext.cs @@ -24,6 +24,7 @@ using System.Threading.Tasks; using Grpc.Core; using Grpc.Shared; +using Grpc.Shared.Server; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Logging; @@ -43,16 +44,20 @@ internal sealed partial class HttpContextServerCallContext : ServerCallContext, private HttpContextSerializationContext? _serializationContext; private DefaultDeserializationContext? _deserializationContext; - internal HttpContextServerCallContext(HttpContext httpContext, MethodContext methodContext, ILogger logger) + internal HttpContextServerCallContext(HttpContext httpContext, MethodOptions options, Type requestType, Type responseType, ILogger logger) { HttpContext = httpContext; - MethodContext = methodContext; + Options = options; + RequestType = requestType; + ResponseType = responseType; Logger = logger; } internal ILogger Logger { get; } internal HttpContext HttpContext { get; } - internal MethodContext MethodContext { get; } + internal MethodOptions Options { get; } + internal Type RequestType { get; } + internal Type ResponseType { get; } internal string? ResponseGrpcEncoding { get; private set; } internal HttpContextSerializationContext SerializationContext @@ -170,7 +175,7 @@ private void ProcessHandlerError(Exception ex, string method) { GrpcServerLog.ErrorExecutingServiceMethod(Logger, method, ex); - var message = ErrorMessageHelper.BuildErrorMessage("Exception was thrown by handler.", ex, MethodContext.EnableDetailedErrors); + var message = ErrorMessageHelper.BuildErrorMessage("Exception was thrown by handler.", ex, Options.EnableDetailedErrors); _status = new Status(StatusCode.Unknown, message); } @@ -377,7 +382,7 @@ public void Initialize(ISystemClock? clock = null) DeadlineManager = new ServerCallDeadlineManager(clock ?? SystemClock.Instance, timeout, DeadlineExceededAsync, HttpContext.RequestAborted); } - var serviceDefaultCompression = MethodContext.ResponseCompressionAlgorithm; + var serviceDefaultCompression = Options.ResponseCompressionAlgorithm; if (serviceDefaultCompression != null && !string.Equals(serviceDefaultCompression, GrpcProtocolConstants.IdentityGrpcEncoding, StringComparison.Ordinal) && IsEncodingInRequestAcceptEncoding(serviceDefaultCompression)) diff --git a/src/Grpc.AspNetCore.Server/Internal/MethodContext.cs b/src/Grpc.AspNetCore.Server/Internal/MethodContext.cs deleted file mode 100644 index 0bf4e03bc..000000000 --- a/src/Grpc.AspNetCore.Server/Internal/MethodContext.cs +++ /dev/null @@ -1,71 +0,0 @@ -#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.Collections.Generic; -using System.IO.Compression; -using Grpc.Net.Compression; - -namespace Grpc.AspNetCore.Server.Internal -{ - internal class MethodContext - { - public Type RequestType { get; } - public Type ResponseType { get; } - public Dictionary CompressionProviders { get; } - public InterceptorCollection Interceptors { get; } - // Fast check for whether the service has any interceptors - public bool HasInterceptors { get; } - public int? MaxSendMessageSize { get; } - public int? MaxReceiveMessageSize { get; } - public bool? EnableDetailedErrors { get; } - public string? ResponseCompressionAlgorithm { get; } - public CompressionLevel? ResponseCompressionLevel { get; } - - public MethodContext( - Type requestType, - Type responseType, - Dictionary compressionProviders, - InterceptorCollection interceptors, - int? maxSendMessageSize, - int? maxReceiveMessageSize, - bool? enableDetailedErrors, - string? responseCompressionAlgorithm, - CompressionLevel? responseCompressionLevel) - { - RequestType = requestType; - ResponseType = responseType; - CompressionProviders = compressionProviders; - Interceptors = interceptors; - HasInterceptors = interceptors.Count > 0; - MaxSendMessageSize = maxSendMessageSize; - MaxReceiveMessageSize = maxReceiveMessageSize; - EnableDetailedErrors = enableDetailedErrors; - ResponseCompressionAlgorithm = responseCompressionAlgorithm; - ResponseCompressionLevel = responseCompressionLevel; - - if (ResponseCompressionAlgorithm != null) - { - if (!CompressionProviders.TryGetValue(ResponseCompressionAlgorithm, out var _)) - { - throw new InvalidOperationException($"The configured response compression algorithm '{ResponseCompressionAlgorithm}' does not have a matching compression provider."); - } - } - } - } -} diff --git a/src/Grpc.AspNetCore.Server/Internal/PipeExtensions.cs b/src/Grpc.AspNetCore.Server/Internal/PipeExtensions.cs index a016eaa60..fdfa3816d 100644 --- a/src/Grpc.AspNetCore.Server/Internal/PipeExtensions.cs +++ b/src/Grpc.AspNetCore.Server/Internal/PipeExtensions.cs @@ -339,7 +339,7 @@ private static bool TryReadMessage(ref ReadOnlySequence buffer, HttpContex return false; } - if (messageLength > context.MethodContext.MaxReceiveMessageSize) + if (messageLength > context.Options.MaxReceiveMessageSize) { throw new RpcException(ReceivedMessageExceedsLimitStatus); } @@ -366,7 +366,7 @@ private static bool TryReadMessage(ref ReadOnlySequence buffer, HttpContex } // Performance improvement would be to decompress without converting to an intermediary byte array - if (!TryDecompressMessage(context.Logger, encoding, context.MethodContext.CompressionProviders, messageBuffer, out var decompressedMessage)) + if (!TryDecompressMessage(context.Logger, encoding, context.Options.CompressionProviders, messageBuffer, out var decompressedMessage)) { // https://github.com/grpc/grpc/blob/master/doc/compression.md#test-cases // A message compressed by a client in a way not supported by its server MUST fail with status UNIMPLEMENTED, @@ -374,7 +374,7 @@ private static bool TryReadMessage(ref ReadOnlySequence buffer, HttpContex // grpc-accept-encoding header MUST NOT contain the compression method (encoding) used. var supportedEncodings = new List(); supportedEncodings.Add(GrpcProtocolConstants.IdentityGrpcEncoding); - supportedEncodings.AddRange(context.MethodContext.CompressionProviders.Select(p => p.Key)); + supportedEncodings.AddRange(context.Options.CompressionProviders.Select(p => p.Key)); if (!context.HttpContext.Response.HasStarted) { @@ -399,7 +399,7 @@ private static bool TryReadMessage(ref ReadOnlySequence buffer, HttpContex return true; } - private static bool TryDecompressMessage(ILogger logger, string compressionEncoding, Dictionary compressionProviders, ReadOnlySequence messageData, [NotNullWhen(true)]out ReadOnlySequence? result) + private static bool TryDecompressMessage(ILogger logger, string compressionEncoding, IReadOnlyDictionary compressionProviders, ReadOnlySequence messageData, [NotNullWhen(true)]out ReadOnlySequence? result) { if (compressionProviders.TryGetValue(compressionEncoding, out var compressionProvider)) { diff --git a/src/Grpc.AspNetCore.Server/Internal/ServerCallHandlerFactory.cs b/src/Grpc.AspNetCore.Server/Internal/ServerCallHandlerFactory.cs index fa39fabb5..fd985b6b8 100644 --- a/src/Grpc.AspNetCore.Server/Internal/ServerCallHandlerFactory.cs +++ b/src/Grpc.AspNetCore.Server/Internal/ServerCallHandlerFactory.cs @@ -17,14 +17,11 @@ #endregion using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Grpc.AspNetCore.Server.Internal.CallHandlers; using Grpc.AspNetCore.Server.Model; using Grpc.Core; -using Grpc.Net.Compression; +using Grpc.Shared.Server; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -37,91 +34,65 @@ namespace Grpc.AspNetCore.Server.Internal internal partial class ServerCallHandlerFactory where TService : class { private readonly ILoggerFactory _loggerFactory; + private readonly IGrpcServiceActivator _serviceActivator; private readonly GrpcServiceOptions _globalOptions; private readonly GrpcServiceOptions _serviceOptions; - private readonly IGrpcServiceActivator _serviceActivator; - private readonly IServiceProvider _serviceProvider; public ServerCallHandlerFactory( ILoggerFactory loggerFactory, IOptions globalOptions, IOptions> serviceOptions, - IGrpcServiceActivator serviceActivator, - IServiceProvider serviceProvider) + IGrpcServiceActivator serviceActivator) { _loggerFactory = loggerFactory; + _serviceActivator = serviceActivator; _serviceOptions = serviceOptions.Value; _globalOptions = globalOptions.Value; - _serviceActivator = serviceActivator; - _serviceProvider = serviceProvider; } - private MethodContext CreateMethodContext() + private MethodOptions CreateMethodOptions() { - // This is required to get ensure that service methods without any explicit configuration - // will continue to get the global configuration options - var resolvedCompressionProviders = new Dictionary(StringComparer.Ordinal); - AddCompressionProviders(resolvedCompressionProviders, _serviceOptions._compressionProviders); - AddCompressionProviders(resolvedCompressionProviders, _globalOptions._compressionProviders); - - var interceptors = new InterceptorCollection(); - interceptors.AddRange(_globalOptions.Interceptors); - interceptors.AddRange(_serviceOptions.Interceptors); - - return new MethodContext - ( - requestType: typeof(TRequest), - responseType: typeof(TResponse), - compressionProviders: resolvedCompressionProviders, - interceptors: interceptors, - maxSendMessageSize: _serviceOptions.MaxSendMessageSize ?? _globalOptions.MaxSendMessageSize, - maxReceiveMessageSize: _serviceOptions.MaxReceiveMessageSize ?? _globalOptions.MaxReceiveMessageSize, - enableDetailedErrors: _serviceOptions.EnableDetailedErrors ?? _globalOptions.EnableDetailedErrors, - responseCompressionAlgorithm: _serviceOptions.ResponseCompressionAlgorithm ?? _globalOptions.ResponseCompressionAlgorithm, - responseCompressionLevel: _serviceOptions.ResponseCompressionLevel ?? _globalOptions.ResponseCompressionLevel - ); - } - - private static void AddCompressionProviders(Dictionary resolvedProviders, IList? compressionProviders) - { - if (compressionProviders != null) - { - foreach (var compressionProvider in compressionProviders) - { - if (!resolvedProviders.ContainsKey(compressionProvider.EncodingName)) - { - resolvedProviders.Add(compressionProvider.EncodingName, compressionProvider); - } - } - } + return MethodOptions.Create(new[] { _globalOptions, _serviceOptions }); } public UnaryServerCallHandler CreateUnary(Method method, UnaryServerMethod invoker) where TRequest : class where TResponse : class { - return new UnaryServerCallHandler(method, invoker, CreateMethodContext(), _loggerFactory, _serviceActivator, _serviceProvider); + var options = CreateMethodOptions(); + var methodInvoker = new UnaryServerMethodInvoker(invoker, method, options, _serviceActivator); + + return new UnaryServerCallHandler(methodInvoker, _loggerFactory); } public ClientStreamingServerCallHandler CreateClientStreaming(Method method, ClientStreamingServerMethod invoker) where TRequest : class where TResponse : class { - return new ClientStreamingServerCallHandler(method, invoker, CreateMethodContext(), _loggerFactory, _serviceActivator, _serviceProvider); + var options = CreateMethodOptions(); + var methodInvoker = new ClientStreamingServerMethodInvoker(invoker, method, options, _serviceActivator); + + return new ClientStreamingServerCallHandler(methodInvoker, _loggerFactory); } public DuplexStreamingServerCallHandler CreateDuplexStreaming(Method method, DuplexStreamingServerMethod invoker) where TRequest : class where TResponse : class { - return new DuplexStreamingServerCallHandler(method, invoker, CreateMethodContext(), _loggerFactory, _serviceActivator, _serviceProvider); + var options = CreateMethodOptions(); + var methodInvoker = new DuplexStreamingServerMethodInvoker(invoker, method, options, _serviceActivator); + + return new DuplexStreamingServerCallHandler(methodInvoker, _loggerFactory); } public ServerStreamingServerCallHandler CreateServerStreaming(Method method, ServerStreamingServerMethod invoker) where TRequest : class where TResponse : class { - return new ServerStreamingServerCallHandler(method, invoker, CreateMethodContext(), _loggerFactory, _serviceActivator, _serviceProvider); + var options = CreateMethodOptions(); + var methodInvoker = new ServerStreamingServerMethodInvoker(invoker, method, options, _serviceActivator); + + return new ServerStreamingServerCallHandler(methodInvoker, _loggerFactory); } public RequestDelegate CreateUnimplementedMethod() diff --git a/src/Grpc.AspNetCore.Server/Model/Internal/BinderServiceModelProvider.cs b/src/Grpc.AspNetCore.Server/Model/Internal/BinderServiceModelProvider.cs index 322548fbb..1f057865b 100644 --- a/src/Grpc.AspNetCore.Server/Model/Internal/BinderServiceModelProvider.cs +++ b/src/Grpc.AspNetCore.Server/Model/Internal/BinderServiceModelProvider.cs @@ -17,6 +17,7 @@ #endregion using System; +using Grpc.Shared.Server; using Microsoft.Extensions.Logging; namespace Grpc.AspNetCore.Server.Model.Internal diff --git a/src/Grpc.AspNetCore.Server/Model/Internal/MethodModel.cs b/src/Grpc.AspNetCore.Server/Model/Internal/MethodModel.cs index fe1124aa3..3f0afbf57 100644 --- a/src/Grpc.AspNetCore.Server/Model/Internal/MethodModel.cs +++ b/src/Grpc.AspNetCore.Server/Model/Internal/MethodModel.cs @@ -20,22 +20,23 @@ using System.Collections.Generic; using Grpc.Core; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Patterns; namespace Grpc.AspNetCore.Server.Model.Internal { internal class MethodModel { - public MethodModel(IMethod method, IList metadata, RequestDelegate requestDelegate) + public MethodModel(IMethod method, RoutePattern pattern, IList metadata, RequestDelegate requestDelegate) { Method = method; + Pattern = pattern; Metadata = metadata; RequestDelegate = requestDelegate; } public IMethod Method { get; } - + public RoutePattern Pattern { get; } public IList Metadata { get; } - public RequestDelegate RequestDelegate { get; } } } diff --git a/src/Grpc.AspNetCore.Server/Model/Internal/ProviderServiceBinder.cs b/src/Grpc.AspNetCore.Server/Model/Internal/ProviderServiceBinder.cs index 0f24ebc10..cc2b7c436 100644 --- a/src/Grpc.AspNetCore.Server/Model/Internal/ProviderServiceBinder.cs +++ b/src/Grpc.AspNetCore.Server/Model/Internal/ProviderServiceBinder.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Reflection; using Grpc.Core; +using Microsoft.AspNetCore.Routing; namespace Grpc.AspNetCore.Server.Model.Internal { @@ -86,6 +87,7 @@ public override void AddMethod(Method metadata.AddRange(typeof(TService).GetCustomAttributes(inherit: true)); // Add method metadata last so it has a higher priority metadata.AddRange(handlerMethod.GetCustomAttributes(inherit: true)); + metadata.Add(new HttpMethodMetadata(new[] { "POST" })); return (invoker, metadata); } diff --git a/src/Grpc.AspNetCore.Server/Model/Internal/ServiceRouteBuilder.cs b/src/Grpc.AspNetCore.Server/Model/Internal/ServiceRouteBuilder.cs index 8bba79576..42ee0cce1 100644 --- a/src/Grpc.AspNetCore.Server/Model/Internal/ServiceRouteBuilder.cs +++ b/src/Grpc.AspNetCore.Server/Model/Internal/ServiceRouteBuilder.cs @@ -63,12 +63,11 @@ internal List Build(IEndpointRouteBuilder endpointRo { foreach (var method in serviceMethodProviderContext.Methods) { - var pattern = method.Method.FullName; - var endpointBuilder = endpointRouteBuilder.MapPost(pattern, method.RequestDelegate); + var endpointBuilder = endpointRouteBuilder.Map(method.Pattern, method.RequestDelegate); endpointBuilder.Add(ep => { - ep.DisplayName = $"gRPC - {pattern}"; + ep.DisplayName = $"gRPC - {method.Pattern.RawText}"; ep.Metadata.Add(new GrpcMethodMetadata(typeof(TService), method.Method)); foreach (var item in method.Metadata) @@ -79,7 +78,7 @@ internal List Build(IEndpointRouteBuilder endpointRo endpointConventionBuilders.Add(endpointBuilder); - Log.AddedServiceMethod(_logger, method.Method.Name, method.Method.ServiceName, method.Method.Type, pattern); + Log.AddedServiceMethod(_logger, method.Method.Name, method.Method.ServiceName, method.Method.Type, method.Pattern.RawText); } } else diff --git a/src/Grpc.AspNetCore.Server/Model/ServiceMethodProviderContext.cs b/src/Grpc.AspNetCore.Server/Model/ServiceMethodProviderContext.cs index 75f7f926e..f326802d0 100644 --- a/src/Grpc.AspNetCore.Server/Model/ServiceMethodProviderContext.cs +++ b/src/Grpc.AspNetCore.Server/Model/ServiceMethodProviderContext.cs @@ -20,6 +20,8 @@ using Grpc.AspNetCore.Server.Internal; using Grpc.AspNetCore.Server.Model.Internal; using Grpc.Core; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Patterns; namespace Grpc.AspNetCore.Server.Model { @@ -52,9 +54,7 @@ public void AddUnaryMethod(Method meth where TResponse : class { var callHandler = _serverCallHandlerFactory.CreateUnary(method, invoker); - var methodModel = new MethodModel(method, metadata, callHandler.HandleCallAsync); - - Methods.Add(methodModel); + AddMethod(method, RoutePatternFactory.Parse(method.FullName), metadata, callHandler.HandleCallAsync); } /// @@ -70,9 +70,7 @@ public void AddServerStreamingMethod(Method(method, invoker); - var methodModel = new MethodModel(method, metadata, callHandler.HandleCallAsync); - - Methods.Add(methodModel); + AddMethod(method, RoutePatternFactory.Parse(method.FullName), metadata, callHandler.HandleCallAsync); } /// @@ -88,9 +86,7 @@ public void AddClientStreamingMethod(Method(method, invoker); - var methodModel = new MethodModel(method, metadata, callHandler.HandleCallAsync); - - Methods.Add(methodModel); + AddMethod(method, RoutePatternFactory.Parse(method.FullName), metadata, callHandler.HandleCallAsync); } /// @@ -106,8 +102,26 @@ public void AddDuplexStreamingMethod(Method(method, invoker); - var methodModel = new MethodModel(method, metadata, callHandler.HandleCallAsync); + AddMethod(method, RoutePatternFactory.Parse(method.FullName), metadata, callHandler.HandleCallAsync); + } + /// + /// Adds a method to a service. This method is handled by the specified . + /// This overload of AddMethod is intended for advanced scenarios where raw processing of HTTP requests + /// is desired. + /// Note: experimental API that can change or be removed without any prior notice. + /// + /// Request message type for this method. + /// Response message type for this method. + /// The method description. + /// The method pattern. This pattern is used by routing to match the method to an HTTP request. + /// The method metadata. This metadata can be used by routing and middleware when invoking a gRPC method. + /// The that is executed when the method is called. + public void AddMethod(Method method, RoutePattern pattern, IList metadata, RequestDelegate invoker) + where TRequest : class + where TResponse : class + { + var methodModel = new MethodModel(method, pattern, metadata, invoker); Methods.Add(methodModel); } } diff --git a/src/Grpc.AspNetCore.Server/Model/Internal/BindMethodFinder.cs b/src/Shared/Server/BindMethodFinder.cs similarity index 98% rename from src/Grpc.AspNetCore.Server/Model/Internal/BindMethodFinder.cs rename to src/Shared/Server/BindMethodFinder.cs index 48f33c538..2205d3492 100644 --- a/src/Grpc.AspNetCore.Server/Model/Internal/BindMethodFinder.cs +++ b/src/Shared/Server/BindMethodFinder.cs @@ -20,7 +20,7 @@ using System.Reflection; using Grpc.Core; -namespace Grpc.AspNetCore.Server.Model.Internal +namespace Grpc.Shared.Server { internal static class BindMethodFinder { diff --git a/src/Shared/Server/ClientStreamingServerMethodInvoker.cs b/src/Shared/Server/ClientStreamingServerMethodInvoker.cs new file mode 100644 index 000000000..11ea58ef3 --- /dev/null +++ b/src/Shared/Server/ClientStreamingServerMethodInvoker.cs @@ -0,0 +1,121 @@ +#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.Threading.Tasks; +using Grpc.AspNetCore.Server; +using Grpc.AspNetCore.Server.Model; +using Grpc.Core; +using Microsoft.AspNetCore.Http; + +namespace Grpc.Shared.Server +{ + /// + /// Client streaming server method invoker. + /// + /// Service type for this method. + /// Request message type for this method. + /// Response message type for this method. + internal sealed class ClientStreamingServerMethodInvoker : ServerMethodInvokerBase + where TRequest : class + where TResponse : class + where TService : class + { + private readonly ClientStreamingServerMethod _invoker; + private readonly ClientStreamingServerMethod? _pipelineInvoker; + + /// + /// Creates a new instance of . + /// + /// The client streaming method to invoke. + /// The description of the gRPC method. + /// The options used to execute the method. + /// The service activator used to create service instances. + public ClientStreamingServerMethodInvoker( + ClientStreamingServerMethod invoker, + Method method, + MethodOptions options, + IGrpcServiceActivator serviceActivator) + : base(method, options, serviceActivator) + { + _invoker = invoker; + + if (Options.HasInterceptors) + { + var interceptorPipeline = new InterceptorPipelineBuilder(Options.Interceptors); + _pipelineInvoker = interceptorPipeline.ClientStreamingPipeline(ResolvedInterceptorInvoker); + } + } + + private async Task ResolvedInterceptorInvoker(IAsyncStreamReader requestStream, ServerCallContext resolvedContext) + { + GrpcActivatorHandle serviceHandle = default; + try + { + serviceHandle = ServiceActivator.Create(resolvedContext.GetHttpContext().RequestServices); + return await _invoker( + serviceHandle.Instance, + requestStream, + resolvedContext); + } + finally + { + if (serviceHandle.Instance != null) + { + await ServiceActivator.ReleaseAsync(serviceHandle); + } + } + } + + /// + /// Invoke the client streaming method with the specified . + /// + /// The for the current request. + /// The . + /// The reader. + /// A that represents the asynchronous method. The + /// property returns the message. + public async Task Invoke(HttpContext httpContext, ServerCallContext serverCallContext, IAsyncStreamReader requestStream) + { + if (_pipelineInvoker == null) + { + GrpcActivatorHandle serviceHandle = default; + try + { + serviceHandle = ServiceActivator.Create(httpContext.RequestServices); + return await _invoker( + serviceHandle.Instance, + requestStream, + serverCallContext); + } + finally + { + if (serviceHandle.Instance != null) + { + await ServiceActivator.ReleaseAsync(serviceHandle); + } + } + } + else + { + return await _pipelineInvoker( + requestStream, + serverCallContext); + } + } + } +} diff --git a/src/Shared/Server/DuplexStreamingServerMethodInvoker.cs b/src/Shared/Server/DuplexStreamingServerMethodInvoker.cs new file mode 100644 index 000000000..4c2e7e7b0 --- /dev/null +++ b/src/Shared/Server/DuplexStreamingServerMethodInvoker.cs @@ -0,0 +1,124 @@ +#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.Threading.Tasks; +using Grpc.AspNetCore.Server; +using Grpc.AspNetCore.Server.Model; +using Grpc.Core; +using Microsoft.AspNetCore.Http; + +namespace Grpc.Shared.Server +{ + /// + /// Duplex streaming server method invoker. + /// + /// Service type for this method. + /// Request message type for this method. + /// Response message type for this method. + internal sealed class DuplexStreamingServerMethodInvoker : ServerMethodInvokerBase + where TRequest : class + where TResponse : class + where TService : class + { + private readonly DuplexStreamingServerMethod _invoker; + private readonly DuplexStreamingServerMethod? _pipelineInvoker; + + /// + /// Creates a new instance of . + /// + /// The duplex streaming method to invoke. + /// The description of the gRPC method. + /// The options used to execute the method. + /// The service activator used to create service instances. + public DuplexStreamingServerMethodInvoker( + DuplexStreamingServerMethod invoker, + Method method, + MethodOptions options, + IGrpcServiceActivator serviceActivator) + : base(method, options, serviceActivator) + { + _invoker = invoker; + + if (Options.HasInterceptors) + { + var interceptorPipeline = new InterceptorPipelineBuilder(Options.Interceptors); + _pipelineInvoker = interceptorPipeline.DuplexStreamingPipeline(ResolvedInterceptorInvoker); + } + } + + private async Task ResolvedInterceptorInvoker(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext resolvedContext) + { + GrpcActivatorHandle serviceHandle = default; + try + { + serviceHandle = ServiceActivator.Create(resolvedContext.GetHttpContext().RequestServices); + await _invoker( + serviceHandle.Instance, + requestStream, + responseStream, + resolvedContext); + } + finally + { + if (serviceHandle.Instance != null) + { + await ServiceActivator.ReleaseAsync(serviceHandle); + } + } + } + + /// + /// Invoke the duplex streaming method with the specified . + /// + /// The for the current request. + /// The . + /// The reader. + /// The writer. + /// A that represents the asynchronous method. + public async Task Invoke(HttpContext httpContext, ServerCallContext serverCallContext, IAsyncStreamReader requestStream, IServerStreamWriter responseStream) + { + if (_pipelineInvoker == null) + { + GrpcActivatorHandle serviceHandle = default; + try + { + serviceHandle = ServiceActivator.Create(httpContext.RequestServices); + await _invoker( + serviceHandle.Instance, + requestStream, + responseStream, + serverCallContext); + } + finally + { + if (serviceHandle.Instance != null) + { + await ServiceActivator.ReleaseAsync(serviceHandle); + } + } + } + else + { + await _pipelineInvoker( + requestStream, + responseStream, + serverCallContext); + } + } + } +} diff --git a/src/Grpc.AspNetCore.Server/Internal/CallHandlers/InterceptorPipelineBuilder.cs b/src/Shared/Server/InterceptorPipelineBuilder.cs similarity index 82% rename from src/Grpc.AspNetCore.Server/Internal/CallHandlers/InterceptorPipelineBuilder.cs rename to src/Shared/Server/InterceptorPipelineBuilder.cs index 568e3d882..93d7e5f2a 100644 --- a/src/Grpc.AspNetCore.Server/Internal/CallHandlers/InterceptorPipelineBuilder.cs +++ b/src/Shared/Server/InterceptorPipelineBuilder.cs @@ -17,35 +17,35 @@ #endregion using System; +using System.Collections.Generic; +using Grpc.AspNetCore.Server; using Grpc.Core; using Grpc.Core.Interceptors; -using Microsoft.Extensions.DependencyInjection; -namespace Grpc.AspNetCore.Server.Internal.CallHandlers +namespace Grpc.Shared.Server { internal class InterceptorPipelineBuilder where TRequest : class where TResponse : class { - private readonly InterceptorCollection _interceptors; - private readonly IServiceProvider _serviceProvider; + private readonly IReadOnlyList _interceptors; - public InterceptorPipelineBuilder(InterceptorCollection interceptors, IServiceProvider serviceProvider) + public InterceptorPipelineBuilder(IReadOnlyList interceptors) { _interceptors = interceptors; - _serviceProvider = serviceProvider; } public ClientStreamingServerMethod ClientStreamingPipeline(ClientStreamingServerMethod innerInvoker) { return BuildPipeline(innerInvoker, BuildInvoker); - static ClientStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, IServiceProvider serviceProvider, ClientStreamingServerMethod next) + static ClientStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, ClientStreamingServerMethod next) { return async (requestStream, context) => { + var serviceProvider = context.GetHttpContext().RequestServices; var interceptorActivator = interceptorRegistration.GetActivator(serviceProvider); - var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, context.GetHttpContext().RequestServices); + var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, serviceProvider); try { @@ -63,12 +63,13 @@ internal DuplexStreamingServerMethod DuplexStreamingPipelin { return BuildPipeline(innerInvoker, BuildInvoker); - static DuplexStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, IServiceProvider serviceProvider, DuplexStreamingServerMethod next) + static DuplexStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, DuplexStreamingServerMethod next) { return async (requestStream, responseStream, context) => { + var serviceProvider = context.GetHttpContext().RequestServices; var interceptorActivator = interceptorRegistration.GetActivator(serviceProvider); - var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, context.GetHttpContext().RequestServices); + var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, serviceProvider); try { @@ -86,12 +87,13 @@ internal ServerStreamingServerMethod ServerStreamingPipelin { return BuildPipeline(innerInvoker, BuildInvoker); - static ServerStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, IServiceProvider serviceProvider, ServerStreamingServerMethod next) + static ServerStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, ServerStreamingServerMethod next) { return async (request, responseStream, context) => { + var serviceProvider = context.GetHttpContext().RequestServices; var interceptorActivator = interceptorRegistration.GetActivator(serviceProvider); - var interceptorHandle = interceptorActivator.Create(context.GetHttpContext().RequestServices, interceptorRegistration); + var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, serviceProvider); if (interceptorHandle.Instance == null) { @@ -114,12 +116,13 @@ internal UnaryServerMethod UnaryPipeline(UnaryServerMethod< { return BuildPipeline(innerInvoker, BuildInvoker); - static UnaryServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, IServiceProvider serviceProvider, UnaryServerMethod next) + static UnaryServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, UnaryServerMethod next) { return async (request, context) => { + var serviceProvider = context.GetHttpContext().RequestServices; var interceptorActivator = interceptorRegistration.GetActivator(serviceProvider); - var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, context.GetHttpContext().RequestServices); + var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, serviceProvider); try { @@ -133,7 +136,7 @@ static UnaryServerMethod BuildInvoker(InterceptorRegistrati } } - private T BuildPipeline(T innerInvoker, Func wrapInvoker) + private T BuildPipeline(T innerInvoker, Func wrapInvoker) { // The inner invoker will create the service instance and invoke the method var resolvedInvoker = innerInvoker; @@ -141,7 +144,7 @@ private T BuildPipeline(T innerInvoker, Func= 0; i--) { - resolvedInvoker = wrapInvoker(_interceptors[i], _serviceProvider, resolvedInvoker); + resolvedInvoker = wrapInvoker(_interceptors[i], resolvedInvoker); } return resolvedInvoker; diff --git a/src/Shared/Server/MethodOptions.cs b/src/Shared/Server/MethodOptions.cs new file mode 100644 index 000000000..d689ea56d --- /dev/null +++ b/src/Shared/Server/MethodOptions.cs @@ -0,0 +1,160 @@ +#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.Collections.Generic; +using System.IO.Compression; +using System.Linq; +using Grpc.AspNetCore.Server; +using Grpc.Net.Compression; + +namespace Grpc.Shared.Server +{ + /// + /// Options used to execute a gRPC method. + /// + internal sealed class MethodOptions + { + /// + /// Gets the list of compression providers used to compress and decompress gRPC messages. + /// + public IReadOnlyDictionary CompressionProviders { get; } + + /// + /// Get a collection of interceptors to be executed with every call. Interceptors are executed in order. + /// + public IReadOnlyList Interceptors { get; } + + /// + /// Gets the maximum message size in bytes that can be sent from the server. + /// + public int? MaxSendMessageSize { get; } + + /// + /// Gets the maximum message size in bytes that can be received by the server. + /// + public int? MaxReceiveMessageSize { get; } + + /// + /// Gets a value indicating whether detailed error messages are sent to the peer. + /// Detailed error messages include details from exceptions thrown on the server. + /// + public bool? EnableDetailedErrors { get; } + + /// + /// Gets the compression algorithm used to compress messages sent from the server. + /// The request grpc-accept-encoding header value must contain this algorithm for it to + /// be used. + /// + public string? ResponseCompressionAlgorithm { get; } + + /// + /// Gets the compression level used to compress messages sent from the server. + /// The compression level will be passed to the compression provider. + /// + public CompressionLevel? ResponseCompressionLevel { get; } + + // Fast check for whether the service has any interceptors + internal bool HasInterceptors { get; } + + private MethodOptions( + Dictionary compressionProviders, + InterceptorCollection interceptors, + int? maxSendMessageSize, + int? maxReceiveMessageSize, + bool? enableDetailedErrors, + string? responseCompressionAlgorithm, + CompressionLevel? responseCompressionLevel) + { + CompressionProviders = compressionProviders; + Interceptors = interceptors; + HasInterceptors = interceptors.Count > 0; + MaxSendMessageSize = maxSendMessageSize; + MaxReceiveMessageSize = maxReceiveMessageSize; + EnableDetailedErrors = enableDetailedErrors; + ResponseCompressionAlgorithm = responseCompressionAlgorithm; + ResponseCompressionLevel = responseCompressionLevel; + + if (ResponseCompressionAlgorithm != null) + { + if (!CompressionProviders.TryGetValue(ResponseCompressionAlgorithm, out var _)) + { + throw new InvalidOperationException($"The configured response compression algorithm '{ResponseCompressionAlgorithm}' does not have a matching compression provider."); + } + } + } + + /// + /// Creates method options by merging together the settings the specificed collection. + /// The should be ordered with items arranged in ascending order of precedence. + /// + /// A collection of instances, arranged in ascending order of precedence. + /// A new instanced with settings merged from specifid collection. + public static MethodOptions Create(IEnumerable serviceOptions) + { + // This is required to get ensure that service methods without any explicit configuration + // will continue to get the global configuration options + var resolvedCompressionProviders = new Dictionary(StringComparer.Ordinal); + var tempInterceptors = new List(); + int? maxSendMessageSize = null; + int? maxReceiveMessageSize = null; + bool? enableDetailedErrors = null; + string? responseCompressionAlgorithm = null; + CompressionLevel? responseCompressionLevel = null; + + foreach (var options in serviceOptions.Reverse()) + { + AddCompressionProviders(resolvedCompressionProviders, options._compressionProviders); + tempInterceptors.InsertRange(0, options.Interceptors); + maxSendMessageSize ??= options.MaxSendMessageSize; + maxReceiveMessageSize ??= options.MaxReceiveMessageSize; + enableDetailedErrors ??= options.EnableDetailedErrors; + responseCompressionAlgorithm ??= options.ResponseCompressionAlgorithm; + responseCompressionLevel ??= options.ResponseCompressionLevel; + } + + var interceptors = new InterceptorCollection(); + interceptors.AddRange(tempInterceptors); + + return new MethodOptions + ( + compressionProviders: resolvedCompressionProviders, + interceptors: interceptors, + maxSendMessageSize: maxSendMessageSize, + maxReceiveMessageSize: maxReceiveMessageSize, + enableDetailedErrors: enableDetailedErrors, + responseCompressionAlgorithm: responseCompressionAlgorithm, + responseCompressionLevel: responseCompressionLevel + ); + } + + private static void AddCompressionProviders(Dictionary resolvedProviders, IList? compressionProviders) + { + if (compressionProviders != null) + { + foreach (var compressionProvider in compressionProviders) + { + if (!resolvedProviders.ContainsKey(compressionProvider.EncodingName)) + { + resolvedProviders.Add(compressionProvider.EncodingName, compressionProvider); + } + } + } + } + } +} diff --git a/src/Shared/Server/ServerMethodInvokerBase.cs b/src/Shared/Server/ServerMethodInvokerBase.cs new file mode 100644 index 000000000..2e9e72c23 --- /dev/null +++ b/src/Shared/Server/ServerMethodInvokerBase.cs @@ -0,0 +1,66 @@ +#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 Grpc.AspNetCore.Server; +using Grpc.Core; + +namespace Grpc.Shared.Server +{ + /// + /// Server method invoker base type. + /// + /// Service type for this method. + /// Request message type for this method. + /// Response message type for this method. + internal abstract class ServerMethodInvokerBase + where TRequest : class + where TResponse : class + where TService : class + { + /// + /// Gets the description of the gRPC method. + /// + public Method Method { get; } + + /// + /// Gets the options used to execute the method. + /// + public MethodOptions Options { get; } + + /// + /// Gets the service activator used to create service instances. + /// + public IGrpcServiceActivator ServiceActivator { get; } + + /// + /// Creates a new instance of . + /// + /// The description of the gRPC method. + /// The options used to execute the method. + /// The service activator used to create service instances. + private protected ServerMethodInvokerBase( + Method method, + MethodOptions options, + IGrpcServiceActivator serviceActivator) + { + Method = method; + Options = options; + ServiceActivator = serviceActivator; + } + } +} diff --git a/src/Shared/Server/ServerStreamingServerMethodInvoker.cs b/src/Shared/Server/ServerStreamingServerMethodInvoker.cs new file mode 100644 index 000000000..ada5a4117 --- /dev/null +++ b/src/Shared/Server/ServerStreamingServerMethodInvoker.cs @@ -0,0 +1,124 @@ +#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.Threading.Tasks; +using Grpc.AspNetCore.Server; +using Grpc.AspNetCore.Server.Model; +using Grpc.Core; +using Microsoft.AspNetCore.Http; + +namespace Grpc.Shared.Server +{ + /// + /// Server streaming server method invoker. + /// + /// Service type for this method. + /// Request message type for this method. + /// Response message type for this method. + internal sealed class ServerStreamingServerMethodInvoker : ServerMethodInvokerBase + where TRequest : class + where TResponse : class + where TService : class + { + private readonly ServerStreamingServerMethod _invoker; + private readonly ServerStreamingServerMethod? _pipelineInvoker; + + /// + /// Creates a new instance of . + /// + /// The server streaming method to invoke. + /// The description of the gRPC method. + /// The options used to execute the method. + /// The service activator used to create service instances. + public ServerStreamingServerMethodInvoker( + ServerStreamingServerMethod invoker, + Method method, + MethodOptions options, + IGrpcServiceActivator serviceActivator) + : base(method, options, serviceActivator) + { + _invoker = invoker; + + if (Options.HasInterceptors) + { + var interceptorPipeline = new InterceptorPipelineBuilder(Options.Interceptors); + _pipelineInvoker = interceptorPipeline.ServerStreamingPipeline(ResolvedInterceptorInvoker); + } + } + + private async Task ResolvedInterceptorInvoker(TRequest request, IServerStreamWriter responseStream, ServerCallContext resolvedContext) + { + GrpcActivatorHandle serviceHandle = default; + try + { + serviceHandle = ServiceActivator.Create(resolvedContext.GetHttpContext().RequestServices); + await _invoker( + serviceHandle.Instance, + request, + responseStream, + resolvedContext); + } + finally + { + if (serviceHandle.Instance != null) + { + await ServiceActivator.ReleaseAsync(serviceHandle); + } + } + } + + /// + /// Invoke the server streaming method with the specified . + /// + /// The for the current request. + /// The . + /// The message. + /// The stream writer. + /// A that represents the asynchronous method. + public async Task Invoke(HttpContext httpContext, ServerCallContext serverCallContext, TRequest request, IServerStreamWriter streamWriter) + { + if (_pipelineInvoker == null) + { + GrpcActivatorHandle serviceHandle = default; + try + { + serviceHandle = ServiceActivator.Create(httpContext.RequestServices); + await _invoker( + serviceHandle.Instance, + request, + streamWriter, + serverCallContext); + } + finally + { + if (serviceHandle.Instance != null) + { + await ServiceActivator.ReleaseAsync(serviceHandle); + } + } + } + else + { + await _pipelineInvoker( + request, + streamWriter, + serverCallContext); + } + } + } +} diff --git a/src/Shared/Server/UnaryServerMethodInvoker.cs b/src/Shared/Server/UnaryServerMethodInvoker.cs new file mode 100644 index 000000000..5b817b1ed --- /dev/null +++ b/src/Shared/Server/UnaryServerMethodInvoker.cs @@ -0,0 +1,118 @@ +#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.Threading.Tasks; +using Grpc.AspNetCore.Server; +using Grpc.AspNetCore.Server.Model; +using Grpc.Core; +using Microsoft.AspNetCore.Http; + +namespace Grpc.Shared.Server +{ + /// + /// Unary server method invoker. + /// + /// Service type for this method. + /// Request message type for this method. + /// Response message type for this method. + internal sealed class UnaryServerMethodInvoker : ServerMethodInvokerBase + where TRequest : class + where TResponse : class + where TService : class + { + private readonly UnaryServerMethod _invoker; + private readonly UnaryServerMethod? _pipelineInvoker; + + /// + /// Creates a new instance of . + /// + /// The unary method to invoke. + /// The description of the gRPC method. + /// The options used to execute the method. + /// The service activator used to create service instances. + public UnaryServerMethodInvoker( + UnaryServerMethod invoker, + Method method, + MethodOptions options, + IGrpcServiceActivator serviceActivator) + : base(method, options, serviceActivator) + { + _invoker = invoker; + + if (Options.HasInterceptors) + { + var interceptorPipeline = new InterceptorPipelineBuilder(Options.Interceptors); + _pipelineInvoker = interceptorPipeline.UnaryPipeline(ResolvedInterceptorInvoker); + } + } + + private async Task ResolvedInterceptorInvoker(TRequest resolvedRequest, ServerCallContext resolvedContext) + { + GrpcActivatorHandle serviceHandle = default; + try + { + serviceHandle = ServiceActivator.Create(resolvedContext.GetHttpContext().RequestServices); + return await _invoker(serviceHandle.Instance, resolvedRequest, resolvedContext); + } + finally + { + if (serviceHandle.Instance != null) + { + await ServiceActivator.ReleaseAsync(serviceHandle); + } + } + } + + /// + /// Invoke the unary method with the specified . + /// + /// The for the current request. + /// The . + /// The message. + /// A that represents the asynchronous method. The + /// property returns the message. + public async Task Invoke(HttpContext httpContext, ServerCallContext serverCallContext, TRequest request) + { + if (_pipelineInvoker == null) + { + GrpcActivatorHandle serviceHandle = default; + try + { + serviceHandle = ServiceActivator.Create(httpContext.RequestServices); + return await _invoker( + serviceHandle.Instance, + request, + serverCallContext); + } + finally + { + if (serviceHandle.Instance != null) + { + await ServiceActivator.ReleaseAsync(serviceHandle); + } + } + } + else + { + return await _pipelineInvoker( + request, + serverCallContext); + } + } + } +} diff --git a/test/Grpc.AspNetCore.Server.Tests/BindMethodFinderTests.cs b/test/Grpc.AspNetCore.Server.Tests/BindMethodFinderTests.cs index 46395b47b..a25db1a87 100644 --- a/test/Grpc.AspNetCore.Server.Tests/BindMethodFinderTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/BindMethodFinderTests.cs @@ -17,10 +17,9 @@ #endregion using System; -using Grpc.AspNetCore.Server.Internal; -using Grpc.AspNetCore.Server.Model.Internal; using Grpc.AspNetCore.Server.Tests.TestObjects.Services.WithAttribute; using Grpc.AspNetCore.Server.Tests.TestObjects.Services.WithoutAttribute; +using Grpc.Shared.Server; using NUnit.Framework; namespace Grpc.AspNetCore.Server.Tests diff --git a/test/Grpc.AspNetCore.Server.Tests/CallHandlerTests.cs b/test/Grpc.AspNetCore.Server.Tests/CallHandlerTests.cs index d653c12bf..7101c2c0a 100644 --- a/test/Grpc.AspNetCore.Server.Tests/CallHandlerTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/CallHandlerTests.cs @@ -25,15 +25,18 @@ using System.Threading.Tasks; using Grpc.AspNetCore.Server.Internal; using Grpc.AspNetCore.Server.Internal.CallHandlers; +using Grpc.AspNetCore.Server.Model; using Grpc.AspNetCore.Server.Tests.Infrastructure; using Grpc.AspNetCore.Server.Tests.TestObjects; using Grpc.Core; using Grpc.Net.Compression; +using Grpc.Shared.Server; using Grpc.Tests.Shared; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; @@ -123,8 +126,10 @@ public async Task SetResponseTrailers_FeatureMissing_ThrowError() // Arrange var testSink = new TestSink(); var testLoggerFactory = new TestLoggerFactory(testSink, true); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); - var httpContext = HttpContextHelpers.CreateContext(skipTrailerFeatureSet: true); + var httpContext = HttpContextHelpers.CreateContext(skipTrailerFeatureSet: true, serviceProvider: serviceCollection.BuildServiceProvider()); var call = CreateHandler(MethodType.ClientStreaming, testLoggerFactory); // Act @@ -179,36 +184,36 @@ private static ServerCallHandlerBase Crea { case MethodType.Unary: return new UnaryServerCallHandler( - method, - (service, reader, context) => Task.FromResult(new TestMessage()), - HttpContextServerCallContextHelper.CreateMethodContext(), - loggerFactory ?? NullLoggerFactory.Instance, - new TestGrpcServiceActivator(), - TestServiceProvider.Instance); + new UnaryServerMethodInvoker( + (service, reader, context) => Task.FromResult(new TestMessage()), + method, + HttpContextServerCallContextHelper.CreateMethodOptions(), + new TestGrpcServiceActivator()), + loggerFactory ?? NullLoggerFactory.Instance); case MethodType.ClientStreaming: return new ClientStreamingServerCallHandler( - method, - (service, reader, context) => Task.FromResult(new TestMessage()), - HttpContextServerCallContextHelper.CreateMethodContext(), - loggerFactory ?? NullLoggerFactory.Instance, - new TestGrpcServiceActivator(), - TestServiceProvider.Instance); + new ClientStreamingServerMethodInvoker( + (service, reader, context) => Task.FromResult(new TestMessage()), + method, + HttpContextServerCallContextHelper.CreateMethodOptions(), + new TestGrpcServiceActivator()), + loggerFactory ?? NullLoggerFactory.Instance); case MethodType.ServerStreaming: return new ServerStreamingServerCallHandler( - method, - (service, request, writer, context) => Task.FromResult(new TestMessage()), - HttpContextServerCallContextHelper.CreateMethodContext(), - loggerFactory ?? NullLoggerFactory.Instance, - new TestGrpcServiceActivator(), - TestServiceProvider.Instance); + new ServerStreamingServerMethodInvoker( + (service, request, writer, context) => Task.FromResult(new TestMessage()), + method, + HttpContextServerCallContextHelper.CreateMethodOptions(), + new TestGrpcServiceActivator()), + loggerFactory ?? NullLoggerFactory.Instance); case MethodType.DuplexStreaming: return new DuplexStreamingServerCallHandler( - method, - (service, reader, writer, context) => Task.CompletedTask, - HttpContextServerCallContextHelper.CreateMethodContext(), - loggerFactory ?? NullLoggerFactory.Instance, - new TestGrpcServiceActivator(), - TestServiceProvider.Instance); + new DuplexStreamingServerMethodInvoker( + (service, reader, writer, context) => Task.CompletedTask, + method, + HttpContextServerCallContextHelper.CreateMethodOptions(), + new TestGrpcServiceActivator()), + loggerFactory ?? NullLoggerFactory.Instance); default: throw new ArgumentException(); } diff --git a/test/Grpc.AspNetCore.Server.Tests/HttpContextStreamReaderTests.cs b/test/Grpc.AspNetCore.Server.Tests/HttpContextStreamReaderTests.cs index eaa75d569..547857282 100644 --- a/test/Grpc.AspNetCore.Server.Tests/HttpContextStreamReaderTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/HttpContextStreamReaderTests.cs @@ -39,7 +39,7 @@ public void MoveNext_AlreadyCancelledToken_CancelReturnImmediately() var httpContext = new DefaultHttpContext(); var serverCallContext = HttpContextServerCallContextHelper.CreateServerCallContext(httpContext); - var reader = new HttpContextStreamReader(serverCallContext, MessageHelpers.HelloReplyMarshaller.ContextualDeserializer); + var reader = new HttpContextStreamReader(serverCallContext, MessageHelpers.ServiceMethod.ResponseMarshaller.ContextualDeserializer); // Act var nextTask = reader.MoveNext(new CancellationToken(true)); @@ -58,7 +58,7 @@ public async Task MoveNext_TokenCancelledDuringMoveNext_CancelTask() var httpContext = new DefaultHttpContext(); httpContext.Features.Set(new TestRequestBodyPipeFeature(PipeReader.Create(ms))); var serverCallContext = HttpContextServerCallContextHelper.CreateServerCallContext(httpContext); - var reader = new HttpContextStreamReader(serverCallContext, MessageHelpers.HelloReplyMarshaller.ContextualDeserializer); + var reader = new HttpContextStreamReader(serverCallContext, MessageHelpers.ServiceMethod.ResponseMarshaller.ContextualDeserializer); var cts = new CancellationTokenSource(); diff --git a/test/Grpc.AspNetCore.Server.Tests/HttpContextStreamWriterTests.cs b/test/Grpc.AspNetCore.Server.Tests/HttpContextStreamWriterTests.cs index e5770d95b..708b1b4bf 100644 --- a/test/Grpc.AspNetCore.Server.Tests/HttpContextStreamWriterTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/HttpContextStreamWriterTests.cs @@ -41,7 +41,7 @@ public async Task WriteAsync_DefaultWriteOptions_Flushes() var httpContext = new DefaultHttpContext(); httpContext.Features.Set(new TestResponseBodyFeature(PipeWriter.Create(ms))); var serverCallContext = HttpContextServerCallContextHelper.CreateServerCallContext(httpContext); - var writer = new HttpContextStreamWriter(serverCallContext, MessageHelpers.HelloReplyMarshaller.ContextualSerializer); + var writer = new HttpContextStreamWriter(serverCallContext, MessageHelpers.ServiceMethod.ResponseMarshaller.ContextualSerializer); // Act 1 await writer.WriteAsync(new HelloReply @@ -79,7 +79,7 @@ public async Task WriteAsync_BufferHintWriteOptions_DoesNotFlush() var httpContext = new DefaultHttpContext(); httpContext.Features.Set(new TestResponseBodyFeature(PipeWriter.Create(ms))); var serverCallContext = HttpContextServerCallContextHelper.CreateServerCallContext(httpContext); - var writer = new HttpContextStreamWriter(serverCallContext, MessageHelpers.HelloReplyMarshaller.ContextualSerializer); + var writer = new HttpContextStreamWriter(serverCallContext, MessageHelpers.ServiceMethod.ResponseMarshaller.ContextualSerializer); serverCallContext.WriteOptions = new WriteOptions(WriteFlags.BufferHint); // Act 1 diff --git a/test/Grpc.AspNetCore.Server.Tests/Model/BinderServiceMethodProviderTests.cs b/test/Grpc.AspNetCore.Server.Tests/Model/BinderServiceMethodProviderTests.cs index d6ecbddc4..2bc6cfc2e 100644 --- a/test/Grpc.AspNetCore.Server.Tests/Model/BinderServiceMethodProviderTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/Model/BinderServiceMethodProviderTests.cs @@ -39,17 +39,20 @@ public async Task OnServiceMethodDiscovery_ServiceWithDuplicateMethodNames_Succe { // Arrange var services = new ServiceCollection(); + services.AddSingleton(); var serverCallHandlerFactory = new ServerCallHandlerFactory( NullLoggerFactory.Instance, Options.Create(new GrpcServiceOptions()), Options.Create>(new GrpcServiceOptions()), - new TestGrpcServiceActivator(), - services.BuildServiceProvider()); + new TestGrpcServiceActivator()); var provider = new BinderServiceMethodProvider(NullLoggerFactory.Instance); var context = new ServiceMethodProviderContext(serverCallHandlerFactory); + var httpContext = HttpContextHelpers.CreateContext(); + httpContext.RequestServices = services.BuildServiceProvider(); + // Act provider.OnServiceMethodDiscovery(context); @@ -59,8 +62,6 @@ public async Task OnServiceMethodDiscovery_ServiceWithDuplicateMethodNames_Succe var methodModel = context.Methods[0]; Assert.AreEqual("SayHello", methodModel.Method.Name); - var httpContext = HttpContextHelpers.CreateContext(); - var ms = new MemoryStream(); MessageHelpers.WriteMessage(ms, new HelloRequest { diff --git a/test/Grpc.AspNetCore.Server.Tests/PipeExtensionsTestsBase.cs b/test/Grpc.AspNetCore.Server.Tests/PipeExtensionsTestsBase.cs index bd60a62c0..8a3e925a1 100644 --- a/test/Grpc.AspNetCore.Server.Tests/PipeExtensionsTestsBase.cs +++ b/test/Grpc.AspNetCore.Server.Tests/PipeExtensionsTestsBase.cs @@ -685,9 +685,9 @@ public async Task WriteMessageAsync_ExceedSendSize_ThrowError() public async Task WriteMessageAsync_GzipCompressed_WriteCompressedData() { // Arrange - var compressionProviders = new Dictionary + var compressionProviders = new List { - ["gzip"] = new GzipCompressionProvider(System.IO.Compression.CompressionLevel.Fastest) + new GzipCompressionProvider(System.IO.Compression.CompressionLevel.Fastest) }; var httpContext = new DefaultHttpContext(); @@ -711,7 +711,7 @@ public async Task WriteMessageAsync_GzipCompressed_WriteCompressedData() Assert.AreEqual(1, messageData[0]); // compression Assert.AreEqual(21, messageData[4]); // message length - byte[] result = Decompress(compressionProviders["gzip"], messageData); + byte[] result = Decompress(compressionProviders.Single(), messageData); Assert.AreEqual(1, result.Length); Assert.AreEqual(0x10, result[0]); } @@ -729,9 +729,9 @@ public async Task WriteMessageAsync_HasCustomCompressionLevel_WriteCompressedDat httpContext, responseCompressionAlgorithm: "Mock", responseCompressionLevel: System.IO.Compression.CompressionLevel.Optimal, - compressionProviders: new Dictionary + compressionProviders: new List { - [mockCompressionProvider.EncodingName] = mockCompressionProvider + mockCompressionProvider }); context.Initialize(); diff --git a/test/Grpc.AspNetCore.Server.Tests/TestObjects/TestGrpcServiceActivator.cs b/test/Grpc.AspNetCore.Server.Tests/TestObjects/TestGrpcServiceActivator.cs index 06c9ceeee..9a7866ab0 100644 --- a/test/Grpc.AspNetCore.Server.Tests/TestObjects/TestGrpcServiceActivator.cs +++ b/test/Grpc.AspNetCore.Server.Tests/TestObjects/TestGrpcServiceActivator.cs @@ -18,7 +18,6 @@ using System; using System.Threading.Tasks; -using Grpc.AspNetCore.Server.Internal; namespace Grpc.AspNetCore.Server.Tests.TestObjects { @@ -34,4 +33,4 @@ public ValueTask ReleaseAsync(GrpcActivatorHandle service) return default; } } -} +} \ No newline at end of file diff --git a/test/Shared/HttpContextHelpers.cs b/test/Shared/HttpContextHelpers.cs index f55fbed18..1e9cf6149 100644 --- a/test/Shared/HttpContextHelpers.cs +++ b/test/Shared/HttpContextHelpers.cs @@ -45,12 +45,14 @@ public static HttpContext CreateContext( bool isMaxRequestBodySizeFeatureReadOnly = false, bool skipTrailerFeatureSet = false, string? protocol = null, - string? contentType = null) + string? contentType = null, + IServiceProvider? serviceProvider = null) { var httpContext = new DefaultHttpContext(); var responseFeature = new TestHttpResponseFeature(); var responseBodyFeature = new TestHttpResponseBodyFeature(httpContext.Features.Get(), responseFeature); + httpContext.RequestServices = serviceProvider; httpContext.Request.Protocol = protocol ?? GrpcProtocolConstants.Http2Protocol; httpContext.Request.ContentType = contentType ?? GrpcProtocolConstants.GrpcContentType; httpContext.Features.Set(new TestMinRequestBodyDataRateFeature()); diff --git a/test/Shared/HttpContextServerCallContextHelpers.cs b/test/Shared/HttpContextServerCallContextHelpers.cs index d331d6924..6f6f8379c 100644 --- a/test/Shared/HttpContextServerCallContextHelpers.cs +++ b/test/Shared/HttpContextServerCallContextHelpers.cs @@ -20,7 +20,9 @@ using System.IO.Compression; using Grpc.AspNetCore.Server; using Grpc.AspNetCore.Server.Internal; +using Grpc.AspNetCore.Server.Model; using Grpc.Net.Compression; +using Grpc.Shared.Server; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -31,7 +33,7 @@ internal static class HttpContextServerCallContextHelper { public static HttpContextServerCallContext CreateServerCallContext( HttpContext? httpContext = null, - Dictionary? compressionProviders = null, + List? compressionProviders = null, string? responseCompressionAlgorithm = null, CompressionLevel? responseCompressionLevel = null, int? maxSendMessageSize = null, @@ -39,7 +41,7 @@ public static HttpContextServerCallContext CreateServerCallContext( ILogger? logger = null, bool initialize = true) { - var methodContext = CreateMethodContext( + var options = CreateMethodOptions( compressionProviders, responseCompressionAlgorithm, responseCompressionLevel, @@ -48,7 +50,9 @@ public static HttpContextServerCallContext CreateServerCallContext( var context = new HttpContextServerCallContext( httpContext ?? new DefaultHttpContext(), - methodContext, + options, + typeof(object), + typeof(object), logger ?? NullLogger.Instance); if (initialize) { @@ -58,26 +62,23 @@ public static HttpContextServerCallContext CreateServerCallContext( return context; } - public static MethodContext CreateMethodContext( - Dictionary? compressionProviders = null, + public static MethodOptions CreateMethodOptions( + List? compressionProviders = null, string? responseCompressionAlgorithm = null, CompressionLevel? responseCompressionLevel = null, int? maxSendMessageSize = null, int? maxReceiveMessageSize = null, InterceptorCollection? interceptors = null) { - return new MethodContext - ( - requestType: typeof(object), - responseType: typeof(object), - compressionProviders: compressionProviders ?? new Dictionary(), - interceptors: interceptors ?? new InterceptorCollection(), - maxSendMessageSize: maxSendMessageSize, - maxReceiveMessageSize: maxReceiveMessageSize, - enableDetailedErrors: null, - responseCompressionAlgorithm: responseCompressionAlgorithm, - responseCompressionLevel: responseCompressionLevel - ); + var serviceOptions = new GrpcServiceOptions(); + serviceOptions.CompressionProviders = compressionProviders ?? new List(); + serviceOptions.Interceptors.AddRange(interceptors ?? new InterceptorCollection()); + serviceOptions.MaxSendMessageSize = maxSendMessageSize; + serviceOptions.MaxReceiveMessageSize = maxReceiveMessageSize; + serviceOptions.ResponseCompressionAlgorithm = responseCompressionAlgorithm; + serviceOptions.ResponseCompressionLevel = responseCompressionLevel; + + return MethodOptions.Create(new[] { serviceOptions }); } } } diff --git a/test/Shared/MessageHelpers.cs b/test/Shared/MessageHelpers.cs index 3d2334b7b..9ac3a17f7 100644 --- a/test/Shared/MessageHelpers.cs +++ b/test/Shared/MessageHelpers.cs @@ -34,10 +34,17 @@ namespace Grpc.Tests.Shared { internal static class MessageHelpers { - public static readonly Marshaller HelloRequestMarshaller = Marshallers.Create(r => r.ToByteArray(), data => HelloRequest.Parser.ParseFrom(data)); - public static readonly Marshaller HelloReplyMarshaller = Marshallers.Create(r => r.ToByteArray(), data => HelloReply.Parser.ParseFrom(data)); + public static Marshaller GetMarshaller(MessageParser parser) where TMessage : IMessage => + Marshallers.Create(r => r.ToByteArray(), data => parser.ParseFrom(data)); - public static readonly Method ServiceMethod = new Method(MethodType.Unary, "ServiceName", "MethodName", HelloRequestMarshaller, HelloReplyMarshaller); + public static readonly Method ServiceMethod = CreateServiceMethod("MethodName", HelloRequest.Parser, HelloReply.Parser); + + public static Method CreateServiceMethod(string methodName, MessageParser requestParser, MessageParser responseParser) + where TRequest : IMessage + where TResponse : IMessage + { + return new Method(MethodType.Unary, "ServiceName", methodName, GetMarshaller(requestParser), GetMarshaller(responseParser)); + } private static readonly HttpContextServerCallContext TestServerCallContext = HttpContextServerCallContextHelper.CreateServerCallContext(); @@ -55,8 +62,6 @@ internal static class MessageHelpers new GzipCompressionProvider(CompressionLevel.Fastest) }; - var resolvedProviders = ResolveProviders(compressionProviders); - var pipeReader = PipeReader.Create(stream); var httpContext = new DefaultHttpContext(); @@ -64,7 +69,7 @@ internal static class MessageHelpers var serverCallContext = HttpContextServerCallContextHelper.CreateServerCallContext( httpContext: httpContext, - compressionProviders: resolvedProviders, + compressionProviders: compressionProviders, responseCompressionAlgorithm: compressionEncoding); var message = await pipeReader.ReadSingleMessageAsync(serverCallContext, Deserialize).AsTask().DefaultTimeout(); @@ -86,14 +91,12 @@ internal static class MessageHelpers new GzipCompressionProvider(CompressionLevel.Fastest) }; - var resolvedProviders = ResolveProviders(compressionProviders); - var httpContext = new DefaultHttpContext(); httpContext.Request.Headers[GrpcProtocolConstants.MessageEncodingHeader] = compressionEncoding; var serverCallContext = HttpContextServerCallContextHelper.CreateServerCallContext( httpContext: httpContext, - compressionProviders: resolvedProviders, + compressionProviders: compressionProviders, responseCompressionAlgorithm: compressionEncoding); var message = await pipeReader.ReadStreamMessageAsync(serverCallContext, Deserialize).AsTask().DefaultTimeout(); @@ -108,8 +111,6 @@ public static void WriteMessage(Stream stream, T message, string? compression new GzipCompressionProvider(CompressionLevel.Fastest) }; - var resolvedProviders = ResolveProviders(compressionProviders); - var pipeWriter = PipeWriter.Create(stream); var httpContext = new DefaultHttpContext(); @@ -117,7 +118,7 @@ public static void WriteMessage(Stream stream, T message, string? compression var serverCallContext = HttpContextServerCallContextHelper.CreateServerCallContext( httpContext: httpContext, - compressionProviders: resolvedProviders, + compressionProviders: compressionProviders, responseCompressionAlgorithm: compressionEncoding); serverCallContext.Initialize(); From 0b2dd50f6b383418a69fcf3f1cd28e2239d49080 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 11 Dec 2019 10:38:46 +1300 Subject: [PATCH 2/4] PR feedback --- src/Shared/Server/MethodOptions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Shared/Server/MethodOptions.cs b/src/Shared/Server/MethodOptions.cs index d689ea56d..c13075a62 100644 --- a/src/Shared/Server/MethodOptions.cs +++ b/src/Shared/Server/MethodOptions.cs @@ -102,9 +102,10 @@ private MethodOptions( /// /// Creates method options by merging together the settings the specificed collection. /// The should be ordered with items arranged in ascending order of precedence. + /// Interceptors on options will be executed in reverse order of precendence. /// /// A collection of instances, arranged in ascending order of precedence. - /// A new instanced with settings merged from specifid collection. + /// A new instanced with settings merged from specifid collection. public static MethodOptions Create(IEnumerable serviceOptions) { // This is required to get ensure that service methods without any explicit configuration From ee95c49eff6fd6f18ad5af81568a3de1dd771f9d Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 11 Dec 2019 10:40:37 +1300 Subject: [PATCH 3/4] PR feedback --- src/Shared/Server/MethodOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/Server/MethodOptions.cs b/src/Shared/Server/MethodOptions.cs index c13075a62..ebd668397 100644 --- a/src/Shared/Server/MethodOptions.cs +++ b/src/Shared/Server/MethodOptions.cs @@ -102,7 +102,7 @@ private MethodOptions( /// /// Creates method options by merging together the settings the specificed collection. /// The should be ordered with items arranged in ascending order of precedence. - /// Interceptors on options will be executed in reverse order of precendence. + /// When interceptors from multiple options are merged together they will be executed in reverse order of precendence. /// /// A collection of instances, arranged in ascending order of precedence. /// A new instanced with settings merged from specifid collection. From 7826af78c4358c1e690ced83dcc2363b8c8ed9f4 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 11 Dec 2019 10:46:35 +1300 Subject: [PATCH 4/4] Clean up --- src/Shared/Server/MethodOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/Server/MethodOptions.cs b/src/Shared/Server/MethodOptions.cs index ebd668397..5eabfd6cb 100644 --- a/src/Shared/Server/MethodOptions.cs +++ b/src/Shared/Server/MethodOptions.cs @@ -120,7 +120,7 @@ public static MethodOptions Create(IEnumerable serviceOption foreach (var options in serviceOptions.Reverse()) { - AddCompressionProviders(resolvedCompressionProviders, options._compressionProviders); + AddCompressionProviders(resolvedCompressionProviders, options.CompressionProviders); tempInterceptors.InsertRange(0, options.Interceptors); maxSendMessageSize ??= options.MaxSendMessageSize; maxReceiveMessageSize ??= options.MaxReceiveMessageSize;