From 89e9ce7c69b20a7f471325f7c279e14a9c8f1180 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Thu, 23 Mar 2023 18:07:20 +0300 Subject: [PATCH] Introduce IOperationMessageListener --- .../GraphQLHttpMiddleware.cs | 7 +++++-- .../IOperationMessageListener.cs | 12 ++++++++++++ .../WebSockets/GraphQLWs/SubscriptionServer.cs | 15 +++++++++++---- .../SubscriptionServer.cs | 15 +++++++++++---- ...phQL.Server.Transports.AspNetCore.approved.txt | 8 ++++++-- ...phQL.Server.Transports.AspNetCore.approved.txt | 8 ++++++-- 6 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 src/Transports.AspNetCore/IOperationMessageListener.cs diff --git a/src/Transports.AspNetCore/GraphQLHttpMiddleware.cs b/src/Transports.AspNetCore/GraphQLHttpMiddleware.cs index 584a3afc..2e8bce10 100644 --- a/src/Transports.AspNetCore/GraphQLHttpMiddleware.cs +++ b/src/Transports.AspNetCore/GraphQLHttpMiddleware.cs @@ -680,6 +680,7 @@ protected virtual IWebSocketConnection CreateWebSocketConnection(HttpContext htt protected virtual IOperationMessageProcessor CreateMessageProcessor(IWebSocketConnection webSocketConnection, string subProtocol) { var authService = webSocketConnection.HttpContext.RequestServices.GetService(); + var listeners = webSocketConnection.HttpContext.RequestServices.GetServices(); if (subProtocol == WebSockets.GraphQLWs.SubscriptionServer.SubProtocol) { @@ -691,7 +692,8 @@ protected virtual IOperationMessageProcessor CreateMessageProcessor(IWebSocketCo _serializer, _serviceScopeFactory, this, - authService); + authService, + listeners); } else if (subProtocol == WebSockets.SubscriptionsTransportWs.SubscriptionServer.SubProtocol) { @@ -703,7 +705,8 @@ protected virtual IOperationMessageProcessor CreateMessageProcessor(IWebSocketCo _serializer, _serviceScopeFactory, this, - authService); + authService, + listeners); } throw new ArgumentOutOfRangeException(nameof(subProtocol)); diff --git a/src/Transports.AspNetCore/IOperationMessageListener.cs b/src/Transports.AspNetCore/IOperationMessageListener.cs new file mode 100644 index 00000000..c55af86b --- /dev/null +++ b/src/Transports.AspNetCore/IOperationMessageListener.cs @@ -0,0 +1,12 @@ +namespace GraphQL.Server.Transports.AspNetCore; + +/// +/// Allows to hook up into . +/// +public interface IOperationMessageListener +{ + /// + /// This method is called at the very beginning of + /// + ValueTask ListenAsync(BaseSubscriptionServer subscriptionServer, OperationMessage message); +} diff --git a/src/Transports.AspNetCore/WebSockets/GraphQLWs/SubscriptionServer.cs b/src/Transports.AspNetCore/WebSockets/GraphQLWs/SubscriptionServer.cs index c0f6cb35..1fd5966f 100644 --- a/src/Transports.AspNetCore/WebSockets/GraphQLWs/SubscriptionServer.cs +++ b/src/Transports.AspNetCore/WebSockets/GraphQLWs/SubscriptionServer.cs @@ -4,6 +4,7 @@ namespace GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWs; public class SubscriptionServer : BaseSubscriptionServer { private readonly IWebSocketAuthenticationService? _authenticationService; + private readonly IOperationMessageListener[]? _listeners; /// /// The WebSocket sub-protocol used for this protocol. @@ -51,6 +52,7 @@ public class SubscriptionServer : BaseSubscriptionServer /// A to create service scopes for execution of GraphQL requests. /// The user context builder used during connection initialization. /// An optional service to authenticate connections. + /// An optional collection of message listeners. public SubscriptionServer( IWebSocketConnection connection, GraphQLWebSocketOptions options, @@ -59,7 +61,8 @@ public SubscriptionServer( IGraphQLSerializer serializer, IServiceScopeFactory serviceScopeFactory, IUserContextBuilder userContextBuilder, - IWebSocketAuthenticationService? authenticationService = null) + IWebSocketAuthenticationService? authenticationService = null, + IEnumerable? listeners = null) : base(connection, options, authorizationOptions) { DocumentExecuter = executer ?? throw new ArgumentNullException(nameof(executer)); @@ -67,11 +70,16 @@ public SubscriptionServer( UserContextBuilder = userContextBuilder ?? throw new ArgumentNullException(nameof(userContextBuilder)); Serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); _authenticationService = authenticationService; + _listeners = listeners?.ToArray(); } /// public override async Task OnMessageReceivedAsync(OperationMessage message) { + if (_listeners != null) + foreach (var listener in _listeners) + await listener.ListenAsync(this, message); + if (message.Type == MessageType.Ping) { await OnPingAsync(message); @@ -196,10 +204,9 @@ await Connection.SendMessageAsync(new OperationMessage /// protected override async Task ExecuteRequestAsync(OperationMessage message) { - var request = Serializer.ReadNode(message.Payload); #pragma warning disable CA2208 // Instantiate argument exceptions correctly - if (request == null) - throw new ArgumentNullException(nameof(message) + "." + nameof(OperationMessage.Payload)); + var request = Serializer.ReadNode(message.Payload) + ?? throw new ArgumentNullException(nameof(message) + "." + nameof(OperationMessage.Payload)); #pragma warning restore CA2208 // Instantiate argument exceptions correctly var scope = ServiceScopeFactory.CreateScope(); try diff --git a/src/Transports.AspNetCore/WebSockets/SubscriptionsTransportWs/SubscriptionServer.cs b/src/Transports.AspNetCore/WebSockets/SubscriptionsTransportWs/SubscriptionServer.cs index d2fd7bf3..e4d823cf 100644 --- a/src/Transports.AspNetCore/WebSockets/SubscriptionsTransportWs/SubscriptionServer.cs +++ b/src/Transports.AspNetCore/WebSockets/SubscriptionsTransportWs/SubscriptionServer.cs @@ -4,6 +4,7 @@ namespace GraphQL.Server.Transports.AspNetCore.WebSockets.SubscriptionsTransport public class SubscriptionServer : BaseSubscriptionServer { private readonly IWebSocketAuthenticationService? _authenticationService; + private readonly IOperationMessageListener[]? _listeners; /// /// The WebSocket sub-protocol used for this protocol. @@ -51,6 +52,7 @@ public class SubscriptionServer : BaseSubscriptionServer /// A to create service scopes for execution of GraphQL requests. /// The user context builder used during connection initialization. /// An optional service to authenticate connections. + /// An optional collection of message listeners. public SubscriptionServer( IWebSocketConnection connection, GraphQLWebSocketOptions options, @@ -59,7 +61,8 @@ public SubscriptionServer( IGraphQLSerializer serializer, IServiceScopeFactory serviceScopeFactory, IUserContextBuilder userContextBuilder, - IWebSocketAuthenticationService? authenticationService = null) + IWebSocketAuthenticationService? authenticationService = null, + IEnumerable? listeners = null) : base(connection, options, authorizationOptions) { DocumentExecuter = executer ?? throw new ArgumentNullException(nameof(executer)); @@ -67,11 +70,16 @@ public SubscriptionServer( UserContextBuilder = userContextBuilder ?? throw new ArgumentNullException(nameof(userContextBuilder)); Serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); _authenticationService = authenticationService; + _listeners = listeners?.ToArray(); } /// public override async Task OnMessageReceivedAsync(OperationMessage message) { + if (_listeners != null) + foreach (var listener in _listeners) + await listener.ListenAsync(this, message); + if (message.Type == MessageType.GQL_CONNECTION_TERMINATE) { await OnCloseConnectionAsync(); @@ -174,10 +182,9 @@ await Connection.SendMessageAsync(new OperationMessage /// protected override async Task ExecuteRequestAsync(OperationMessage message) { - var request = Serializer.ReadNode(message.Payload); #pragma warning disable CA2208 // Instantiate argument exceptions correctly - if (request == null) - throw new ArgumentNullException(nameof(message) + "." + nameof(OperationMessage.Payload)); + var request = Serializer.ReadNode(message.Payload) + ?? throw new ArgumentNullException(nameof(message) + "." + nameof(OperationMessage.Payload)); #pragma warning restore CA2208 // Instantiate argument exceptions correctly var scope = ServiceScopeFactory.CreateScope(); try diff --git a/tests/ApiApprovalTests/net50+net60+netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/net50+net60+netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt index 224b7b8e..66a05b1f 100644 --- a/tests/ApiApprovalTests/net50+net60+netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/net50+net60+netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -147,6 +147,10 @@ namespace GraphQL.Server.Transports.AspNetCore string? AuthorizedPolicy { get; } System.Collections.Generic.IEnumerable AuthorizedRoles { get; } } + public interface IOperationMessageListener + { + System.Threading.Tasks.ValueTask ListenAsync(GraphQL.Server.Transports.AspNetCore.WebSockets.BaseSubscriptionServer subscriptionServer, GraphQL.Transport.OperationMessage message); + } public interface IUserContextBuilder { System.Threading.Tasks.ValueTask?> BuildUserContextAsync(Microsoft.AspNetCore.Http.HttpContext context, object? payload); @@ -312,7 +316,7 @@ namespace GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWs } public class SubscriptionServer : GraphQL.Server.Transports.AspNetCore.WebSockets.BaseSubscriptionServer { - public SubscriptionServer(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection connection, GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions options, GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions authorizationOptions, GraphQL.IDocumentExecuter executer, GraphQL.IGraphQLSerializer serializer, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.IUserContextBuilder userContextBuilder, GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketAuthenticationService? authenticationService = null) { } + public SubscriptionServer(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection connection, GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions options, GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions authorizationOptions, GraphQL.IDocumentExecuter executer, GraphQL.IGraphQLSerializer serializer, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.IUserContextBuilder userContextBuilder, GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketAuthenticationService? authenticationService = null, System.Collections.Generic.IEnumerable? listeners = null) { } protected GraphQL.IDocumentExecuter DocumentExecuter { get; } protected GraphQL.IGraphQLSerializer Serializer { get; } protected Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ServiceScopeFactory { get; } @@ -350,7 +354,7 @@ namespace GraphQL.Server.Transports.AspNetCore.WebSockets.SubscriptionsTransport } public class SubscriptionServer : GraphQL.Server.Transports.AspNetCore.WebSockets.BaseSubscriptionServer { - public SubscriptionServer(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection connection, GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions options, GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions authorizationOptions, GraphQL.IDocumentExecuter executer, GraphQL.IGraphQLSerializer serializer, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.IUserContextBuilder userContextBuilder, GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketAuthenticationService? authenticationService = null) { } + public SubscriptionServer(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection connection, GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions options, GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions authorizationOptions, GraphQL.IDocumentExecuter executer, GraphQL.IGraphQLSerializer serializer, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.IUserContextBuilder userContextBuilder, GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketAuthenticationService? authenticationService = null, System.Collections.Generic.IEnumerable? listeners = null) { } protected GraphQL.IDocumentExecuter DocumentExecuter { get; } protected GraphQL.IGraphQLSerializer Serializer { get; } protected Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ServiceScopeFactory { get; } diff --git a/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt index d1f317d7..2c69fa0d 100644 --- a/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -165,6 +165,10 @@ namespace GraphQL.Server.Transports.AspNetCore { System.Threading.CancellationToken ApplicationStopping { get; } } + public interface IOperationMessageListener + { + System.Threading.Tasks.ValueTask ListenAsync(GraphQL.Server.Transports.AspNetCore.WebSockets.BaseSubscriptionServer subscriptionServer, GraphQL.Transport.OperationMessage message); + } public interface IUserContextBuilder { System.Threading.Tasks.ValueTask?> BuildUserContextAsync(Microsoft.AspNetCore.Http.HttpContext context, object? payload); @@ -330,7 +334,7 @@ namespace GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWs } public class SubscriptionServer : GraphQL.Server.Transports.AspNetCore.WebSockets.BaseSubscriptionServer { - public SubscriptionServer(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection connection, GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions options, GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions authorizationOptions, GraphQL.IDocumentExecuter executer, GraphQL.IGraphQLSerializer serializer, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.IUserContextBuilder userContextBuilder, GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketAuthenticationService? authenticationService = null) { } + public SubscriptionServer(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection connection, GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions options, GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions authorizationOptions, GraphQL.IDocumentExecuter executer, GraphQL.IGraphQLSerializer serializer, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.IUserContextBuilder userContextBuilder, GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketAuthenticationService? authenticationService = null, System.Collections.Generic.IEnumerable? listeners = null) { } protected GraphQL.IDocumentExecuter DocumentExecuter { get; } protected GraphQL.IGraphQLSerializer Serializer { get; } protected Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ServiceScopeFactory { get; } @@ -368,7 +372,7 @@ namespace GraphQL.Server.Transports.AspNetCore.WebSockets.SubscriptionsTransport } public class SubscriptionServer : GraphQL.Server.Transports.AspNetCore.WebSockets.BaseSubscriptionServer { - public SubscriptionServer(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection connection, GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions options, GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions authorizationOptions, GraphQL.IDocumentExecuter executer, GraphQL.IGraphQLSerializer serializer, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.IUserContextBuilder userContextBuilder, GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketAuthenticationService? authenticationService = null) { } + public SubscriptionServer(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection connection, GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions options, GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions authorizationOptions, GraphQL.IDocumentExecuter executer, GraphQL.IGraphQLSerializer serializer, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.IUserContextBuilder userContextBuilder, GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketAuthenticationService? authenticationService = null, System.Collections.Generic.IEnumerable? listeners = null) { } protected GraphQL.IDocumentExecuter DocumentExecuter { get; } protected GraphQL.IGraphQLSerializer Serializer { get; } protected Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ServiceScopeFactory { get; }