diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionContextBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionContextBenchmark.cs index 2bd07d955a..2be9401a6f 100644 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionContextBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionContextBenchmark.cs @@ -11,6 +11,7 @@ using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.SignalR.Core; using Microsoft.AspNetCore.SignalR.Internal; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared; using Microsoft.Extensions.Logging.Abstractions; diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionSendBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionSendBenchmark.cs index 3d0b2cf0f4..ab83b7e929 100644 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionSendBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionSendBenchmark.cs @@ -9,7 +9,7 @@ using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.AspNetCore.SignalR.Internal; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared; using Microsoft.Extensions.DependencyInjection; diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionStartBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionStartBenchmark.cs index bbeffd1c8f..2a6480a039 100644 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionStartBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionStartBenchmark.cs @@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.AspNetCore.SignalR.Internal; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared; using Microsoft.Extensions.DependencyInjection; diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs index 8d6e67f351..575ad44f82 100644 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs @@ -2,7 +2,7 @@ using System; using System.Buffers; using System.IO; using BenchmarkDotNet.Attributes; -using Microsoft.AspNetCore.SignalR.Internal; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Internal.Formatters; namespace Microsoft.AspNetCore.SignalR.Microbenchmarks diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/NegotiateProtocolBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/NegotiateProtocolBenchmark.cs new file mode 100644 index 0000000000..6718581ab9 --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/NegotiateProtocolBenchmark.cs @@ -0,0 +1,55 @@ +// 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.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Http.Connections.Internal; +using Microsoft.AspNetCore.Internal; + +namespace Microsoft.AspNetCore.SignalR.Microbenchmarks +{ + public class NegotiateProtocolBenchmark + { + private NegotiationResponse _negotiateResponse; + private Stream _stream; + + [GlobalSetup] + public void GlobalSetup() + { + _negotiateResponse = new NegotiationResponse + { + ConnectionId = "d100338e-8c01-4281-92c2-9a967fdeebcb", + AvailableTransports = new List + { + new AvailableTransport + { + Transport = "WebSockets", + TransferFormats = new List + { + "Text", + "Binary" + } + } + } + }; + _stream = Stream.Null; + } + + [Benchmark] + public Task WriteResponse_MemoryBufferWriter() + { + var writer = new MemoryBufferWriter(); + try + { + NegotiateProtocol.WriteResponse(_negotiateResponse, writer); + return writer.CopyToAsync(_stream); + } + finally + { + writer.Reset(); + } + } + } +} diff --git a/src/Common/MemoryBufferWriter.cs b/src/Common/MemoryBufferWriter.cs new file mode 100644 index 0000000000..0362196faf --- /dev/null +++ b/src/Common/MemoryBufferWriter.cs @@ -0,0 +1,174 @@ +// 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.Buffers; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Internal +{ + internal sealed class MemoryBufferWriter : IBufferWriter + { + [ThreadStatic] + private static MemoryBufferWriter _cachedInstance; + +#if DEBUG + private bool _inUse; +#endif + + private readonly int _segmentSize; + private int _bytesWritten; + + private List _fullSegments; + private byte[] _currentSegment; + private int _position; + + public MemoryBufferWriter(int segmentSize = 2048) + { + _segmentSize = segmentSize; + } + + public int Length => _bytesWritten; + + public static MemoryBufferWriter Get() + { + var writer = _cachedInstance; + if (writer == null) + { + writer = new MemoryBufferWriter(); + } + + // Taken off the thread static + _cachedInstance = null; +#if DEBUG + if (writer._inUse) + { + throw new InvalidOperationException("The reader wasn't returned!"); + } + + writer._inUse = true; +#endif + + return writer; + } + + public static void Return(MemoryBufferWriter writer) + { + _cachedInstance = writer; +#if DEBUG + writer._inUse = false; +#endif + writer.Reset(); + } + + public void Reset() + { + if (_fullSegments != null) + { + for (var i = 0; i < _fullSegments.Count; i++) + { + ArrayPool.Shared.Return(_fullSegments[i]); + } + + _fullSegments.Clear(); + } + + if (_currentSegment != null) + { + ArrayPool.Shared.Return(_currentSegment); + _currentSegment = null; + } + + _bytesWritten = 0; + _position = 0; + } + + public void Advance(int count) + { + _bytesWritten += count; + _position += count; + } + + public Memory GetMemory(int sizeHint = 0) + { + // TODO: Use sizeHint + if (_currentSegment == null) + { + _currentSegment = ArrayPool.Shared.Rent(_segmentSize); + _position = 0; + } + else if (_position == _segmentSize) + { + if (_fullSegments == null) + { + _fullSegments = new List(); + } + _fullSegments.Add(_currentSegment); + _currentSegment = ArrayPool.Shared.Rent(_segmentSize); + _position = 0; + } + + return _currentSegment.AsMemory(_position, _currentSegment.Length - _position); + } + + public Span GetSpan(int sizeHint = 0) + { + return GetMemory(sizeHint).Span; + } + + public Task CopyToAsync(Stream stream) + { + if (_fullSegments == null) + { + // There is only one segment so write without async + return stream.WriteAsync(_currentSegment, 0, _position); + } + + return CopyToSlowAsync(stream); + } + + private async Task CopyToSlowAsync(Stream stream) + { + if (_fullSegments != null) + { + // Copy full segments + for (var i = 0; i < _fullSegments.Count - 1; i++) + { + await stream.WriteAsync(_fullSegments[i], 0, _segmentSize); + } + } + + await stream.WriteAsync(_currentSegment, 0, _position); + } + + public byte[] ToArray() + { + if (_currentSegment == null) + { + return Array.Empty(); + } + + var result = new byte[_bytesWritten]; + + var totalWritten = 0; + + if (_fullSegments != null) + { + // Copy full segments + for (var i = 0; i < _fullSegments.Count; i++) + { + _fullSegments[i].CopyTo(result, totalWritten); + + totalWritten += _segmentSize; + } + } + + // Copy current incomplete segment + _currentSegment.AsSpan(0, _position).CopyTo(result.AsSpan(totalWritten)); + + return result; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Utf8BufferTextReader.cs b/src/Common/Utf8BufferTextReader.cs similarity index 97% rename from src/Microsoft.AspNetCore.SignalR.Common/Internal/Utf8BufferTextReader.cs rename to src/Common/Utf8BufferTextReader.cs index 6d73f4ccd5..4f22ea5b16 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Utf8BufferTextReader.cs +++ b/src/Common/Utf8BufferTextReader.cs @@ -9,7 +9,7 @@ using System.Text; namespace Microsoft.AspNetCore.SignalR.Internal { - public class Utf8BufferTextReader : TextReader + internal sealed class Utf8BufferTextReader : TextReader { private readonly Decoder _decoder; private ReadOnlySequence _utf8Buffer; diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Utf8BufferTextWriter.cs b/src/Common/Utf8BufferTextWriter.cs similarity index 98% rename from src/Microsoft.AspNetCore.SignalR.Common/Internal/Utf8BufferTextWriter.cs rename to src/Common/Utf8BufferTextWriter.cs index 286aac91ce..aed1681649 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Utf8BufferTextWriter.cs +++ b/src/Common/Utf8BufferTextWriter.cs @@ -9,9 +9,9 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -namespace Microsoft.AspNetCore.SignalR.Internal +namespace Microsoft.AspNetCore.Internal { - public sealed class Utf8BufferTextWriter : TextWriter + internal sealed class Utf8BufferTextWriter : TextWriter { private static readonly UTF8Encoding _utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); diff --git a/src/Microsoft.AspNetCore.Http.Connections.Common/Internal/NegotiateProtocol.cs b/src/Microsoft.AspNetCore.Http.Connections.Common/Internal/NegotiateProtocol.cs index ef77fb53fc..71a26f3984 100644 --- a/src/Microsoft.AspNetCore.Http.Connections.Common/Internal/NegotiateProtocol.cs +++ b/src/Microsoft.AspNetCore.Http.Connections.Common/Internal/NegotiateProtocol.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Text; @@ -12,44 +13,50 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal { public static class NegotiateProtocol { - private static readonly UTF8Encoding _utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - private const string ConnectionIdPropertyName = "connectionId"; private const string AvailableTransportsPropertyName = "availableTransports"; private const string TransportPropertyName = "transport"; private const string TransferFormatsPropertyName = "transferFormats"; - public static void WriteResponse(NegotiationResponse response, Stream output) + public static void WriteResponse(NegotiationResponse response, IBufferWriter output) { - using (var jsonWriter = JsonUtils.CreateJsonTextWriter(new StreamWriter(output, _utf8NoBom, 1024, leaveOpen: true))) + var textWriter = Utf8BufferTextWriter.Get(output); + try { - jsonWriter.WriteStartObject(); - jsonWriter.WritePropertyName(ConnectionIdPropertyName); - jsonWriter.WriteValue(response.ConnectionId); - jsonWriter.WritePropertyName(AvailableTransportsPropertyName); - jsonWriter.WriteStartArray(); - - foreach (var availableTransport in response.AvailableTransports) + using (var jsonWriter = JsonUtils.CreateJsonTextWriter(textWriter)) { jsonWriter.WriteStartObject(); - jsonWriter.WritePropertyName(TransportPropertyName); - jsonWriter.WriteValue(availableTransport.Transport); - jsonWriter.WritePropertyName(TransferFormatsPropertyName); + jsonWriter.WritePropertyName(ConnectionIdPropertyName); + jsonWriter.WriteValue(response.ConnectionId); + jsonWriter.WritePropertyName(AvailableTransportsPropertyName); jsonWriter.WriteStartArray(); - foreach (var transferFormat in availableTransport.TransferFormats) + foreach (var availableTransport in response.AvailableTransports) { - jsonWriter.WriteValue(transferFormat); + jsonWriter.WriteStartObject(); + jsonWriter.WritePropertyName(TransportPropertyName); + jsonWriter.WriteValue(availableTransport.Transport); + jsonWriter.WritePropertyName(TransferFormatsPropertyName); + jsonWriter.WriteStartArray(); + + foreach (var transferFormat in availableTransport.TransferFormats) + { + jsonWriter.WriteValue(transferFormat); + } + + jsonWriter.WriteEndArray(); + jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); jsonWriter.WriteEndObject(); + + jsonWriter.Flush(); } - - jsonWriter.WriteEndArray(); - jsonWriter.WriteEndObject(); - - jsonWriter.Flush(); + } + finally + { + Utf8BufferTextWriter.Return(textWriter); } } diff --git a/src/Microsoft.AspNetCore.Http.Connections.Common/Microsoft.AspNetCore.Http.Connections.Common.csproj b/src/Microsoft.AspNetCore.Http.Connections.Common/Microsoft.AspNetCore.Http.Connections.Common.csproj index 8eec6602bc..d398daf38c 100644 --- a/src/Microsoft.AspNetCore.Http.Connections.Common/Microsoft.AspNetCore.Http.Connections.Common.csproj +++ b/src/Microsoft.AspNetCore.Http.Connections.Common/Microsoft.AspNetCore.Http.Connections.Common.csproj @@ -4,10 +4,12 @@ Common primitives for ASP.NET Connection Handlers and clients netstandard2.0 Microsoft.AspNetCore.Http.Connections + true + diff --git a/src/Microsoft.AspNetCore.Http.Connections/HttpConnectionDispatcher.cs b/src/Microsoft.AspNetCore.Http.Connections/HttpConnectionDispatcher.cs index 5bc5b6dcf0..6b6b304c16 100644 --- a/src/Microsoft.AspNetCore.Http.Connections/HttpConnectionDispatcher.cs +++ b/src/Microsoft.AspNetCore.Http.Connections/HttpConnectionDispatcher.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -14,6 +15,7 @@ using Microsoft.AspNetCore.Http.Connections.Features; using Microsoft.AspNetCore.Http.Connections.Internal; using Microsoft.AspNetCore.Http.Connections.Internal.Transports; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; @@ -373,7 +375,7 @@ namespace Microsoft.AspNetCore.Http.Connections await connectionDelegate(connection); } - private Task ProcessNegotiate(HttpContext context, HttpConnectionOptions options, ConnectionLogScope logScope) + private async Task ProcessNegotiate(HttpContext context, HttpConnectionOptions options, ConnectionLogScope logScope) { context.Response.ContentType = "application/json"; @@ -384,17 +386,27 @@ namespace Microsoft.AspNetCore.Http.Connections // Connection ID metadata set. logScope.ConnectionId = connection.ConnectionId; - // Get the bytes for the connection id - var negotiateResponseBuffer = GetNegotiatePayload(connection.ConnectionId, context, options); + // Don't use thread static instance here because writer is used with async + var writer = new MemoryBufferWriter(); - Log.NegotiationRequest(_logger); + try + { + // Get the bytes for the connection id + WriteNegotiatePayload(writer, connection.ConnectionId, context, options); - // Write it out to the response with the right content length - context.Response.ContentLength = negotiateResponseBuffer.Length; - return context.Response.Body.WriteAsync(negotiateResponseBuffer, 0, negotiateResponseBuffer.Length); + Log.NegotiationRequest(_logger); + + // Write it out to the response with the right content length + context.Response.ContentLength = writer.Length; + await writer.CopyToAsync(context.Response.Body); + } + finally + { + writer.Reset(); + } } - private static byte[] GetNegotiatePayload(string connectionId, HttpContext context, HttpConnectionOptions options) + private static void WriteNegotiatePayload(IBufferWriter writer, string connectionId, HttpContext context, HttpConnectionOptions options) { var response = new NegotiationResponse(); response.ConnectionId = connectionId; @@ -415,10 +427,7 @@ namespace Microsoft.AspNetCore.Http.Connections response.AvailableTransports.Add(_longPollingAvailableTransport); } - var ms = new MemoryStream(); - NegotiateProtocol.WriteResponse(response, ms); - - return ms.ToArray(); + NegotiateProtocol.WriteResponse(response, writer); } private static bool ServerHasWebSockets(IFeatureCollection features) diff --git a/src/Microsoft.AspNetCore.Http.Connections/Microsoft.AspNetCore.Http.Connections.csproj b/src/Microsoft.AspNetCore.Http.Connections/Microsoft.AspNetCore.Http.Connections.csproj index 8cf00e91c9..cd36dcd2e0 100644 --- a/src/Microsoft.AspNetCore.Http.Connections/Microsoft.AspNetCore.Http.Connections.csproj +++ b/src/Microsoft.AspNetCore.Http.Connections/Microsoft.AspNetCore.Http.Connections.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/MemoryBufferWriter.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/MemoryBufferWriter.cs deleted file mode 100644 index 3331ee9e12..0000000000 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/MemoryBufferWriter.cs +++ /dev/null @@ -1,122 +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 System.Buffers; -using System.Collections.Generic; - -namespace Microsoft.AspNetCore.SignalR.Internal -{ - public sealed class MemoryBufferWriter : IBufferWriter - { - [ThreadStatic] - private static MemoryBufferWriter _cachedInstance; - -#if DEBUG - private bool _inUse; -#endif - - private readonly int _segmentSize; - private int _bytesWritten; - - private List _segments; - private int _position; - - private MemoryBufferWriter(int segmentSize = 2048) - { - _segmentSize = segmentSize; - - _segments = new List(); - } - - public static MemoryBufferWriter Get() - { - var writer = _cachedInstance; - if (writer == null) - { - writer = new MemoryBufferWriter(); - } - - // Taken off the thread static - _cachedInstance = null; -#if DEBUG - if (writer._inUse) - { - throw new InvalidOperationException("The reader wasn't returned!"); - } - - writer._inUse = true; -#endif - - return writer; - } - - public static void Return(MemoryBufferWriter writer) - { - _cachedInstance = writer; -#if DEBUG - writer._inUse = false; -#endif - for (var i = 0; i < writer._segments.Count; i++) - { - ArrayPool.Shared.Return(writer._segments[i]); - } - writer._segments.Clear(); - writer._bytesWritten = 0; - writer._position = 0; - } - - public Memory CurrentSegment => _segments[_segments.Count - 1]; - - public void Advance(int count) - { - _bytesWritten += count; - _position += count; - } - - public Memory GetMemory(int sizeHint = 0) - { - // TODO: Use sizeHint - - if (_segments.Count == 0 || _position == _segmentSize) - { - _segments.Add(ArrayPool.Shared.Rent(_segmentSize)); - _position = 0; - } - - // Cache property access - var currentSegment = CurrentSegment; - return currentSegment.Slice(_position, currentSegment.Length - _position); - } - - public Span GetSpan(int sizeHint = 0) - { - return GetMemory(sizeHint).Span; - } - - public byte[] ToArray() - { - if (_segments.Count == 0) - { - return Array.Empty(); - } - - var result = new byte[_bytesWritten]; - - var totalWritten = 0; - - // Copy full segments - for (var i = 0; i < _segments.Count - 1; i++) - { - _segments[i].AsMemory().CopyTo(result.AsMemory(totalWritten, _segmentSize)); - - totalWritten += _segmentSize; - } - - // Copy current incomplete segment - CurrentSegment.Slice(0, _position).CopyTo(result.AsMemory(totalWritten, _position)); - - return result; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HandshakeProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HandshakeProtocol.cs index 4cd127c35f..6ec77d39b5 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HandshakeProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HandshakeProtocol.cs @@ -1,6 +1,7 @@ // 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.Buffers; using System.IO; using Microsoft.AspNetCore.Internal; @@ -16,6 +17,22 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol private const string ErrorPropertyName = "error"; private const string TypePropertyName = "type"; + public static ReadOnlyMemory SuccessHandshakeData { get; } + + static HandshakeProtocol() + { + var memoryBufferWriter = MemoryBufferWriter.Get(); + try + { + WriteResponseMessage(HandshakeResponseMessage.Empty, memoryBufferWriter); + SuccessHandshakeData = memoryBufferWriter.ToArray(); + } + finally + { + MemoryBufferWriter.Return(memoryBufferWriter); + } + } + public static void WriteRequestMessage(HandshakeRequestMessage requestMessage, IBufferWriter output) { var textWriter = Utf8BufferTextWriter.Get(output); diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HubProtocolExtensions.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HubProtocolExtensions.cs index c3e4fba9bd..2da713eb81 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HubProtocolExtensions.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HubProtocolExtensions.cs @@ -1,6 +1,8 @@ // 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 Microsoft.AspNetCore.Internal; + namespace Microsoft.AspNetCore.SignalR.Internal.Protocol { public static class HubProtocolExtensions diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj b/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj index 046f28c120..7656d3074b 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj +++ b/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj @@ -9,6 +9,9 @@ + + + diff --git a/src/Microsoft.AspNetCore.SignalR.Core/HubConnectionContext.cs b/src/Microsoft.AspNetCore.SignalR.Core/HubConnectionContext.cs index 180fbf4409..fe9718084d 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/HubConnectionContext.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/HubConnectionContext.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Core; using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Internal.Protocol; @@ -24,7 +25,6 @@ namespace Microsoft.AspNetCore.SignalR public class HubConnectionContext { private static readonly Action _abortedCallback = AbortConnection; - private static readonly byte[] _successHandshakeResponseData; private readonly ConnectionContext _connectionContext; private readonly ILogger _logger; @@ -36,20 +36,6 @@ namespace Microsoft.AspNetCore.SignalR private long _lastSendTimestamp = Stopwatch.GetTimestamp(); private byte[] _cachedPingMessage; - static HubConnectionContext() - { - var memoryBufferWriter = MemoryBufferWriter.Get(); - try - { - HandshakeProtocol.WriteResponseMessage(HandshakeResponseMessage.Empty, memoryBufferWriter); - _successHandshakeResponseData = memoryBufferWriter.ToArray(); - } - finally - { - MemoryBufferWriter.Return(memoryBufferWriter); - } - } - public HubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory) { _connectionContext = connectionContext; @@ -264,7 +250,7 @@ namespace Microsoft.AspNetCore.SignalR if (message == HandshakeResponseMessage.Empty) { // success response is always an empty object so send cached data - _connectionContext.Transport.Output.Write(_successHandshakeResponseData); + _connectionContext.Transport.Output.Write(HandshakeProtocol.SuccessHandshakeData.Span); } else { diff --git a/src/Microsoft.AspNetCore.SignalR.Protocols.Json/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj b/src/Microsoft.AspNetCore.SignalR.Protocols.Json/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj index c8f53c26e6..6578fb5c3a 100644 --- a/src/Microsoft.AspNetCore.SignalR.Protocols.Json/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj +++ b/src/Microsoft.AspNetCore.SignalR.Protocols.Json/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj @@ -4,10 +4,13 @@ Implements the SignalR Hub Protocol over JSON. netstandard2.0 Microsoft.AspNetCore.SignalR + true + + diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj b/test/Microsoft.AspNetCore.SignalR.Client.Tests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj index 4ebefdd9b0..efdeb1d1fc 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj @@ -4,6 +4,10 @@ $(StandardTestTfms) + + + + PreserveNewest diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs index b62fc3aaec..8fb64d9cdd 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.SignalR.Internal; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Internal.Formatters; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Newtonsoft.Json; @@ -75,17 +75,21 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests { var s = await ReadSentTextMessageAsync(); + byte[] response; + var output = MemoryBufferWriter.Get(); try { HandshakeProtocol.WriteResponseMessage(HandshakeResponseMessage.Empty, output); - await Application.Output.WriteAsync(output.ToArray()); + response = output.ToArray(); } finally { MemoryBufferWriter.Return(output); } + await Application.Output.WriteAsync(response); + return s; } diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Formatters/BinaryMessageFormatterTests.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Formatters/BinaryMessageFormatterTests.cs index f355d772ec..910f8c0818 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Formatters/BinaryMessageFormatterTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Formatters/BinaryMessageFormatterTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Internal.Formatters; using Xunit; diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs index cd3f8d45bb..44b1e9949a 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Internal.Formatters; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.Extensions.Options; @@ -16,7 +17,6 @@ using Xunit; namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol { - using Microsoft.AspNetCore.SignalR.Internal; using static HubMessageHelpers; public class JsonHubProtocolTests diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs index 8091d2aa5a..10604984ec 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using Microsoft.AspNetCore.SignalR.Internal; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Internal.Formatters; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using MsgPack; diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/Utf8BufferTextWriterTests.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/Utf8BufferTextWriterTests.cs index 5765b4bdd2..629dde3edd 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/Utf8BufferTextWriterTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/Utf8BufferTextWriterTests.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Collections.Generic; using System.Text; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Internal; using Xunit; diff --git a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TestClient.cs b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TestClient.cs index 8a321c5886..61b5b8df5c 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TestClient.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TestClient.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Internal.Protocol; @@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests { public class TestClient : ITransferFormatFeature, IConnectionHeartbeatFeature, IDisposable { - private object _heartbeatLock = new object(); + private readonly object _heartbeatLock = new object(); private List<(Action handler, object state)> _heartbeatHandlers; private static int _id;