diff --git a/src/SignalR/common/testassets/Tests.Utils/HubConnectionContextUtils.cs b/src/SignalR/common/testassets/Tests.Utils/HubConnectionContextUtils.cs index 79fb05425cfc..2d8a1840ad08 100644 --- a/src/SignalR/common/testassets/Tests.Utils/HubConnectionContextUtils.cs +++ b/src/SignalR/common/testassets/Tests.Utils/HubConnectionContextUtils.cs @@ -20,7 +20,12 @@ static class HubConnectionContextUtils { public static HubConnectionContext Create(ConnectionContext connection, IHubProtocol protocol = null, string userIdentifier = null) { - return new HubConnectionContext(connection, TimeSpan.FromSeconds(15), NullLoggerFactory.Instance) + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = TimeSpan.FromSeconds(15), + }; + + return new HubConnectionContext(connection, contextOptions, NullLoggerFactory.Instance) { Protocol = protocol ?? new JsonHubProtocol(), UserIdentifier = userIdentifier, @@ -29,15 +34,20 @@ public static HubConnectionContext Create(ConnectionContext connection, IHubProt public static MockHubConnectionContext CreateMock(ConnectionContext connection) { - return new MockHubConnectionContext(connection, TimeSpan.FromSeconds(15), NullLoggerFactory.Instance, TimeSpan.FromSeconds(15), streamBufferCapacity: 10); + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = TimeSpan.FromSeconds(15), + ClientTimeoutInterval = TimeSpan.FromSeconds(15), + StreamBufferCapacity = 10, + }; + return new MockHubConnectionContext(connection, contextOptions, NullLoggerFactory.Instance); } public class MockHubConnectionContext : HubConnectionContext { - public MockHubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory, TimeSpan clientTimeoutInterval, int streamBufferCapacity) - : base(connectionContext, keepAliveInterval, loggerFactory, clientTimeoutInterval, streamBufferCapacity) + public MockHubConnectionContext(ConnectionContext connectionContext, HubConnectionContextOptions contextOptions, ILoggerFactory loggerFactory) + : base(connectionContext, contextOptions, loggerFactory) { - } public override ValueTask WriteAsync(HubMessage message, CancellationToken cancellationToken = default) diff --git a/src/SignalR/perf/Microbenchmarks/BroadcastBenchmark.cs b/src/SignalR/perf/Microbenchmarks/BroadcastBenchmark.cs index c074c1c21d72..8e1b88eccc98 100644 --- a/src/SignalR/perf/Microbenchmarks/BroadcastBenchmark.cs +++ b/src/SignalR/perf/Microbenchmarks/BroadcastBenchmark.cs @@ -46,7 +46,11 @@ public void GlobalSetup() { var pair = DuplexPipe.CreateConnectionPair(options, options); var connection = new DefaultConnectionContext(Guid.NewGuid().ToString(), pair.Application, pair.Transport); - var hubConnection = new HubConnectionContext(connection, Timeout.InfiniteTimeSpan, NullLoggerFactory.Instance); + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = Timeout.InfiniteTimeSpan, + }; + var hubConnection = new HubConnectionContext(connection, contextOptions, NullLoggerFactory.Instance); hubConnection.Protocol = protocol; _hubLifetimeManager.OnConnectedAsync(hubConnection).GetAwaiter().GetResult(); _hubLifetimeManager.AddToGroupAsync(connection.ConnectionId, TestGroupName).GetAwaiter().GetResult(); diff --git a/src/SignalR/perf/Microbenchmarks/DefaultHubDispatcherBenchmark.cs b/src/SignalR/perf/Microbenchmarks/DefaultHubDispatcherBenchmark.cs index 956a298ed975..982ead5340b6 100644 --- a/src/SignalR/perf/Microbenchmarks/DefaultHubDispatcherBenchmark.cs +++ b/src/SignalR/perf/Microbenchmarks/DefaultHubDispatcherBenchmark.cs @@ -4,9 +4,7 @@ using System; using System.Buffers; using System.Collections.Generic; -using System.IO; using System.IO.Pipelines; -using System.Reactive.Linq; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; @@ -46,7 +44,11 @@ public void GlobalSetup() var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default); var connection = new DefaultConnectionContext(Guid.NewGuid().ToString(), pair.Application, pair.Transport); - _connectionContext = new NoErrorHubConnectionContext(connection, TimeSpan.Zero, NullLoggerFactory.Instance); + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = TimeSpan.Zero, + }; + _connectionContext = new NoErrorHubConnectionContext(connection, contextOptions, NullLoggerFactory.Instance); _connectionContext.Protocol = new FakeHubProtocol(); } @@ -83,7 +85,8 @@ public class NoErrorHubConnectionContext : HubConnectionContext { public TaskCompletionSource ReceivedCompleted = new TaskCompletionSource(); - public NoErrorHubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory) : base(connectionContext, keepAliveInterval, loggerFactory) + public NoErrorHubConnectionContext(ConnectionContext connectionContext, HubConnectionContextOptions contextOptions, ILoggerFactory loggerFactory) + : base(connectionContext, contextOptions, loggerFactory) { } diff --git a/src/SignalR/perf/Microbenchmarks/DefaultHubLifetimeManagerBenchmark.cs b/src/SignalR/perf/Microbenchmarks/DefaultHubLifetimeManagerBenchmark.cs index d54311a7137f..1a2210993897 100644 --- a/src/SignalR/perf/Microbenchmarks/DefaultHubLifetimeManagerBenchmark.cs +++ b/src/SignalR/perf/Microbenchmarks/DefaultHubLifetimeManagerBenchmark.cs @@ -51,7 +51,11 @@ public void GlobalSetup() ConnectionId = connectionId, Transport = new TestDuplexPipe(ForceAsync) }; - var hubConnectionContext = new HubConnectionContext(connectionContext, TimeSpan.Zero, NullLoggerFactory.Instance); + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = TimeSpan.Zero, + }; + var hubConnectionContext = new HubConnectionContext(connectionContext, contextOptions, NullLoggerFactory.Instance); hubConnectionContext.UserIdentifier = userIdentifier; hubConnectionContext.Protocol = jsonHubProtocol; diff --git a/src/SignalR/perf/Microbenchmarks/HubConnectionContextBenchmark.cs b/src/SignalR/perf/Microbenchmarks/HubConnectionContextBenchmark.cs index 891a73b49e2e..a1ca8995782b 100644 --- a/src/SignalR/perf/Microbenchmarks/HubConnectionContextBenchmark.cs +++ b/src/SignalR/perf/Microbenchmarks/HubConnectionContextBenchmark.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Protocol; using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared; @@ -44,7 +43,11 @@ public void GlobalSetup() _pipe = new TestDuplexPipe(); var connection = new DefaultConnectionContext(Guid.NewGuid().ToString(), _pipe, _pipe); - _hubConnectionContext = new HubConnectionContext(connection, Timeout.InfiniteTimeSpan, NullLoggerFactory.Instance); + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = Timeout.InfiniteTimeSpan, + }; + _hubConnectionContext = new HubConnectionContext(connection, contextOptions, NullLoggerFactory.Instance); _successHubProtocolResolver = new TestHubProtocolResolver(new NewtonsoftJsonHubProtocol()); _failureHubProtocolResolver = new TestHubProtocolResolver(null); diff --git a/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.netcoreapp3.0.cs b/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.netcoreapp3.0.cs index 0d36db60a634..14087c25fe1f 100644 --- a/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.netcoreapp3.0.cs +++ b/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.netcoreapp3.0.cs @@ -127,9 +127,7 @@ public static partial class HubClientsExtensions } public partial class HubConnectionContext { - public HubConnectionContext(Microsoft.AspNetCore.Connections.ConnectionContext connectionContext, System.TimeSpan keepAliveInterval, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } - public HubConnectionContext(Microsoft.AspNetCore.Connections.ConnectionContext connectionContext, System.TimeSpan keepAliveInterval, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.TimeSpan clientTimeoutInterval) { } - public HubConnectionContext(Microsoft.AspNetCore.Connections.ConnectionContext connectionContext, System.TimeSpan keepAliveInterval, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.TimeSpan clientTimeoutInterval, int streamBufferCapacity) { } + public HubConnectionContext(Microsoft.AspNetCore.Connections.ConnectionContext connectionContext, Microsoft.AspNetCore.SignalR.HubConnectionContextOptions contextOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public virtual System.Threading.CancellationToken ConnectionAborted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public virtual string ConnectionId { get { throw null; } } public virtual Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get { throw null; } } @@ -141,6 +139,13 @@ public virtual void Abort() { } public virtual System.Threading.Tasks.ValueTask WriteAsync(Microsoft.AspNetCore.SignalR.Protocol.HubMessage message, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.ValueTask WriteAsync(Microsoft.AspNetCore.SignalR.SerializedHubMessage message, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } + public partial class HubConnectionContextOptions + { + public HubConnectionContextOptions() { } + public System.TimeSpan ClientTimeoutInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan KeepAliveInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int StreamBufferCapacity { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } public partial class HubConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler where THub : Microsoft.AspNetCore.SignalR.Hub { public HubConnectionHandler(Microsoft.AspNetCore.SignalR.HubLifetimeManager lifetimeManager, Microsoft.AspNetCore.SignalR.IHubProtocolResolver protocolResolver, Microsoft.Extensions.Options.IOptions globalHubOptions, Microsoft.Extensions.Options.IOptions> hubOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.SignalR.IUserIdProvider userIdProvider, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory) { } diff --git a/src/SignalR/server/Core/src/HubConnectionContext.cs b/src/SignalR/server/Core/src/HubConnectionContext.cs index f5f870b38bcb..16ca9313bf25 100644 --- a/src/SignalR/server/Core/src/HubConnectionContext.cs +++ b/src/SignalR/server/Core/src/HubConnectionContext.cs @@ -48,37 +48,17 @@ public class HubConnectionContext /// Initializes a new instance of the class. /// /// The underlying . - /// The keep alive interval. If no messages are sent by the server in this interval, a Ping message will be sent. /// The logger factory. - public HubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory) - : this(connectionContext, keepAliveInterval, loggerFactory, HubOptionsSetup.DefaultClientTimeoutInterval, HubOptionsSetup.DefaultStreamBufferCapacity) { } - - /// - /// Initializes a new instance of the class. - /// - /// The underlying . - /// The keep alive interval. If no messages are sent by the server in this interval, a Ping message will be sent. - /// The logger factory. - /// Clients we haven't heard from in this interval are assumed to have disconnected. - public HubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory, TimeSpan clientTimeoutInterval) - : this(connectionContext, keepAliveInterval, loggerFactory, clientTimeoutInterval, HubOptionsSetup.DefaultStreamBufferCapacity) { } - - /// - /// Initializes a new instance of the class. - /// - /// The underlying . - /// The keep alive interval. If no messages are sent by the server in this interval, a Ping message will be sent. - /// The logger factory. - /// Clients we haven't heard from in this interval are assumed to have disconnected. - /// The buffer size for client upload streams - public HubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory, TimeSpan clientTimeoutInterval, int streamBufferCapacity) + /// The options to configure the HubConnectionContext. + public HubConnectionContext(ConnectionContext connectionContext, HubConnectionContextOptions contextOptions, ILoggerFactory loggerFactory) { + _keepAliveInterval = contextOptions.KeepAliveInterval.Ticks; + _clientTimeoutInterval = contextOptions.ClientTimeoutInterval.Ticks; + _streamBufferCapacity = contextOptions.StreamBufferCapacity; + _connectionContext = connectionContext; _logger = loggerFactory.CreateLogger(); ConnectionAborted = _connectionAbortedTokenSource.Token; - _keepAliveInterval = keepAliveInterval.Ticks; - _clientTimeoutInterval = clientTimeoutInterval.Ticks; - _streamBufferCapacity = streamBufferCapacity; } internal StreamTracker StreamTracker diff --git a/src/SignalR/server/Core/src/HubConnectionContextOptions.cs b/src/SignalR/server/Core/src/HubConnectionContextOptions.cs new file mode 100644 index 000000000000..a38096cbefea --- /dev/null +++ b/src/SignalR/server/Core/src/HubConnectionContextOptions.cs @@ -0,0 +1,28 @@ +// 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; + +namespace Microsoft.AspNetCore.SignalR +{ + /// + /// Options used to configure . + /// + public class HubConnectionContextOptions + { + /// + /// Gets or sets the interval used to send keep alive pings to connected clients. + /// + public TimeSpan KeepAliveInterval { get; set; } + + /// + /// Gets or sets the time window clients have to send a message before the server closes the connection. + /// + public TimeSpan ClientTimeoutInterval { get; set; } + + /// + /// Gets or sets the max buffer size for client upload streams. + /// + public int StreamBufferCapacity { get; set; } + } +} diff --git a/src/SignalR/server/Core/src/HubConnectionHandler.cs b/src/SignalR/server/Core/src/HubConnectionHandler.cs index d3e2b4185c52..252f7436da39 100644 --- a/src/SignalR/server/Core/src/HubConnectionHandler.cs +++ b/src/SignalR/server/Core/src/HubConnectionHandler.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.SignalR.Protocol; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.SignalR @@ -75,21 +74,26 @@ IServiceScopeFactory serviceScopeFactory public override async Task OnConnectedAsync(ConnectionContext connection) { // We check to see if HubOptions are set because those take precedence over global hub options. - // Then set the keepAlive and handshakeTimeout values to the defaults in HubOptionsSetup incase they were explicitly set to null. - var keepAlive = _hubOptions.KeepAliveInterval ?? _globalHubOptions.KeepAliveInterval ?? HubOptionsSetup.DefaultKeepAliveInterval; - var clientTimeout = _hubOptions.ClientTimeoutInterval ?? _globalHubOptions.ClientTimeoutInterval ?? HubOptionsSetup.DefaultClientTimeoutInterval; - var handshakeTimeout = _hubOptions.HandshakeTimeout ?? _globalHubOptions.HandshakeTimeout ?? HubOptionsSetup.DefaultHandshakeTimeout; - var streamBufferCapacity = _hubOptions.StreamBufferCapacity ?? _globalHubOptions.StreamBufferCapacity ?? HubOptionsSetup.DefaultStreamBufferCapacity; - var supportedProtocols = _hubOptions.SupportedProtocols ?? _globalHubOptions.SupportedProtocols; + // Then set the keepAlive and handshakeTimeout values to the defaults in HubOptionsSetup when they were explicitly set to null. - if (supportedProtocols != null && supportedProtocols.Count == 0) + var supportedProtocols = _hubOptions.SupportedProtocols ?? _globalHubOptions.SupportedProtocols; + if (supportedProtocols == null || supportedProtocols.Count == 0) { throw new InvalidOperationException("There are no supported protocols"); } + var handshakeTimeout = _hubOptions.HandshakeTimeout ?? _globalHubOptions.HandshakeTimeout ?? HubOptionsSetup.DefaultHandshakeTimeout; + + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = _hubOptions.KeepAliveInterval ?? _globalHubOptions.KeepAliveInterval ?? HubOptionsSetup.DefaultKeepAliveInterval, + ClientTimeoutInterval = _hubOptions.ClientTimeoutInterval ?? _globalHubOptions.ClientTimeoutInterval ?? HubOptionsSetup.DefaultClientTimeoutInterval, + StreamBufferCapacity = _hubOptions.StreamBufferCapacity ?? _globalHubOptions.StreamBufferCapacity ?? HubOptionsSetup.DefaultStreamBufferCapacity, + }; + Log.ConnectedStarting(_logger); - var connectionContext = new HubConnectionContext(connection, keepAlive, _loggerFactory, clientTimeout, streamBufferCapacity); + var connectionContext = new HubConnectionContext(connection, contextOptions, _loggerFactory); var resolvedSupportedProtocols = (supportedProtocols as IReadOnlyList) ?? supportedProtocols.ToList(); if (!await connectionContext.HandshakeAsync(handshakeTimeout, resolvedSupportedProtocols, _protocolResolver, _userIdProvider, _enableDetailedErrors)) diff --git a/src/SignalR/server/Core/src/HubOptions.cs b/src/SignalR/server/Core/src/HubOptions.cs index 7c49fd2957a8..e92a3686ca46 100644 --- a/src/SignalR/server/Core/src/HubOptions.cs +++ b/src/SignalR/server/Core/src/HubOptions.cs @@ -17,7 +17,7 @@ public class HubOptions // for all available protocols. /// - /// Gets or sets the interval used by the server to timeout incoming handshake requests by clients. The default timeout is 15 seconds + /// Gets or sets the interval used by the server to timeout incoming handshake requests by clients. The default timeout is 15 seconds. /// public TimeSpan? HandshakeTimeout { get; set; } = null; diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs index 25e47f03f4db..56b77c0b7a50 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs @@ -2309,6 +2309,54 @@ public async Task HubOptionsCanUseCustomMessagePackSettings() } } + [Fact] + public async Task HubOptionsCanNotHaveNullSupportedProtocols() + { + using (StartVerifiableLog()) + { + var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services => + { + services.AddSignalR(o => + { + o.SupportedProtocols = null; + }); + }, LoggerFactory); + + var connectionHandler = serviceProvider.GetService>(); + + var msgPackOptions = serviceProvider.GetRequiredService>(); + using (var client = new TestClient(protocol: new MessagePackHubProtocol(msgPackOptions))) + { + client.SupportedFormats = TransferFormat.Binary; + await Assert.ThrowsAsync(async () => await await client.ConnectAsync(connectionHandler, expectedHandshakeResponseMessage: false)).OrTimeout(); + } + } + } + + [Fact] + public async Task HubOptionsCanNotHaveEmptySupportedProtocols() + { + using (StartVerifiableLog()) + { + var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services => + { + services.AddSignalR(o => + { + o.SupportedProtocols = new List(); + }); + }, LoggerFactory); + + var connectionHandler = serviceProvider.GetService>(); + + var msgPackOptions = serviceProvider.GetRequiredService>(); + using (var client = new TestClient(protocol: new MessagePackHubProtocol(msgPackOptions))) + { + client.SupportedFormats = TransferFormat.Binary; + await Assert.ThrowsAsync(async () => await await client.ConnectAsync(connectionHandler, expectedHandshakeResponseMessage: false)).OrTimeout(); + } + } + } + [Fact] public async Task ConnectionUserIdIsAssignedByUserIdProvider() {