parent
32baa655b9
commit
6a2d41cc9f
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.SignalR.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
|
|
@ -41,7 +42,7 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
|
||||||
_returnType = returnType;
|
_returnType = returnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type[] GetParameterTypes(string methodName)
|
public IReadOnlyList<Type> GetParameterTypes(string methodName)
|
||||||
{
|
{
|
||||||
if (_paramTypes != null)
|
if (_paramTypes != null)
|
||||||
{
|
{
|
||||||
|
|
@ -59,4 +60,4 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
|
||||||
throw new InvalidOperationException("Unexpected binder call");
|
throw new InvalidOperationException("Unexpected binder call");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1251,6 +1251,12 @@
|
||||||
"tweetnacl": "0.14.5"
|
"tweetnacl": "0.14.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "0.10.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||||
|
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||||
|
|
@ -1261,12 +1267,6 @@
|
||||||
"strip-ansi": "4.0.0"
|
"strip-ansi": "4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
|
||||||
"version": "0.10.31",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
|
||||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"stringstream": {
|
"stringstream": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"windowsAuthentication": false,
|
"windowsAuthentication": false,
|
||||||
"anonymousAuthentication": true,
|
"anonymousAuthentication": true,
|
||||||
"iisExpress": {
|
"iisExpress": {
|
||||||
"applicationUrl": "http://localhost:57780/",
|
"applicationUrl": "http://localhost:8719/",
|
||||||
"sslPort": 0
|
"sslPort": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"windowsAuthentication": false,
|
"windowsAuthentication": false,
|
||||||
"anonymousAuthentication": true,
|
"anonymousAuthentication": true,
|
||||||
"iisExpress": {
|
"iisExpress": {
|
||||||
"applicationUrl": "http://localhost:59847/",
|
"applicationUrl": "http://localhost:8718/",
|
||||||
"sslPort": 0
|
"sslPort": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -575,7 +575,7 @@ namespace Microsoft.AspNetCore.SignalR.Client
|
||||||
return irq.ResultType;
|
return irq.ResultType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type[] GetParameterTypes(string methodName)
|
public IReadOnlyList<Type> GetParameterTypes(string methodName)
|
||||||
{
|
{
|
||||||
if (!_connection._handlers.TryGetValue(methodName, out var handlers))
|
if (!_connection._handlers.TryGetValue(methodName, out var handlers))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Internal
|
namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
{
|
{
|
||||||
public interface IInvocationBinder
|
public interface IInvocationBinder
|
||||||
{
|
{
|
||||||
Type GetReturnType(string invocationId);
|
Type GetReturnType(string invocationId);
|
||||||
Type[] GetParameterTypes(string methodName);
|
IReadOnlyList<Type> GetParameterTypes(string methodName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -350,17 +350,17 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
private object[] BindArguments(JArray args, Type[] paramTypes)
|
private object[] BindArguments(JArray args, IReadOnlyList<Type> paramTypes)
|
||||||
{
|
{
|
||||||
var arguments = new object[args.Count];
|
var arguments = new object[args.Count];
|
||||||
if (paramTypes.Length != arguments.Length)
|
if (paramTypes.Count != arguments.Length)
|
||||||
{
|
{
|
||||||
throw new InvalidDataException($"Invocation provides {arguments.Length} argument(s) but target expects {paramTypes.Length}.");
|
throw new InvalidDataException($"Invocation provides {arguments.Length} argument(s) but target expects {paramTypes.Count}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (var i = 0; i < paramTypes.Length; i++)
|
for (var i = 0; i < paramTypes.Count; i++)
|
||||||
{
|
{
|
||||||
var paramType = paramTypes[i];
|
var paramType = paramTypes[i];
|
||||||
arguments[i] = args[i].ToObject(paramType, PayloadSerializer);
|
arguments[i] = args[i].ToObject(paramType, PayloadSerializer);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -156,7 +155,7 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
|
|
||||||
ProtocolReaderWriter = new HubProtocolReaderWriter(protocol, dataEncoder);
|
ProtocolReaderWriter = new HubProtocolReaderWriter(protocol, dataEncoder);
|
||||||
|
|
||||||
_logger.UsingHubProtocol(protocol.Name);
|
Log.UsingHubProtocol(_logger, protocol.Name);
|
||||||
|
|
||||||
UserIdentifier = userIdProvider.GetUserId(this);
|
UserIdentifier = userIdProvider.GetUserId(this);
|
||||||
|
|
||||||
|
|
@ -177,7 +176,7 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
_logger.NegotiateCanceled();
|
Log.NegotiateCanceled(_logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -211,7 +210,7 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
// adding a Ping message when the transport is full is unnecessary since the
|
// adding a Ping message when the transport is full is unnecessary since the
|
||||||
// transport is still in the process of sending frames.
|
// transport is still in the process of sending frames.
|
||||||
|
|
||||||
_logger.SentPing();
|
Log.SentPing(_logger);
|
||||||
|
|
||||||
_ = WriteAsync(PingMessage.Instance);
|
_ = WriteAsync(PingMessage.Instance);
|
||||||
|
|
||||||
|
|
@ -236,5 +235,42 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
connection._abortCompletedTcs.TrySetException(ex);
|
connection._abortCompletedTcs.TrySetException(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class Log
|
||||||
|
{
|
||||||
|
// Category: HubConnectionContext
|
||||||
|
private static readonly Action<ILogger, string, Exception> _usingHubProtocol =
|
||||||
|
LoggerMessage.Define<string>(LogLevel.Information, new EventId(1, "UsingHubProtocol"), "Using HubProtocol '{protocol}'.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, Exception> _negotiateCanceled =
|
||||||
|
LoggerMessage.Define(LogLevel.Debug, new EventId(2, "NegotiateCanceled"), "Negotiate was canceled.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, Exception> _sentPing =
|
||||||
|
LoggerMessage.Define(LogLevel.Trace, new EventId(3, "SentPing"), "Sent a ping message to the client.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, Exception> _transportBufferFull =
|
||||||
|
LoggerMessage.Define(LogLevel.Debug, new EventId(4, "TransportBufferFull"), "Unable to send Ping message to client, the transport buffer is full.");
|
||||||
|
|
||||||
|
public static void UsingHubProtocol(ILogger logger, string hubProtocol)
|
||||||
|
{
|
||||||
|
_usingHubProtocol(logger, hubProtocol, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void NegotiateCanceled(ILogger logger)
|
||||||
|
{
|
||||||
|
_negotiateCanceled(logger, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SentPing(ILogger logger)
|
||||||
|
{
|
||||||
|
_sentPing(logger, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TransportBufferFull(ILogger logger)
|
||||||
|
{
|
||||||
|
_transportBufferFull(logger, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,57 +2,39 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Channels;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Protocols;
|
using Microsoft.AspNetCore.Protocols;
|
||||||
using Microsoft.AspNetCore.SignalR.Core;
|
using Microsoft.AspNetCore.SignalR.Core;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.SignalR.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Internal;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR
|
namespace Microsoft.AspNetCore.SignalR
|
||||||
{
|
{
|
||||||
public class HubEndPoint<THub> : IInvocationBinder where THub : Hub
|
public class HubEndPoint<THub> where THub : Hub
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, HubMethodDescriptor> _methods = new Dictionary<string, HubMethodDescriptor>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
private readonly HubLifetimeManager<THub> _lifetimeManager;
|
private readonly HubLifetimeManager<THub> _lifetimeManager;
|
||||||
private readonly IHubContext<THub> _hubContext;
|
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
private readonly ILogger<HubEndPoint<THub>> _logger;
|
private readonly ILogger<HubEndPoint<THub>> _logger;
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
|
||||||
private readonly IHubProtocolResolver _protocolResolver;
|
private readonly IHubProtocolResolver _protocolResolver;
|
||||||
private readonly HubOptions _hubOptions;
|
private readonly HubOptions _hubOptions;
|
||||||
private readonly IUserIdProvider _userIdProvider;
|
private readonly IUserIdProvider _userIdProvider;
|
||||||
|
private readonly HubDispatcher<THub> _dispatcher;
|
||||||
|
|
||||||
public HubEndPoint(HubLifetimeManager<THub> lifetimeManager,
|
public HubEndPoint(HubLifetimeManager<THub> lifetimeManager,
|
||||||
IHubProtocolResolver protocolResolver,
|
IHubProtocolResolver protocolResolver,
|
||||||
IHubContext<THub> hubContext,
|
|
||||||
IOptions<HubOptions> hubOptions,
|
IOptions<HubOptions> hubOptions,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IServiceScopeFactory serviceScopeFactory,
|
IUserIdProvider userIdProvider,
|
||||||
IUserIdProvider userIdProvider)
|
HubDispatcher<THub> dispatcher)
|
||||||
{
|
{
|
||||||
_protocolResolver = protocolResolver;
|
_protocolResolver = protocolResolver;
|
||||||
_lifetimeManager = lifetimeManager;
|
_lifetimeManager = lifetimeManager;
|
||||||
_hubContext = hubContext;
|
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
_hubOptions = hubOptions.Value;
|
_hubOptions = hubOptions.Value;
|
||||||
_logger = loggerFactory.CreateLogger<HubEndPoint<THub>>();
|
_logger = loggerFactory.CreateLogger<HubEndPoint<THub>>();
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
|
||||||
_userIdProvider = userIdProvider;
|
_userIdProvider = userIdProvider;
|
||||||
|
_dispatcher = dispatcher;
|
||||||
DiscoverHubMethods();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnConnectedAsync(ConnectionContext connection)
|
public async Task OnConnectedAsync(ConnectionContext connection)
|
||||||
|
|
@ -78,7 +60,15 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
|
|
||||||
private async Task RunHubAsync(HubConnectionContext connection)
|
private async Task RunHubAsync(HubConnectionContext connection)
|
||||||
{
|
{
|
||||||
await HubOnConnectedAsync(connection);
|
try
|
||||||
|
{
|
||||||
|
await _dispatcher.OnConnectedAsync(connection);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.ErrorDispatchingHubEvent(_logger, "OnConnectedAsync", ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -86,7 +76,7 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.ErrorProcessingRequest(ex);
|
Log.ErrorProcessingRequest(_logger, ex);
|
||||||
await HubOnDisconnectedAsync(connection, ex);
|
await HubOnDisconnectedAsync(connection, ex);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
@ -94,67 +84,28 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
await HubOnDisconnectedAsync(connection, null);
|
await HubOnDisconnectedAsync(connection, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HubOnConnectedAsync(HubConnectionContext connection)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
|
||||||
{
|
|
||||||
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>();
|
|
||||||
var hub = hubActivator.Create();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
InitializeHub(hub, connection);
|
|
||||||
await hub.OnConnectedAsync();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
hubActivator.Release(hub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorInvokingHubMethod("OnConnectedAsync", ex);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HubOnDisconnectedAsync(HubConnectionContext connection, Exception exception)
|
private async Task HubOnDisconnectedAsync(HubConnectionContext connection, Exception exception)
|
||||||
{
|
{
|
||||||
|
// We wait on abort to complete, this is so that we can guarantee that all callbacks have fired
|
||||||
|
// before OnDisconnectedAsync
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// We wait on abort to complete, this is so that we can guarantee that all callbacks have fired
|
// Ensure the connection is aborted before firing disconnect
|
||||||
// before OnDisconnectedAsync
|
await connection.AbortAsync();
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Ensure the connection is aborted before firing disconnect
|
|
||||||
await connection.AbortAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.AbortFailed(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
|
||||||
{
|
|
||||||
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>();
|
|
||||||
var hub = hubActivator.Create();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
InitializeHub(hub, connection);
|
|
||||||
await hub.OnDisconnectedAsync(exception);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
hubActivator.Release(hub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.ErrorInvokingHubMethod("OnDisconnectedAsync", ex);
|
Log.AbortFailed(_logger, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _dispatcher.OnDisconnectedAsync(connection, exception);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.ErrorDispatchingHubEvent(_logger, "OnDisconnectedAsync", ex);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -177,52 +128,13 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
{
|
{
|
||||||
if (!buffer.IsEmpty)
|
if (!buffer.IsEmpty)
|
||||||
{
|
{
|
||||||
if (connection.ProtocolReaderWriter.ReadMessages(buffer, this, out var hubMessages, out consumed, out examined))
|
if (connection.ProtocolReaderWriter.ReadMessages(buffer, _dispatcher, out var hubMessages, out consumed, out examined))
|
||||||
{
|
{
|
||||||
foreach (var hubMessage in hubMessages)
|
foreach (var hubMessage in hubMessages)
|
||||||
{
|
{
|
||||||
switch (hubMessage)
|
// Don't wait on the result of execution, continue processing other
|
||||||
{
|
// incoming messages on this connection.
|
||||||
case InvocationMessage invocationMessage:
|
_ = _dispatcher.DispatchMessageAsync(connection, hubMessage);
|
||||||
_logger.ReceivedHubInvocation(invocationMessage);
|
|
||||||
|
|
||||||
// Don't wait on the result of execution, continue processing other
|
|
||||||
// incoming messages on this connection.
|
|
||||||
_ = ProcessInvocation(connection, invocationMessage, isStreamedInvocation: false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case StreamInvocationMessage streamInvocationMessage:
|
|
||||||
_logger.ReceivedStreamHubInvocation(streamInvocationMessage);
|
|
||||||
|
|
||||||
// Don't wait on the result of execution, continue processing other
|
|
||||||
// incoming messages on this connection.
|
|
||||||
_ = ProcessInvocation(connection, streamInvocationMessage, isStreamedInvocation: true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CancelInvocationMessage cancelInvocationMessage:
|
|
||||||
// Check if there is an associated active stream and cancel it if it exists.
|
|
||||||
// The cts will be removed when the streaming method completes executing
|
|
||||||
if (connection.ActiveRequestCancellationSources.TryGetValue(cancelInvocationMessage.InvocationId, out var cts))
|
|
||||||
{
|
|
||||||
_logger.CancelStream(cancelInvocationMessage.InvocationId);
|
|
||||||
cts.Cancel();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Stream can be canceled on the server while client is canceling stream.
|
|
||||||
_logger.UnexpectedCancel();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PingMessage _:
|
|
||||||
// We don't care about pings
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Other kind of message we weren't expecting
|
|
||||||
default:
|
|
||||||
_logger.UnsupportedMessageReceived(hubMessage.GetType().FullName);
|
|
||||||
throw new NotSupportedException($"Received unsupported message: {hubMessage}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -244,345 +156,31 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessInvocation(HubConnectionContext connection,
|
private static class Log
|
||||||
HubMethodInvocationMessage hubMethodInvocationMessage, bool isStreamedInvocation)
|
|
||||||
{
|
{
|
||||||
try
|
private static readonly Action<ILogger, string, Exception> _errorDispatchingHubEvent =
|
||||||
|
LoggerMessage.Define<string>(LogLevel.Error, new EventId(1, "ErrorDispatchingHubEvent"), "Error when dispatching '{hubMethod}' on hub.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, Exception> _errorProcessingRequest =
|
||||||
|
LoggerMessage.Define(LogLevel.Error, new EventId(2, "ErrorProcessingRequest"), "Error when processing requests.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, Exception> _abortFailed =
|
||||||
|
LoggerMessage.Define(LogLevel.Trace, new EventId(3, "AbortFailed"), "Abort callback failed.");
|
||||||
|
|
||||||
|
public static void ErrorDispatchingHubEvent(ILogger logger, string hubMethod, Exception exception)
|
||||||
{
|
{
|
||||||
// If an unexpected exception occurs then we want to kill the entire connection
|
_errorDispatchingHubEvent(logger, hubMethod, exception);
|
||||||
// by ending the processing loop
|
|
||||||
if (!_methods.TryGetValue(hubMethodInvocationMessage.Target, out var descriptor))
|
|
||||||
{
|
|
||||||
// Send an error to the client. Then let the normal completion process occur
|
|
||||||
_logger.UnknownHubMethod(hubMethodInvocationMessage.Target);
|
|
||||||
await SendMessageAsync(connection, CompletionMessage.WithError(
|
|
||||||
hubMethodInvocationMessage.InvocationId, $"Unknown hub method '{hubMethodInvocationMessage.Target}'"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await Invoke(descriptor, connection, hubMethodInvocationMessage, isStreamedInvocation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Abort the entire connection if the invocation fails in an unexpected way
|
|
||||||
connection.Abort(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task SendMessageAsync(HubConnectionContext connection, HubMessage hubMessage)
|
|
||||||
{
|
|
||||||
return connection.WriteAsync(hubMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Invoke(HubMethodDescriptor descriptor, HubConnectionContext connection,
|
|
||||||
HubMethodInvocationMessage hubMethodInvocationMessage, bool isStreamedInvocation)
|
|
||||||
{
|
|
||||||
var methodExecutor = descriptor.MethodExecutor;
|
|
||||||
|
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
|
||||||
{
|
|
||||||
if (!await IsHubMethodAuthorized(scope.ServiceProvider, connection.User, descriptor.Policies))
|
|
||||||
{
|
|
||||||
_logger.HubMethodNotAuthorized(hubMethodInvocationMessage.Target);
|
|
||||||
await SendInvocationError(hubMethodInvocationMessage, connection,
|
|
||||||
$"Failed to invoke '{hubMethodInvocationMessage.Target}' because user is unauthorized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!await ValidateInvocationMode(methodExecutor.MethodReturnType, isStreamedInvocation, hubMethodInvocationMessage, connection))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>();
|
|
||||||
var hub = hubActivator.Create();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
InitializeHub(hub, connection);
|
|
||||||
|
|
||||||
var result = await ExecuteHubMethod(methodExecutor, hub, hubMethodInvocationMessage.Arguments);
|
|
||||||
|
|
||||||
if (isStreamedInvocation)
|
|
||||||
{
|
|
||||||
var enumerator = GetStreamingEnumerator(connection, hubMethodInvocationMessage.InvocationId, methodExecutor, result, methodExecutor.MethodReturnType);
|
|
||||||
_logger.StreamingResult(hubMethodInvocationMessage.InvocationId, methodExecutor);
|
|
||||||
await StreamResultsAsync(hubMethodInvocationMessage.InvocationId, connection, enumerator);
|
|
||||||
}
|
|
||||||
// Non-empty/null InvocationId ==> Blocking invocation that needs a response
|
|
||||||
else if (!string.IsNullOrEmpty(hubMethodInvocationMessage.InvocationId))
|
|
||||||
{
|
|
||||||
_logger.SendingResult(hubMethodInvocationMessage.InvocationId, methodExecutor);
|
|
||||||
await SendMessageAsync(connection, CompletionMessage.WithResult(hubMethodInvocationMessage.InvocationId, result));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (TargetInvocationException ex)
|
|
||||||
{
|
|
||||||
_logger.FailedInvokingHubMethod(hubMethodInvocationMessage.Target, ex);
|
|
||||||
await SendInvocationError(hubMethodInvocationMessage, connection, ex.InnerException.Message);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.FailedInvokingHubMethod(hubMethodInvocationMessage.Target, ex);
|
|
||||||
await SendInvocationError(hubMethodInvocationMessage, connection, ex.Message);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
hubActivator.Release(hub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<object> ExecuteHubMethod(ObjectMethodExecutor methodExecutor, THub hub, object[] arguments)
|
|
||||||
{
|
|
||||||
// ReadableChannel is awaitable but we don't want to await it.
|
|
||||||
if (methodExecutor.IsMethodAsync && !IsChannel(methodExecutor.MethodReturnType, out _))
|
|
||||||
{
|
|
||||||
if (methodExecutor.MethodReturnType == typeof(Task))
|
|
||||||
{
|
|
||||||
await (Task)methodExecutor.Execute(hub, arguments);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return await methodExecutor.ExecuteAsync(hub, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return methodExecutor.Execute(hub, arguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
public static void ErrorProcessingRequest(ILogger logger, Exception exception)
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SendInvocationError(HubMethodInvocationMessage hubMethodInvocationMessage,
|
|
||||||
HubConnectionContext connection, string errorMessage)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(hubMethodInvocationMessage.InvocationId))
|
|
||||||
{
|
{
|
||||||
return;
|
_errorProcessingRequest(logger, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendMessageAsync(connection, CompletionMessage.WithError(hubMethodInvocationMessage.InvocationId, errorMessage));
|
public static void AbortFailed(ILogger logger, Exception exception)
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeHub(THub hub, HubConnectionContext connection)
|
|
||||||
{
|
|
||||||
hub.Clients = new HubCallerClients(_hubContext.Clients, connection.ConnectionId);
|
|
||||||
hub.Context = new HubCallerContext(connection);
|
|
||||||
hub.Groups = _hubContext.Groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsChannel(Type type, out Type payloadType)
|
|
||||||
{
|
|
||||||
var channelType = type.AllBaseTypes().FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ChannelReader<>));
|
|
||||||
if (channelType == null)
|
|
||||||
{
|
{
|
||||||
payloadType = null;
|
_abortFailed(logger, exception);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
payloadType = channelType.GetGenericArguments()[0];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StreamResultsAsync(string invocationId, HubConnectionContext connection, IAsyncEnumerator<object> enumerator)
|
|
||||||
{
|
|
||||||
string error = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (await enumerator.MoveNextAsync())
|
|
||||||
{
|
|
||||||
// Send the stream item
|
|
||||||
await SendMessageAsync(connection, new StreamItemMessage(invocationId, enumerator.Current));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ChannelClosedException ex)
|
|
||||||
{
|
|
||||||
// If the channel closes from an exception in the streaming method, grab the innerException for the error from the streaming method
|
|
||||||
error = ex.InnerException == null ? ex.Message : ex.InnerException.Message;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// If the streaming method was canceled we don't want to send a HubException message - this is not an error case
|
|
||||||
if (!(ex is OperationCanceledException && connection.ActiveRequestCancellationSources.TryGetValue(invocationId, out var cts)
|
|
||||||
&& cts.IsCancellationRequested))
|
|
||||||
{
|
|
||||||
error = ex.Message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await SendMessageAsync(connection, new CompletionMessage(invocationId, error: error, result: null, hasResult: false));
|
|
||||||
|
|
||||||
if (connection.ActiveRequestCancellationSources.TryRemove(invocationId, out var cts))
|
|
||||||
{
|
|
||||||
cts.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> ValidateInvocationMode(Type resultType, bool isStreamedInvocation,
|
|
||||||
HubMethodInvocationMessage hubMethodInvocationMessage, HubConnectionContext connection)
|
|
||||||
{
|
|
||||||
var isStreamedResult = IsStreamed(resultType);
|
|
||||||
if (isStreamedResult && !isStreamedInvocation)
|
|
||||||
{
|
|
||||||
// Non-null/empty InvocationId? Blocking
|
|
||||||
if (!string.IsNullOrEmpty(hubMethodInvocationMessage.InvocationId))
|
|
||||||
{
|
|
||||||
_logger.StreamingMethodCalledWithInvoke(hubMethodInvocationMessage);
|
|
||||||
await SendMessageAsync(connection, CompletionMessage.WithError(hubMethodInvocationMessage.InvocationId,
|
|
||||||
$"The client attempted to invoke the streaming '{hubMethodInvocationMessage.Target}' method in a non-streaming fashion."));
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isStreamedResult && isStreamedInvocation)
|
|
||||||
{
|
|
||||||
_logger.NonStreamingMethodCalledWithStream(hubMethodInvocationMessage);
|
|
||||||
await SendMessageAsync(connection, CompletionMessage.WithError(hubMethodInvocationMessage.InvocationId,
|
|
||||||
$"The client attempted to invoke the non-streaming '{hubMethodInvocationMessage.Target}' method in a streaming fashion."));
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsStreamed(Type resultType)
|
|
||||||
{
|
|
||||||
var observableInterface = IsIObservable(resultType) ?
|
|
||||||
resultType :
|
|
||||||
resultType.GetInterfaces().FirstOrDefault(IsIObservable);
|
|
||||||
|
|
||||||
if (observableInterface != null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsChannel(resultType, out _))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IAsyncEnumerator<object> GetStreamingEnumerator(HubConnectionContext connection, string invocationId, ObjectMethodExecutor methodExecutor, object result, Type resultType)
|
|
||||||
{
|
|
||||||
if (result != null)
|
|
||||||
{
|
|
||||||
var observableInterface = IsIObservable(resultType) ?
|
|
||||||
resultType :
|
|
||||||
resultType.GetInterfaces().FirstOrDefault(IsIObservable);
|
|
||||||
if (observableInterface != null)
|
|
||||||
{
|
|
||||||
return AsyncEnumeratorAdapters.FromObservable(result, observableInterface, CreateCancellation());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsChannel(resultType, out var payloadType))
|
|
||||||
{
|
|
||||||
return AsyncEnumeratorAdapters.FromChannel(result, payloadType, CreateCancellation());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.InvalidReturnValueFromStreamingMethod(methodExecutor.MethodInfo.Name);
|
|
||||||
throw new InvalidOperationException($"The value returned by the streaming method '{methodExecutor.MethodInfo.Name}' is null, does not implement the IObservable<> interface or is not a ReadableChannel<>.");
|
|
||||||
|
|
||||||
CancellationToken CreateCancellation()
|
|
||||||
{
|
|
||||||
var streamCts = new CancellationTokenSource();
|
|
||||||
connection.ActiveRequestCancellationSources.TryAdd(invocationId, streamCts);
|
|
||||||
return CancellationTokenSource.CreateLinkedTokenSource(connection.ConnectionAbortedToken, streamCts.Token).Token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsIObservable(Type iface)
|
|
||||||
{
|
|
||||||
return iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IObservable<>);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DiscoverHubMethods()
|
|
||||||
{
|
|
||||||
var hubType = typeof(THub);
|
|
||||||
var hubTypeInfo = hubType.GetTypeInfo();
|
|
||||||
var hubName = hubType.Name;
|
|
||||||
|
|
||||||
foreach (var methodInfo in HubReflectionHelper.GetHubMethods(hubType))
|
|
||||||
{
|
|
||||||
var methodName =
|
|
||||||
methodInfo.GetCustomAttribute<HubMethodNameAttribute>()?.Name ??
|
|
||||||
methodInfo.Name;
|
|
||||||
|
|
||||||
if (_methods.ContainsKey(methodName))
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"Duplicate definitions of '{methodName}'. Overloading is not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var executor = ObjectMethodExecutor.Create(methodInfo, hubTypeInfo);
|
|
||||||
var authorizeAttributes = methodInfo.GetCustomAttributes<AuthorizeAttribute>(inherit: true);
|
|
||||||
_methods[methodName] = new HubMethodDescriptor(executor, authorizeAttributes);
|
|
||||||
|
|
||||||
_logger.HubMethodBound(hubName, methodName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> IsHubMethodAuthorized(IServiceProvider provider, ClaimsPrincipal principal, IList<IAuthorizeData> policies)
|
|
||||||
{
|
|
||||||
// If there are no policies we don't need to run auth
|
|
||||||
if (!policies.Any())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var authService = provider.GetRequiredService<IAuthorizationService>();
|
|
||||||
var policyProvider = provider.GetRequiredService<IAuthorizationPolicyProvider>();
|
|
||||||
|
|
||||||
var authorizePolicy = await AuthorizationPolicy.CombineAsync(policyProvider, policies);
|
|
||||||
// AuthorizationPolicy.CombineAsync only returns null if there are no policies and we check that above
|
|
||||||
Debug.Assert(authorizePolicy != null);
|
|
||||||
|
|
||||||
var authorizationResult = await authService.AuthorizeAsync(principal, authorizePolicy);
|
|
||||||
// Only check authorization success, challenge or forbid wouldn't make sense from a hub method invocation
|
|
||||||
return authorizationResult.Succeeded;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type IInvocationBinder.GetReturnType(string invocationId)
|
|
||||||
{
|
|
||||||
return typeof(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
Type[] IInvocationBinder.GetParameterTypes(string methodName)
|
|
||||||
{
|
|
||||||
HubMethodDescriptor descriptor;
|
|
||||||
if (!_methods.TryGetValue(methodName, out descriptor))
|
|
||||||
{
|
|
||||||
return Type.EmptyTypes;
|
|
||||||
}
|
|
||||||
return descriptor.ParameterTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// REVIEW: We can decide to move this out of here if we want pluggable hub discovery
|
|
||||||
private class HubMethodDescriptor
|
|
||||||
{
|
|
||||||
public HubMethodDescriptor(ObjectMethodExecutor methodExecutor, IEnumerable<IAuthorizeData> policies)
|
|
||||||
{
|
|
||||||
MethodExecutor = methodExecutor;
|
|
||||||
ParameterTypes = methodExecutor.MethodParameters.Select(p => p.ParameterType).ToArray();
|
|
||||||
Policies = policies.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectMethodExecutor MethodExecutor { get; }
|
|
||||||
|
|
||||||
public Type[] ParameterTypes { get; }
|
|
||||||
|
|
||||||
public IList<IAuthorizeData> Policies { get; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
|
{
|
||||||
|
public partial class DefaultHubDispatcher<THub>
|
||||||
|
{
|
||||||
|
private static class Log
|
||||||
|
{
|
||||||
|
private static readonly Action<ILogger, InvocationMessage, Exception> _receivedHubInvocation =
|
||||||
|
LoggerMessage.Define<InvocationMessage>(LogLevel.Debug, new EventId(1, "ReceivedHubInvocation"), "Received hub invocation: {invocationMessage}.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, Exception> _unsupportedMessageReceived =
|
||||||
|
LoggerMessage.Define<string>(LogLevel.Error, new EventId(2, "UnsupportedMessageReceived"), "Received unsupported message of type '{messageType}'.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, Exception> _unknownHubMethod =
|
||||||
|
LoggerMessage.Define<string>(LogLevel.Error, new EventId(3, "UnknownHubMethod"), "Unknown hub method '{hubMethod}'.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, Exception> _outboundChannelClosed =
|
||||||
|
LoggerMessage.Define(LogLevel.Warning, new EventId(4, "OutboundChannelClosed"), "Outbound channel was closed while trying to write hub message.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, Exception> _hubMethodNotAuthorized =
|
||||||
|
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(5, "HubMethodNotAuthorized"), "Failed to invoke '{hubMethod}' because user is unauthorized.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, Exception> _streamingResult =
|
||||||
|
LoggerMessage.Define<string, string>(LogLevel.Trace, new EventId(6, "StreamingResult"), "InvocationId {invocationId}: Streaming result of type '{resultType}'.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, Exception> _sendingResult =
|
||||||
|
LoggerMessage.Define<string, string>(LogLevel.Trace, new EventId(7, "SendingResult"), "InvocationId {invocationId}: Sending result of type '{resultType}'.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, Exception> _failedInvokingHubMethod =
|
||||||
|
LoggerMessage.Define<string>(LogLevel.Error, new EventId(8, "FailedInvokingHubMethod"), "Failed to invoke hub method '{hubMethod}'.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, Exception> _hubMethodBound =
|
||||||
|
LoggerMessage.Define<string, string>(LogLevel.Trace, new EventId(9, "HubMethodBound"), "'{hubName}' hub method '{hubMethod}' is bound.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, Exception> _cancelStream =
|
||||||
|
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(10, "CancelStream"), "Canceling stream for invocation {invocationId}.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, Exception> _unexpectedCancel =
|
||||||
|
LoggerMessage.Define(LogLevel.Debug, new EventId(11, "UnexpectedCancel"), "CancelInvocationMessage received unexpectedly.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, StreamInvocationMessage, Exception> _receivedStreamHubInvocation =
|
||||||
|
LoggerMessage.Define<StreamInvocationMessage>(LogLevel.Debug, new EventId(12, "ReceivedStreamHubInvocation"), "Received stream hub invocation: {invocationMessage}.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, HubMethodInvocationMessage, Exception> _streamingMethodCalledWithInvoke =
|
||||||
|
LoggerMessage.Define<HubMethodInvocationMessage>(LogLevel.Error, new EventId(13, "StreamingMethodCalledWithInvoke"), "A streaming method was invoked in the non-streaming fashion : {invocationMessage}.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, HubMethodInvocationMessage, Exception> _nonStreamingMethodCalledWithStream =
|
||||||
|
LoggerMessage.Define<HubMethodInvocationMessage>(LogLevel.Error, new EventId(14, "NonStreamingMethodCalledWithStream"), "A non-streaming method was invoked in the streaming fashion : {invocationMessage}.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, Exception> _invalidReturnValueFromStreamingMethod =
|
||||||
|
LoggerMessage.Define<string>(LogLevel.Error, new EventId(15, "InvalidReturnValueFromStreamingMethod"), "A streaming method returned a value that cannot be used to build enumerator {hubMethod}.");
|
||||||
|
|
||||||
|
public static void ReceivedHubInvocation(ILogger logger, InvocationMessage invocationMessage)
|
||||||
|
{
|
||||||
|
_receivedHubInvocation(logger, invocationMessage, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UnsupportedMessageReceived(ILogger logger, string messageType)
|
||||||
|
{
|
||||||
|
_unsupportedMessageReceived(logger, messageType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UnknownHubMethod(ILogger logger, string hubMethod)
|
||||||
|
{
|
||||||
|
_unknownHubMethod(logger, hubMethod, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OutboundChannelClosed(ILogger logger)
|
||||||
|
{
|
||||||
|
_outboundChannelClosed(logger, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HubMethodNotAuthorized(ILogger logger, string hubMethod)
|
||||||
|
{
|
||||||
|
_hubMethodNotAuthorized(logger, hubMethod, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StreamingResult(ILogger logger, string invocationId, ObjectMethodExecutor objectMethodExecutor)
|
||||||
|
{
|
||||||
|
var resultType = objectMethodExecutor.AsyncResultType == null ? objectMethodExecutor.MethodReturnType : objectMethodExecutor.AsyncResultType;
|
||||||
|
_streamingResult(logger, invocationId, resultType.FullName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SendingResult(ILogger logger, string invocationId, ObjectMethodExecutor objectMethodExecutor)
|
||||||
|
{
|
||||||
|
var resultType = objectMethodExecutor.AsyncResultType == null ? objectMethodExecutor.MethodReturnType : objectMethodExecutor.AsyncResultType;
|
||||||
|
_sendingResult(logger, invocationId, resultType.FullName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FailedInvokingHubMethod(ILogger logger, string hubMethod, Exception exception)
|
||||||
|
{
|
||||||
|
_failedInvokingHubMethod(logger, hubMethod, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HubMethodBound(ILogger logger, string hubName, string hubMethod)
|
||||||
|
{
|
||||||
|
_hubMethodBound(logger, hubName, hubMethod, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CancelStream(ILogger logger, string invocationId)
|
||||||
|
{
|
||||||
|
_cancelStream(logger, invocationId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UnexpectedCancel(ILogger logger)
|
||||||
|
{
|
||||||
|
_unexpectedCancel(logger, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReceivedStreamHubInvocation(ILogger logger, StreamInvocationMessage invocationMessage)
|
||||||
|
{
|
||||||
|
_receivedStreamHubInvocation(logger, invocationMessage, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StreamingMethodCalledWithInvoke(ILogger logger, HubMethodInvocationMessage invocationMessage)
|
||||||
|
{
|
||||||
|
_streamingMethodCalledWithInvoke(logger, invocationMessage, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void NonStreamingMethodCalledWithStream(ILogger logger, HubMethodInvocationMessage invocationMessage)
|
||||||
|
{
|
||||||
|
_nonStreamingMethodCalledWithStream(logger, invocationMessage, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void InvalidReturnValueFromStreamingMethod(ILogger logger, string hubMethod)
|
||||||
|
{
|
||||||
|
_invalidReturnValueFromStreamingMethod(logger, hubMethod, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,448 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
|
{
|
||||||
|
public partial class DefaultHubDispatcher<THub> : HubDispatcher<THub> where THub : Hub
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, HubMethodDescriptor> _methods = new Dictionary<string, HubMethodDescriptor>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
private readonly IHubContext<THub> _hubContext;
|
||||||
|
private readonly ILogger<HubDispatcher<THub>> _logger;
|
||||||
|
|
||||||
|
public DefaultHubDispatcher(IServiceScopeFactory serviceScopeFactory, IHubContext<THub> hubContext, ILogger<DefaultHubDispatcher<THub>> logger)
|
||||||
|
{
|
||||||
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
|
_hubContext = hubContext;
|
||||||
|
_logger = logger;
|
||||||
|
DiscoverHubMethods();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnConnectedAsync(HubConnectionContext connection)
|
||||||
|
{
|
||||||
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
|
{
|
||||||
|
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>();
|
||||||
|
var hub = hubActivator.Create();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InitializeHub(hub, connection);
|
||||||
|
await hub.OnConnectedAsync();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
hubActivator.Release(hub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnDisconnectedAsync(HubConnectionContext connection, Exception exception)
|
||||||
|
{
|
||||||
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
|
{
|
||||||
|
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>();
|
||||||
|
var hub = hubActivator.Create();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InitializeHub(hub, connection);
|
||||||
|
await hub.OnDisconnectedAsync(exception);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
hubActivator.Release(hub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task DispatchMessageAsync(HubConnectionContext connection, HubMessage hubMessage)
|
||||||
|
{
|
||||||
|
switch (hubMessage)
|
||||||
|
{
|
||||||
|
case InvocationMessage invocationMessage:
|
||||||
|
Log.ReceivedHubInvocation(_logger, invocationMessage);
|
||||||
|
await ProcessInvocation(connection, invocationMessage, isStreamedInvocation: false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StreamInvocationMessage streamInvocationMessage:
|
||||||
|
Log.ReceivedStreamHubInvocation(_logger, streamInvocationMessage);
|
||||||
|
await ProcessInvocation(connection, streamInvocationMessage, isStreamedInvocation: true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CancelInvocationMessage cancelInvocationMessage:
|
||||||
|
// Check if there is an associated active stream and cancel it if it exists.
|
||||||
|
// The cts will be removed when the streaming method completes executing
|
||||||
|
if (connection.ActiveRequestCancellationSources.TryGetValue(cancelInvocationMessage.InvocationId, out var cts))
|
||||||
|
{
|
||||||
|
Log.CancelStream(_logger, cancelInvocationMessage.InvocationId);
|
||||||
|
cts.Cancel();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Stream can be canceled on the server while client is canceling stream.
|
||||||
|
Log.UnexpectedCancel(_logger);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PingMessage _:
|
||||||
|
// We don't care about pings
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Other kind of message we weren't expecting
|
||||||
|
default:
|
||||||
|
Log.UnsupportedMessageReceived(_logger, hubMessage.GetType().FullName);
|
||||||
|
throw new NotSupportedException($"Received unsupported message: {hubMessage}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Type GetReturnType(string invocationId)
|
||||||
|
{
|
||||||
|
return typeof(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IReadOnlyList<Type> GetParameterTypes(string methodName)
|
||||||
|
{
|
||||||
|
HubMethodDescriptor descriptor;
|
||||||
|
if (!_methods.TryGetValue(methodName, out descriptor))
|
||||||
|
{
|
||||||
|
return Type.EmptyTypes;
|
||||||
|
}
|
||||||
|
return descriptor.ParameterTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessInvocation(HubConnectionContext connection,
|
||||||
|
HubMethodInvocationMessage hubMethodInvocationMessage, bool isStreamedInvocation)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// If an unexpected exception occurs then we want to kill the entire connection
|
||||||
|
// by ending the processing loop
|
||||||
|
if (!_methods.TryGetValue(hubMethodInvocationMessage.Target, out var descriptor))
|
||||||
|
{
|
||||||
|
// Send an error to the client. Then let the normal completion process occur
|
||||||
|
Log.UnknownHubMethod(_logger, hubMethodInvocationMessage.Target);
|
||||||
|
await connection.WriteAsync(CompletionMessage.WithError(
|
||||||
|
hubMethodInvocationMessage.InvocationId, $"Unknown hub method '{hubMethodInvocationMessage.Target}'"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Invoke(descriptor, connection, hubMethodInvocationMessage, isStreamedInvocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Abort the entire connection if the invocation fails in an unexpected way
|
||||||
|
connection.Abort(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Invoke(HubMethodDescriptor descriptor, HubConnectionContext connection,
|
||||||
|
HubMethodInvocationMessage hubMethodInvocationMessage, bool isStreamedInvocation)
|
||||||
|
{
|
||||||
|
var methodExecutor = descriptor.MethodExecutor;
|
||||||
|
|
||||||
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
|
{
|
||||||
|
if (!await IsHubMethodAuthorized(scope.ServiceProvider, connection.User, descriptor.Policies))
|
||||||
|
{
|
||||||
|
Log.HubMethodNotAuthorized(_logger, hubMethodInvocationMessage.Target);
|
||||||
|
await SendInvocationError(hubMethodInvocationMessage, connection,
|
||||||
|
$"Failed to invoke '{hubMethodInvocationMessage.Target}' because user is unauthorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await ValidateInvocationMode(methodExecutor.MethodReturnType, isStreamedInvocation, hubMethodInvocationMessage, connection))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>();
|
||||||
|
var hub = hubActivator.Create();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InitializeHub(hub, connection);
|
||||||
|
|
||||||
|
var result = await ExecuteHubMethod(methodExecutor, hub, hubMethodInvocationMessage.Arguments);
|
||||||
|
|
||||||
|
if (isStreamedInvocation)
|
||||||
|
{
|
||||||
|
var enumerator = GetStreamingEnumerator(connection, hubMethodInvocationMessage.InvocationId, methodExecutor, result, methodExecutor.MethodReturnType);
|
||||||
|
Log.StreamingResult(_logger, hubMethodInvocationMessage.InvocationId, methodExecutor);
|
||||||
|
await StreamResultsAsync(hubMethodInvocationMessage.InvocationId, connection, enumerator);
|
||||||
|
}
|
||||||
|
// Non-empty/null InvocationId ==> Blocking invocation that needs a response
|
||||||
|
else if (!string.IsNullOrEmpty(hubMethodInvocationMessage.InvocationId))
|
||||||
|
{
|
||||||
|
Log.SendingResult(_logger, hubMethodInvocationMessage.InvocationId, methodExecutor);
|
||||||
|
await connection.WriteAsync(CompletionMessage.WithResult(hubMethodInvocationMessage.InvocationId, result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (TargetInvocationException ex)
|
||||||
|
{
|
||||||
|
Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, ex);
|
||||||
|
await SendInvocationError(hubMethodInvocationMessage, connection, ex.InnerException.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, ex);
|
||||||
|
await SendInvocationError(hubMethodInvocationMessage, connection, ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
hubActivator.Release(hub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StreamResultsAsync(string invocationId, HubConnectionContext connection, IAsyncEnumerator<object> enumerator)
|
||||||
|
{
|
||||||
|
string error = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (await enumerator.MoveNextAsync())
|
||||||
|
{
|
||||||
|
// Send the stream item
|
||||||
|
await connection.WriteAsync(new StreamItemMessage(invocationId, enumerator.Current));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ChannelClosedException ex)
|
||||||
|
{
|
||||||
|
// If the channel closes from an exception in the streaming method, grab the innerException for the error from the streaming method
|
||||||
|
error = ex.InnerException == null ? ex.Message : ex.InnerException.Message;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// If the streaming method was canceled we don't want to send a HubException message - this is not an error case
|
||||||
|
if (!(ex is OperationCanceledException && connection.ActiveRequestCancellationSources.TryGetValue(invocationId, out var cts)
|
||||||
|
&& cts.IsCancellationRequested))
|
||||||
|
{
|
||||||
|
error = ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await connection.WriteAsync(new CompletionMessage(invocationId, error: error, result: null, hasResult: false));
|
||||||
|
|
||||||
|
if (connection.ActiveRequestCancellationSources.TryRemove(invocationId, out var cts))
|
||||||
|
{
|
||||||
|
cts.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<object> ExecuteHubMethod(ObjectMethodExecutor methodExecutor, THub hub, object[] arguments)
|
||||||
|
{
|
||||||
|
// ReadableChannel is awaitable but we don't want to await it.
|
||||||
|
if (methodExecutor.IsMethodAsync && !IsChannel(methodExecutor.MethodReturnType, out _))
|
||||||
|
{
|
||||||
|
if (methodExecutor.MethodReturnType == typeof(Task))
|
||||||
|
{
|
||||||
|
await (Task)methodExecutor.Execute(hub, arguments);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return await methodExecutor.ExecuteAsync(hub, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return methodExecutor.Execute(hub, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendInvocationError(HubMethodInvocationMessage hubMethodInvocationMessage,
|
||||||
|
HubConnectionContext connection, string errorMessage)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(hubMethodInvocationMessage.InvocationId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await connection.WriteAsync(CompletionMessage.WithError(hubMethodInvocationMessage.InvocationId, errorMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeHub(THub hub, HubConnectionContext connection)
|
||||||
|
{
|
||||||
|
hub.Clients = new HubCallerClients(_hubContext.Clients, connection.ConnectionId);
|
||||||
|
hub.Context = new HubCallerContext(connection);
|
||||||
|
hub.Groups = _hubContext.Groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsChannel(Type type, out Type payloadType)
|
||||||
|
{
|
||||||
|
var channelType = type.AllBaseTypes().FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ChannelReader<>));
|
||||||
|
if (channelType == null)
|
||||||
|
{
|
||||||
|
payloadType = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
payloadType = channelType.GetGenericArguments()[0];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> IsHubMethodAuthorized(IServiceProvider provider, ClaimsPrincipal principal, IList<IAuthorizeData> policies)
|
||||||
|
{
|
||||||
|
// If there are no policies we don't need to run auth
|
||||||
|
if (!policies.Any())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var authService = provider.GetRequiredService<IAuthorizationService>();
|
||||||
|
var policyProvider = provider.GetRequiredService<IAuthorizationPolicyProvider>();
|
||||||
|
|
||||||
|
var authorizePolicy = await AuthorizationPolicy.CombineAsync(policyProvider, policies);
|
||||||
|
// AuthorizationPolicy.CombineAsync only returns null if there are no policies and we check that above
|
||||||
|
Debug.Assert(authorizePolicy != null);
|
||||||
|
|
||||||
|
var authorizationResult = await authService.AuthorizeAsync(principal, authorizePolicy);
|
||||||
|
// Only check authorization success, challenge or forbid wouldn't make sense from a hub method invocation
|
||||||
|
return authorizationResult.Succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> ValidateInvocationMode(Type resultType, bool isStreamedInvocation,
|
||||||
|
HubMethodInvocationMessage hubMethodInvocationMessage, HubConnectionContext connection)
|
||||||
|
{
|
||||||
|
var isStreamedResult = IsStreamed(resultType);
|
||||||
|
if (isStreamedResult && !isStreamedInvocation)
|
||||||
|
{
|
||||||
|
// Non-null/empty InvocationId? Blocking
|
||||||
|
if (!string.IsNullOrEmpty(hubMethodInvocationMessage.InvocationId))
|
||||||
|
{
|
||||||
|
Log.StreamingMethodCalledWithInvoke(_logger, hubMethodInvocationMessage);
|
||||||
|
await connection.WriteAsync(CompletionMessage.WithError(hubMethodInvocationMessage.InvocationId,
|
||||||
|
$"The client attempted to invoke the streaming '{hubMethodInvocationMessage.Target}' method in a non-streaming fashion."));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isStreamedResult && isStreamedInvocation)
|
||||||
|
{
|
||||||
|
Log.NonStreamingMethodCalledWithStream(_logger, hubMethodInvocationMessage);
|
||||||
|
await connection.WriteAsync(CompletionMessage.WithError(hubMethodInvocationMessage.InvocationId,
|
||||||
|
$"The client attempted to invoke the non-streaming '{hubMethodInvocationMessage.Target}' method in a streaming fashion."));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsStreamed(Type resultType)
|
||||||
|
{
|
||||||
|
var observableInterface = IsIObservable(resultType) ?
|
||||||
|
resultType :
|
||||||
|
resultType.GetInterfaces().FirstOrDefault(IsIObservable);
|
||||||
|
|
||||||
|
if (observableInterface != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsChannel(resultType, out _))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IAsyncEnumerator<object> GetStreamingEnumerator(HubConnectionContext connection, string invocationId, ObjectMethodExecutor methodExecutor, object result, Type resultType)
|
||||||
|
{
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
var observableInterface = IsIObservable(resultType) ?
|
||||||
|
resultType :
|
||||||
|
resultType.GetInterfaces().FirstOrDefault(IsIObservable);
|
||||||
|
if (observableInterface != null)
|
||||||
|
{
|
||||||
|
return AsyncEnumeratorAdapters.FromObservable(result, observableInterface, CreateCancellation());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsChannel(resultType, out var payloadType))
|
||||||
|
{
|
||||||
|
return AsyncEnumeratorAdapters.FromChannel(result, payloadType, CreateCancellation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.InvalidReturnValueFromStreamingMethod(_logger, methodExecutor.MethodInfo.Name);
|
||||||
|
throw new InvalidOperationException($"The value returned by the streaming method '{methodExecutor.MethodInfo.Name}' is null, does not implement the IObservable<> interface or is not a ReadableChannel<>.");
|
||||||
|
|
||||||
|
CancellationToken CreateCancellation()
|
||||||
|
{
|
||||||
|
var streamCts = new CancellationTokenSource();
|
||||||
|
connection.ActiveRequestCancellationSources.TryAdd(invocationId, streamCts);
|
||||||
|
return CancellationTokenSource.CreateLinkedTokenSource(connection.ConnectionAbortedToken, streamCts.Token).Token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsIObservable(Type iface)
|
||||||
|
{
|
||||||
|
return iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IObservable<>);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DiscoverHubMethods()
|
||||||
|
{
|
||||||
|
var hubType = typeof(THub);
|
||||||
|
var hubTypeInfo = hubType.GetTypeInfo();
|
||||||
|
var hubName = hubType.Name;
|
||||||
|
|
||||||
|
foreach (var methodInfo in HubReflectionHelper.GetHubMethods(hubType))
|
||||||
|
{
|
||||||
|
var methodName =
|
||||||
|
methodInfo.GetCustomAttribute<HubMethodNameAttribute>()?.Name ??
|
||||||
|
methodInfo.Name;
|
||||||
|
|
||||||
|
if (_methods.ContainsKey(methodName))
|
||||||
|
{
|
||||||
|
throw new NotSupportedException($"Duplicate definitions of '{methodName}'. Overloading is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var executor = ObjectMethodExecutor.Create(methodInfo, hubTypeInfo);
|
||||||
|
var authorizeAttributes = methodInfo.GetCustomAttributes<AuthorizeAttribute>(inherit: true);
|
||||||
|
_methods[methodName] = new HubMethodDescriptor(executor, authorizeAttributes);
|
||||||
|
|
||||||
|
Log.HubMethodBound(_logger, hubName, methodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// REVIEW: We can decide to move this out of here if we want pluggable hub discovery
|
||||||
|
private class HubMethodDescriptor
|
||||||
|
{
|
||||||
|
public HubMethodDescriptor(ObjectMethodExecutor methodExecutor, IEnumerable<IAuthorizeData> policies)
|
||||||
|
{
|
||||||
|
MethodExecutor = methodExecutor;
|
||||||
|
ParameterTypes = methodExecutor.MethodParameters.Select(p => p.ParameterType).ToArray();
|
||||||
|
Policies = policies.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectMethodExecutor MethodExecutor { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<Type> ParameterTypes { get; }
|
||||||
|
|
||||||
|
public IList<IAuthorizeData> Policies { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,12 +3,10 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Internal
|
namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
{
|
{
|
||||||
|
|
@ -24,13 +22,13 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
_logger = logger ?? NullLogger<DefaultHubProtocolResolver>.Instance;
|
_logger = logger ?? NullLogger<DefaultHubProtocolResolver>.Instance;
|
||||||
_availableProtocols = new Dictionary<string, IHubProtocol>(StringComparer.OrdinalIgnoreCase);
|
_availableProtocols = new Dictionary<string, IHubProtocol>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
foreach(var protocol in availableProtocols)
|
foreach (var protocol in availableProtocols)
|
||||||
{
|
{
|
||||||
if(_availableProtocols.ContainsKey(protocol.Name))
|
if (_availableProtocols.ContainsKey(protocol.Name))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Multiple Hub Protocols with the name '{protocol.Name}' were registered.");
|
throw new InvalidOperationException($"Multiple Hub Protocols with the name '{protocol.Name}' were registered.");
|
||||||
}
|
}
|
||||||
_logger.RegisteredSignalRProtocol(protocol.Name, protocol.GetType());
|
Log.RegisteredSignalRProtocol(_logger, protocol.Name, protocol.GetType());
|
||||||
_availableProtocols.Add(protocol.Name, protocol);
|
_availableProtocols.Add(protocol.Name, protocol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -41,11 +39,31 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
|
|
||||||
if (_availableProtocols.TryGetValue(protocolName, out var protocol))
|
if (_availableProtocols.TryGetValue(protocolName, out var protocol))
|
||||||
{
|
{
|
||||||
_logger.FoundImplementationForProtocol(protocolName);
|
Log.FoundImplementationForProtocol(_logger, protocolName);
|
||||||
return protocol;
|
return protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NotSupportedException($"The protocol '{protocolName ?? "(null)"}' is not supported.");
|
throw new NotSupportedException($"The protocol '{protocolName ?? "(null)"}' is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class Log
|
||||||
|
{
|
||||||
|
// Category: DefaultHubProtocolResolver
|
||||||
|
private static readonly Action<ILogger, string, Type, Exception> _registeredSignalRProtocol =
|
||||||
|
LoggerMessage.Define<string, Type>(LogLevel.Debug, new EventId(1, "RegisteredSignalRProtocol"), "Registered SignalR Protocol: {ProtocolName}, implemented by {ImplementationType}.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, Exception> _foundImplementationForProtocol =
|
||||||
|
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(2, "FoundImplementationForProtocol"), "Found protocol implementation for requested protocol: {ProtocolName}.");
|
||||||
|
|
||||||
|
public static void RegisteredSignalRProtocol(ILogger logger, string protocolName, Type implementationType)
|
||||||
|
{
|
||||||
|
_registeredSignalRProtocol(logger, protocolName, implementationType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FoundImplementationForProtocol(ILogger logger, string protocolName)
|
||||||
|
{
|
||||||
|
_foundImplementationForProtocol(logger, protocolName, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
|
{
|
||||||
|
public abstract class HubDispatcher<THub> : IInvocationBinder where THub : Hub
|
||||||
|
{
|
||||||
|
public abstract Task OnConnectedAsync(HubConnectionContext connection);
|
||||||
|
public abstract Task OnDisconnectedAsync(HubConnectionContext connection, Exception exception);
|
||||||
|
public abstract Task DispatchMessageAsync(HubConnectionContext connection, HubMessage hubMessage);
|
||||||
|
public abstract IReadOnlyList<Type> GetParameterTypes(string methodName);
|
||||||
|
public abstract Type GetReturnType(string invocationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,210 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
|
||||||
using Microsoft.Extensions.Internal;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Internal
|
|
||||||
{
|
|
||||||
internal static class SignalRCoreLoggerExtensions
|
|
||||||
{
|
|
||||||
// Category: HubEndPoint<THub>
|
|
||||||
private static readonly Action<ILogger, Exception> _errorProcessingRequest =
|
|
||||||
LoggerMessage.Define(LogLevel.Error, new EventId(1, nameof(ErrorProcessingRequest)), "Error when processing requests.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, Exception> _errorInvokingHubMethod =
|
|
||||||
LoggerMessage.Define<string>(LogLevel.Error, new EventId(2, nameof(ErrorInvokingHubMethod)), "Error when invoking '{hubMethod}' on hub.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, InvocationMessage, Exception> _receivedHubInvocation =
|
|
||||||
LoggerMessage.Define<InvocationMessage>(LogLevel.Debug, new EventId(3, nameof(ReceivedHubInvocation)), "Received hub invocation: {invocationMessage}.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, Exception> _unsupportedMessageReceived =
|
|
||||||
LoggerMessage.Define<string>(LogLevel.Error, new EventId(4, nameof(UnsupportedMessageReceived)), "Received unsupported message of type '{messageType}'.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, Exception> _unknownHubMethod =
|
|
||||||
LoggerMessage.Define<string>(LogLevel.Error, new EventId(5, nameof(UnknownHubMethod)), "Unknown hub method '{hubMethod}'.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, Exception> _outboundChannelClosed =
|
|
||||||
LoggerMessage.Define(LogLevel.Warning, new EventId(6, nameof(OutboundChannelClosed)), "Outbound channel was closed while trying to write hub message.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, Exception> _hubMethodNotAuthorized =
|
|
||||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, nameof(HubMethodNotAuthorized)), "Failed to invoke '{hubMethod}' because user is unauthorized.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, string, Exception> _streamingResult =
|
|
||||||
LoggerMessage.Define<string, string>(LogLevel.Trace, new EventId(8, nameof(StreamingResult)), "InvocationId {invocationId}: Streaming result of type '{resultType}'.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, string, Exception> _sendingResult =
|
|
||||||
LoggerMessage.Define<string, string>(LogLevel.Trace, new EventId(9, nameof(SendingResult)), "InvocationId {invocationId}: Sending result of type '{resultType}'.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, Exception> _failedInvokingHubMethod =
|
|
||||||
LoggerMessage.Define<string>(LogLevel.Error, new EventId(10, nameof(FailedInvokingHubMethod)), "Failed to invoke hub method '{hubMethod}'.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, string, Exception> _hubMethodBound =
|
|
||||||
LoggerMessage.Define<string, string>(LogLevel.Trace, new EventId(11, nameof(HubMethodBound)), "'{hubName}' hub method '{hubMethod}' is bound.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, Exception> _cancelStream =
|
|
||||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(12, nameof(CancelStream)), "Canceling stream for invocation {invocationId}.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, Exception> _unexpectedCancel =
|
|
||||||
LoggerMessage.Define(LogLevel.Debug, new EventId(13, nameof(UnexpectedCancel)), "CancelInvocationMessage received unexpectedly.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, Exception> _abortFailed =
|
|
||||||
LoggerMessage.Define(LogLevel.Trace, new EventId(14, nameof(AbortFailed)), "Abort callback failed.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, StreamInvocationMessage, Exception> _receivedStreamHubInvocation =
|
|
||||||
LoggerMessage.Define<StreamInvocationMessage>(LogLevel.Debug, new EventId(15, nameof(ReceivedStreamHubInvocation)), "Received stream hub invocation: {invocationMessage}.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, HubMethodInvocationMessage, Exception> _streamingMethodCalledWithInvoke =
|
|
||||||
LoggerMessage.Define<HubMethodInvocationMessage>(LogLevel.Error, new EventId(16, nameof(StreamingMethodCalledWithInvoke)), "A streaming method was invoked in the non-streaming fashion : {invocationMessage}.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, HubMethodInvocationMessage, Exception> _nonStreamingMethodCalledWithStream =
|
|
||||||
LoggerMessage.Define<HubMethodInvocationMessage>(LogLevel.Error, new EventId(17, nameof(NonStreamingMethodCalledWithStream)), "A non-streaming method was invoked in the streaming fashion : {invocationMessage}.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, Exception> _invalidReturnValueFromStreamingMethod =
|
|
||||||
LoggerMessage.Define<string>(LogLevel.Error, new EventId(18, nameof(InvalidReturnValueFromStreamingMethod)), "A streaming method returned a value that cannot be used to build enumerator {hubMethod}.");
|
|
||||||
|
|
||||||
// Category: HubConnectionContext
|
|
||||||
private static readonly Action<ILogger, string, Exception> _usingHubProtocol =
|
|
||||||
LoggerMessage.Define<string>(LogLevel.Information, new EventId(1, nameof(UsingHubProtocol)), "Using HubProtocol '{protocol}'.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, Exception> _negotiateCanceled =
|
|
||||||
LoggerMessage.Define(LogLevel.Debug, new EventId(2, nameof(NegotiateCanceled)), "Negotiate was canceled.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, Exception> _sentPing =
|
|
||||||
LoggerMessage.Define(LogLevel.Trace, new EventId(3, nameof(SentPing)), "Sent a ping message to the client.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, Exception> _transportBufferFull =
|
|
||||||
LoggerMessage.Define(LogLevel.Debug, new EventId(4, nameof(TransportBufferFull)), "Unable to send Ping message to client, the transport buffer is full.");
|
|
||||||
|
|
||||||
// Category: DefaultHubProtocolResolver
|
|
||||||
private static readonly Action<ILogger, string, Type, Exception> _registeredSignalRProtocol =
|
|
||||||
LoggerMessage.Define<string, Type>(LogLevel.Debug, new EventId(1, nameof(RegisteredSignalRProtocol)), "Registered SignalR Protocol: {ProtocolName}, implemented by {ImplementationType}.");
|
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, Exception> _foundImplementationForProtocol =
|
|
||||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(2, nameof(FoundImplementationForProtocol)), "Found protocol implementation for requested protocol: {ProtocolName}.");
|
|
||||||
|
|
||||||
public static void UsingHubProtocol(this ILogger logger, string hubProtocol)
|
|
||||||
{
|
|
||||||
_usingHubProtocol(logger, hubProtocol, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void NegotiateCanceled(this ILogger logger)
|
|
||||||
{
|
|
||||||
_negotiateCanceled(logger, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ErrorProcessingRequest(this ILogger logger, Exception exception)
|
|
||||||
{
|
|
||||||
_errorProcessingRequest(logger, exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ErrorInvokingHubMethod(this ILogger logger, string hubMethod, Exception exception)
|
|
||||||
{
|
|
||||||
_errorInvokingHubMethod(logger, hubMethod, exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ReceivedHubInvocation(this ILogger logger, InvocationMessage invocationMessage)
|
|
||||||
{
|
|
||||||
_receivedHubInvocation(logger, invocationMessage, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void UnsupportedMessageReceived(this ILogger logger, string messageType)
|
|
||||||
{
|
|
||||||
_unsupportedMessageReceived(logger, messageType, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void UnknownHubMethod(this ILogger logger, string hubMethod)
|
|
||||||
{
|
|
||||||
_unknownHubMethod(logger, hubMethod, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void OutboundChannelClosed(this ILogger logger)
|
|
||||||
{
|
|
||||||
_outboundChannelClosed(logger, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HubMethodNotAuthorized(this ILogger logger, string hubMethod)
|
|
||||||
{
|
|
||||||
_hubMethodNotAuthorized(logger, hubMethod, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void StreamingResult(this ILogger logger, string invocationId, ObjectMethodExecutor objectMethodExecutor)
|
|
||||||
{
|
|
||||||
var resultType = objectMethodExecutor.AsyncResultType == null ? objectMethodExecutor.MethodReturnType : objectMethodExecutor.AsyncResultType;
|
|
||||||
_streamingResult(logger, invocationId, resultType.FullName, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SendingResult(this ILogger logger, string invocationId, ObjectMethodExecutor objectMethodExecutor)
|
|
||||||
{
|
|
||||||
var resultType = objectMethodExecutor.AsyncResultType == null ? objectMethodExecutor.MethodReturnType : objectMethodExecutor.AsyncResultType;
|
|
||||||
_sendingResult(logger, invocationId, resultType.FullName, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void FailedInvokingHubMethod(this ILogger logger, string hubMethod, Exception exception)
|
|
||||||
{
|
|
||||||
_failedInvokingHubMethod(logger, hubMethod, exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HubMethodBound(this ILogger logger, string hubName, string hubMethod)
|
|
||||||
{
|
|
||||||
_hubMethodBound(logger, hubName, hubMethod, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void CancelStream(this ILogger logger, string invocationId)
|
|
||||||
{
|
|
||||||
_cancelStream(logger, invocationId, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void UnexpectedCancel(this ILogger logger)
|
|
||||||
{
|
|
||||||
_unexpectedCancel(logger, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void AbortFailed(this ILogger logger, Exception exception)
|
|
||||||
{
|
|
||||||
_abortFailed(logger, exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ReceivedStreamHubInvocation(this ILogger logger, StreamInvocationMessage invocationMessage)
|
|
||||||
{
|
|
||||||
_receivedStreamHubInvocation(logger, invocationMessage, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void StreamingMethodCalledWithInvoke(this ILogger logger, HubMethodInvocationMessage invocationMessage)
|
|
||||||
{
|
|
||||||
_streamingMethodCalledWithInvoke(logger, invocationMessage, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void NonStreamingMethodCalledWithStream(this ILogger logger, HubMethodInvocationMessage invocationMessage)
|
|
||||||
{
|
|
||||||
_nonStreamingMethodCalledWithStream(logger, invocationMessage, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void InvalidReturnValueFromStreamingMethod(this ILogger logger, string hubMethod)
|
|
||||||
{
|
|
||||||
_invalidReturnValueFromStreamingMethod(logger, hubMethod, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SentPing(this ILogger logger)
|
|
||||||
{
|
|
||||||
_sentPing(logger, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void TransportBufferFull(this ILogger logger)
|
|
||||||
{
|
|
||||||
_transportBufferFull(logger, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void RegisteredSignalRProtocol(this ILogger logger, string protocolName, Type implementationType)
|
|
||||||
{
|
|
||||||
_registeredSignalRProtocol(logger, protocolName, implementationType, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void FoundImplementationForProtocol(this ILogger logger, string protocolName)
|
|
||||||
{
|
|
||||||
_foundImplementationForProtocol(logger, protocolName, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -17,6 +17,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.AddSingleton(typeof(IHubContext<,>), typeof(HubContext<,>));
|
services.AddSingleton(typeof(IHubContext<,>), typeof(HubContext<,>));
|
||||||
services.AddSingleton(typeof(HubEndPoint<>), typeof(HubEndPoint<>));
|
services.AddSingleton(typeof(HubEndPoint<>), typeof(HubEndPoint<>));
|
||||||
services.AddSingleton(typeof(IUserIdProvider), typeof(DefaultUserIdProvider));
|
services.AddSingleton(typeof(IUserIdProvider), typeof(DefaultUserIdProvider));
|
||||||
|
services.AddSingleton(typeof(HubDispatcher<>), typeof(DefaultHubDispatcher<>));
|
||||||
services.AddScoped(typeof(IHubActivator<>), typeof(DefaultHubActivator<>));
|
services.AddScoped(typeof(IHubActivator<>), typeof(DefaultHubActivator<>));
|
||||||
|
|
||||||
services.AddAuthorization();
|
services.AddAuthorization();
|
||||||
|
|
|
||||||
|
|
@ -188,14 +188,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object[] BindArguments(Unpacker unpacker, Type[] parameterTypes)
|
private static object[] BindArguments(Unpacker unpacker, IReadOnlyList<Type> parameterTypes)
|
||||||
{
|
{
|
||||||
var argumentCount = ReadArrayLength(unpacker, "arguments");
|
var argumentCount = ReadArrayLength(unpacker, "arguments");
|
||||||
|
|
||||||
if (parameterTypes.Length != argumentCount)
|
if (parameterTypes.Count != argumentCount)
|
||||||
{
|
{
|
||||||
throw new FormatException(
|
throw new FormatException(
|
||||||
$"Invocation provides {argumentCount} argument(s) but target expects {parameterTypes.Length}.");
|
$"Invocation provides {argumentCount} argument(s) but target expects {parameterTypes.Count}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.SignalR.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
|
|
@ -18,7 +19,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
|
||||||
_hubMessages = hubMessages.Where(IsBindableMessage).ToArray();
|
_hubMessages = hubMessages.Where(IsBindableMessage).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type[] GetParameterTypes(string methodName)
|
public IReadOnlyList<Type> GetParameterTypes(string methodName)
|
||||||
{
|
{
|
||||||
index++;
|
index++;
|
||||||
return new TestBinder(_hubMessages[index - 1]).GetParameterTypes(methodName);
|
return new TestBinder(_hubMessages[index - 1]).GetParameterTypes(methodName);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.SignalR.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
|
|
@ -41,7 +42,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
|
||||||
_returnType = returnType;
|
_returnType = returnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type[] GetParameterTypes(string methodName)
|
public IReadOnlyList<Type> GetParameterTypes(string methodName)
|
||||||
{
|
{
|
||||||
if (_paramTypes != null)
|
if (_paramTypes != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
|
|
||||||
private class DefaultInvocationBinder : IInvocationBinder
|
private class DefaultInvocationBinder : IInvocationBinder
|
||||||
{
|
{
|
||||||
public Type[] GetParameterTypes(string methodName)
|
public IReadOnlyList<Type> GetParameterTypes(string methodName)
|
||||||
{
|
{
|
||||||
// TODO: Possibly support actual client methods
|
// TODO: Possibly support actual client methods
|
||||||
return new[] { typeof(object) };
|
return new[] { typeof(object) };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue