From 41f54d001b166daaccc778535a16386ec8e6df52 Mon Sep 17 00:00:00 2001 From: David Fowler <davidfowl@gmail.com> Date: Thu, 22 Jun 2017 09:23:52 -0700 Subject: [PATCH] Remove dependencies on a bunch of corefxlab things (#576) * Remove dependencies on a bunch of corefxlab things - Used Stream instead of IOutput - Removed pipelines dependency in most places. --- SignalR.sln | 4 +- build/dependencies.props | 1 + .../EchoEndPoint.cs | 1 - .../SocialWeather/SocialWeatherEndPoint.cs | 1 - .../EndPoints/MessagesEndPoint.cs | 1 - src/Common/IOutputExtensions.cs | 59 -------- .../HubConnection.cs | 2 +- .../Formatters/BinaryMessageFormatter.cs | 22 +-- .../Formatters/BinaryMessageParser.cs | 31 ++-- .../Internal/Formatters/BufferExtensions.cs | 32 ----- .../Formatters/TextMessageFormatter.cs | 33 +++-- .../Internal/Formatters/TextMessageParser.cs | 136 +++++++++++++++--- .../HubProtocolWriteMessageExtensions.cs | 20 +-- .../Internal/Protocol/IHubProtocol.cs | 3 +- .../Internal/Protocol/JsonHubProtocol.cs | 6 +- ...Microsoft.AspNetCore.SignalR.Common.csproj | 7 +- .../RedisHubLifetimeManager.cs | 5 +- .../DefaultHubLifetimeManager.cs | 5 +- .../HubEndPoint.cs | 2 +- .../HttpConnectionDispatcher.cs | 1 - .../ServerSentEventsMessageFormatter.cs | 31 ++-- .../Transports/ServerSentEventsTransport.cs | 15 +- .../Microsoft.AspNetCore.Sockets.Http.csproj | 7 +- test/Common/ArrayOutput.cs | 75 ---------- test/Common/ByteArrayExtensions.cs | 47 ------ .../HubConnectionTests.cs | 3 +- ...oft.AspNetCore.SignalR.Client.Tests.csproj | 1 - .../TestConnection.cs | 5 +- .../Internal/Protocol/JsonHubProtocolTests.cs | 8 +- ...oft.AspNetCore.SignalR.Common.Tests.csproj | 4 - .../MessageParserBenchmark.cs | 22 +-- ....AspNetCore.SignalR.Microbenchmarks.csproj | 5 - .../Formatters/BinaryMessageFormatterTests.cs | 11 +- .../Formatters/BinaryMessageParserTests.cs | 33 ++--- .../Formatters/TextMessageFormatterTests.cs | 6 +- .../Formatters/TextMessageParserTests.cs | 42 +++--- .../Microsoft.AspNetCore.SignalR.Tests.csproj | 2 - .../TestClient.cs | 2 +- .../Microsoft.AspNetCore.Sockets.Tests.csproj | 1 - .../ServerSentEventsMessageFormatterTests.cs | 3 +- 40 files changed, 258 insertions(+), 437 deletions(-) delete mode 100644 src/Common/IOutputExtensions.cs delete mode 100644 src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BufferExtensions.cs delete mode 100644 test/Common/ArrayOutput.cs delete mode 100644 test/Common/ByteArrayExtensions.cs diff --git a/SignalR.sln b/SignalR.sln index 0a0d5e979a2..9436f9d8a2b 100644 --- a/SignalR.sln +++ b/SignalR.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26510.0 +VisualStudioVersion = 15.0.26606.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DA69F624-5398-4884-87E4-B816698CDE65}" EndProject @@ -52,8 +52,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Signal EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{6CEC3DC2-5B01-45A8-8F0D-8531315DA90B}" ProjectSection(SolutionItems) = preProject - test\Common\ArrayOutput.cs = test\Common\ArrayOutput.cs - test\Common\ByteArrayExtensions.cs = test\Common\ByteArrayExtensions.cs test\Common\ChannelExtensions.cs = test\Common\ChannelExtensions.cs test\Common\TaskExtensions.cs = test\Common\TaskExtensions.cs EndProjectSection diff --git a/build/dependencies.props b/build/dependencies.props index a2f64ad9f29..7cf7a554477 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,6 +3,7 @@ <AspNetCoreIntegrationTestingVersion>0.4.0-*</AspNetCoreIntegrationTestingVersion> <AspNetCoreVersion>2.0.0-*</AspNetCoreVersion> <CoreFxLabsVersion>0.1.0-*</CoreFxLabsVersion> + <CoreFxVersion>4.4.0-*</CoreFxVersion> <GoogleProtobufVersion>3.1.0</GoogleProtobufVersion> <InternalAspNetCoreSdkVersion>2.1.0-*</InternalAspNetCoreSdkVersion> <JsonNetVersion>10.0.1</JsonNetVersion> diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/EchoEndPoint.cs b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/EchoEndPoint.cs index 2852c885ae6..835bbbd2f08 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/EchoEndPoint.cs +++ b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/EchoEndPoint.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO.Pipelines; using System.Threading.Tasks; using Microsoft.AspNetCore.Sockets; diff --git a/samples/SocialWeather/SocialWeatherEndPoint.cs b/samples/SocialWeather/SocialWeatherEndPoint.cs index 685787d5595..2e8e4fa7da2 100644 --- a/samples/SocialWeather/SocialWeatherEndPoint.cs +++ b/samples/SocialWeather/SocialWeatherEndPoint.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; -using System.IO.Pipelines; using System.Threading.Tasks; using Microsoft.AspNetCore.Sockets; using Microsoft.Extensions.Logging; diff --git a/samples/SocketsSample/EndPoints/MessagesEndPoint.cs b/samples/SocketsSample/EndPoints/MessagesEndPoint.cs index 4d910b1f665..eee54395e21 100644 --- a/samples/SocketsSample/EndPoints/MessagesEndPoint.cs +++ b/samples/SocketsSample/EndPoints/MessagesEndPoint.cs @@ -2,7 +2,6 @@ // 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.Pipelines; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Sockets; diff --git a/src/Common/IOutputExtensions.cs b/src/Common/IOutputExtensions.cs deleted file mode 100644 index 19b5d3b1e4e..00000000000 --- a/src/Common/IOutputExtensions.cs +++ /dev/null @@ -1,59 +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.Binary; -using System.Runtime; -using System.Runtime.CompilerServices; - -namespace System.Buffers -{ - internal static class IOutputExtensions - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryWriteBigEndian<[Primitive] T>(this IOutput self, T value) where T : struct - { - var size = Unsafe.SizeOf<T>(); - if (self.Buffer.Length < size) - { - self.Enlarge(size); - if (self.Buffer.Length < size) - { - return false; - } - } - - self.Buffer.WriteBigEndian(value); - self.Advance(size); - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryWrite(this IOutput self, ReadOnlySpan<byte> data) - { - while (data.Length > 0) - { - if (self.Buffer.Length == 0) - { - self.Enlarge(data.Length); - if (self.Buffer.Length == 0) - { - // Failed to enlarge - return false; - } - } - - var toWrite = Math.Min(self.Buffer.Length, data.Length); - - // Slice based on what we can fit - var chunk = data.Slice(0, toWrite); - data = data.Slice(toWrite); - - // Copy the chunk - chunk.CopyTo(self.Buffer); - self.Advance(chunk.Length); - } - - return true; - } - } -} diff --git a/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs b/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs index fb6678d1b8d..afbc2ad32aa 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs @@ -133,7 +133,7 @@ namespace Microsoft.AspNetCore.SignalR.Client { try { - var payload = await _protocol.WriteToArrayAsync(invocationMessage); + var payload = _protocol.WriteToArray(invocationMessage); _logger.LogInformation("Sending Invocation '{invocationId}'", invocationMessage.InvocationId); diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageFormatter.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageFormatter.cs index 3d8df0ca8cd..4a9b3ecab80 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageFormatter.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageFormatter.cs @@ -2,24 +2,26 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Binary; using System.Buffers; +using System.IO; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { public static class BinaryMessageFormatter { - public static bool TryWriteMessage(ReadOnlySpan<byte> payload, IOutput output) + public static bool TryWriteMessage(ReadOnlySpan<byte> payload, MemoryStream output) { - // Try to write the data - if (!output.TryWriteBigEndian((long)payload.Length)) - { - return false; - } + var length = sizeof(long); + var buffer = ArrayPool<byte>.Shared.Rent(length); + BufferWriter.WriteBigEndian<long>(buffer, payload.Length); + output.Write(buffer, 0, length); + ArrayPool<byte>.Shared.Return(buffer); - if (!output.TryWrite(payload)) - { - return false; - } + buffer = ArrayPool<byte>.Shared.Rent(payload.Length); + payload.CopyTo(buffer); + output.Write(buffer, 0, payload.Length); + ArrayPool<byte>.Shared.Return(buffer); return true; } diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageParser.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageParser.cs index 8cc02cfa3e4..56c1c52793d 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageParser.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageParser.cs @@ -3,7 +3,6 @@ using System; using System.Binary; -using System.Buffers; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { @@ -16,33 +15,27 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters _state = default(ParserState); } - public bool TryParseMessage(ref BytesReader buffer, out ReadOnlyBuffer<byte> payload) + public bool TryParseMessage(ref ReadOnlySpan<byte> buffer, out ReadOnlyBuffer<byte> payload) { if (_state.Length == null) { - var lengthBuffer = buffer.TryReadBytes(sizeof(long)); + long length = 0; - if (lengthBuffer == null) + if (buffer.Length < sizeof(long)) { payload = default(ReadOnlyBuffer<byte>); return false; } - var length = lengthBuffer.Value.ToSingleSpan(); + length = buffer.Slice(0, sizeof(long)).ReadBigEndian<long>(); - if (length.Length < sizeof(long)) - { - payload = default(ReadOnlyBuffer<byte>); - return false; - } - - var longLength = length.ReadBigEndian<long>(); - if (longLength > Int32.MaxValue) + if (length > Int32.MaxValue) { throw new FormatException("Messages over 2GB in size are not supported"); } - buffer.Advance(length.Length); - _state.Length = (int)longLength; + + buffer = buffer.Slice(sizeof(long)); + _state.Length = (int)length; } if (_state.Payload == null) @@ -50,13 +43,13 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters _state.Payload = new byte[_state.Length.Value]; } - while (_state.Read < _state.Payload.Length && buffer.Unread.Length > 0) + while (_state.Read < _state.Payload.Length && buffer.Length > 0) { // Copy what we can from the current unread segment - var toCopy = Math.Min(_state.Payload.Length - _state.Read, buffer.Unread.Length); - buffer.Unread.Slice(0, toCopy).CopyTo(_state.Payload.Slice(_state.Read)); + var toCopy = Math.Min(_state.Payload.Length - _state.Read, buffer.Length); + buffer.Slice(0, toCopy).CopyTo(new Span<byte>(_state.Payload, _state.Read)); _state.Read += toCopy; - buffer.Advance(toCopy); + buffer = buffer.Slice(toCopy); } if (_state.Read == _state.Payload.Length) diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BufferExtensions.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BufferExtensions.cs deleted file mode 100644 index c9c1f257dd1..00000000000 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BufferExtensions.cs +++ /dev/null @@ -1,32 +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. - -namespace System.Buffers -{ - internal static class BufferExtensions - { - public static ReadOnlySpan<byte> ToSingleSpan(this ReadOnlyBytes self) - { - if (self.Rest == null) - { - return self.First.Span; - } - else - { - return self.ToSpan(); - } - } - - public static ReadOnlyBytes? TryReadBytes(this BytesReader self, int count) - { - try - { - return self.ReadBytes(count); - } - catch (ArgumentOutOfRangeException) - { - return null; - } - } - } -} diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageFormatter.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageFormatter.cs index ed4a67580a5..f66c4880d2e 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageFormatter.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageFormatter.cs @@ -2,37 +2,44 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Binary; using System.Buffers; +using System.Globalization; +using System.IO; using System.Text; -using System.Text.Formatting; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { public static class TextMessageFormatter { + private const int Int32OverflowLength = 10; + internal const char FieldDelimiter = ':'; internal const char MessageDelimiter = ';'; - - public static bool TryWriteMessage(ReadOnlySpan<byte> payload, IOutput output) + + public static bool TryWriteMessage(ReadOnlySpan<byte> payload, Stream output) { // Calculate the length, it's the number of characters for text messages, but number of base64 characters for binary - var length = payload.Length; // Write the length as a string - output.Append(length, TextEncoder.Utf8); + + // Super inefficient... + var lengthString = payload.Length.ToString(CultureInfo.InvariantCulture); + var buffer = ArrayPool<byte>.Shared.Rent(Int32OverflowLength); + var encodedLength = Encoding.UTF8.GetBytes(lengthString, 0, lengthString.Length, buffer, 0); + output.Write(buffer, 0, encodedLength); + ArrayPool<byte>.Shared.Return(buffer); // Write the field delimiter ':' - output.Append(FieldDelimiter, TextEncoder.Utf8); + output.WriteByte((byte)FieldDelimiter); - // Write the payload - if (!output.TryWrite(payload)) - { - return false; - } + buffer = ArrayPool<byte>.Shared.Rent(payload.Length); + payload.CopyTo(buffer); + output.Write(buffer, 0, payload.Length); + ArrayPool<byte>.Shared.Return(buffer); // Terminator - output.Append(MessageDelimiter, TextEncoder.Utf8); + output.WriteByte((byte)MessageDelimiter); + return true; } } diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageParser.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageParser.cs index 6e7357dce29..9793dcf793f 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageParser.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageParser.cs @@ -2,13 +2,14 @@ // 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.Text; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { public class TextMessageParser { + private const int Int32OverflowLength = 10; + private ParserState _state; public void Reset() @@ -20,9 +21,9 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters /// Attempts to parse a message from the buffer. Returns 'false' if there is not enough data to complete a message. Throws an /// exception if there is a format error in the provided data. /// </summary> - public bool TryParseMessage(ref BytesReader buffer, out ReadOnlyBuffer<byte> payload) + public bool TryParseMessage(ref ReadOnlySpan<byte> buffer, out ReadOnlyBuffer<byte> payload) { - while (buffer.Unread.Length > 0) + while (buffer.Length > 0) { switch (_state.Phase) { @@ -65,53 +66,50 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters return false; } - private bool TryReadLength(ref BytesReader buffer) + private bool TryReadLength(ref ReadOnlySpan<byte> buffer) { // Read until the first ':' to find the length - var lengthBuffer = buffer.ReadBytesUntil((byte)TextMessageFormatter.FieldDelimiter); + var found = buffer.IndexOf((byte)TextMessageFormatter.FieldDelimiter); - if (lengthBuffer == null) + if (found == -1) { // Insufficient data return false; } - var lengthSpan = lengthBuffer.Value.ToSingleSpan(); + var lengthSpan = buffer.Slice(0, found); - // Parse the length - if (!PrimitiveParser.TryParseInt32(lengthSpan, out var length, out var consumedByLength, encoder: TextEncoder.Utf8) || consumedByLength < lengthSpan.Length) + if (!TryParseInt32(lengthSpan, out var length, out var bytesConsumed) || bytesConsumed < lengthSpan.Length) { - if (TextEncoder.Utf8.TryDecode(lengthSpan, out var lengthString, out _)) - { - throw new FormatException($"Invalid length: '{lengthString}'"); - } - - throw new FormatException("Invalid length"); + throw new FormatException($"Invalid length: '{Encoding.UTF8.GetString(lengthSpan.ToArray())}'"); } + buffer = buffer.Slice(found); + _state.Length = length; _state.Phase = ParsePhase.LengthComplete; return true; } - private bool TryReadDelimiter(ref BytesReader buffer, char delimiter, ParsePhase nextPhase, string field) + private bool TryReadDelimiter(ref ReadOnlySpan<byte> buffer, char delimiter, ParsePhase nextPhase, string field) { - if (buffer.Unread.Length == 0) + if (buffer.Length == 0) { return false; } - if (buffer.Unread[0] != delimiter) + if (buffer[0] != delimiter) { throw new FormatException($"Missing delimiter '{delimiter}' after {field}"); } - buffer.Advance(1); + + buffer = buffer.Slice(1); _state.Phase = nextPhase; return true; } - private void ReadPayload(ref BytesReader buffer) + private void ReadPayload(ref ReadOnlySpan<byte> buffer) { if (_state.Payload == null) { @@ -125,11 +123,103 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters else { // Copy as much as possible from the Unread buffer - var toCopy = Math.Min(_state.Length - _state.Read, buffer.Unread.Length); - buffer.Unread.Slice(0, toCopy).CopyTo(_state.Payload.Slice(_state.Read)); + var toCopy = Math.Min(_state.Length - _state.Read, buffer.Length); + + buffer.Slice(0, toCopy).CopyTo(new Span<byte>(_state.Payload, _state.Read)); _state.Read += toCopy; - buffer.Advance(toCopy); + buffer = buffer.Slice(toCopy); + } + } + + public static bool TryParseInt32(ReadOnlySpan<byte> text, out int value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default(int); + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int32OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + bytesConsumed = 0; + value = default(int); + return false; + } + int parsedValue = firstDigit; + + if (text.Length < overflowLength) + { + // Length is less than Int32OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < text.Length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLength; overflow is only possible after Int32OverflowLength + // digits. There may be no overflow after Int32OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < text.Length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + // If parsedValue > (int.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (int.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > int.MaxValue / 10 || parsedValue == int.MaxValue / 10 && nextDigitTooLarge) + { + bytesConsumed = 0; + value = default(int); + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } } + + bytesConsumed = text.Length; + value = parsedValue * sign; + return true; } private struct ParserState diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HubProtocolWriteMessageExtensions.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HubProtocolWriteMessageExtensions.cs index b53f2909ee3..6f499e6cd65 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HubProtocolWriteMessageExtensions.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HubProtocolWriteMessageExtensions.cs @@ -3,34 +3,22 @@ using System; using System.IO; -using System.IO.Pipelines; -using System.IO.Pipelines.Text.Primitives; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.AspNetCore.SignalR.Internal.Protocol { public static class HubProtocolWriteMessageExtensions { - public static async ValueTask<byte[]> WriteToArrayAsync(this IHubProtocol protocol, HubMessage message) + public static byte[] WriteToArray(this IHubProtocol protocol, HubMessage message) { - using (var memoryStream = new MemoryStream()) + using (var output = new MemoryStream()) { - var pipe = memoryStream.AsPipelineWriter(); - - // See https://github.com/dotnet/corefxlab/issues/1460, the TextEncoder is unimportant but required. - var output = new PipelineTextOutput(pipe, TextEncoder.Utf8); - // Encode the message if (!protocol.TryWriteMessage(message, output)) { throw new InvalidOperationException("Failed to write message to the output stream"); } - - await output.FlushAsync(); - - // Create a message - return memoryStream.ToArray(); + + return output.ToArray(); } } } diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/IHubProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/IHubProtocol.cs index a8bae3cc05f..4ba74233219 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/IHubProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/IHubProtocol.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.IO; namespace Microsoft.AspNetCore.SignalR.Internal.Protocol { @@ -11,6 +12,6 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol { bool TryParseMessages(ReadOnlySpan<byte> input, IInvocationBinder binder, out IList<HubMessage> messages); - bool TryWriteMessage(HubMessage message, IOutput output); + bool TryWriteMessage(HubMessage message, Stream output); } } diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/JsonHubProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/JsonHubProtocol.cs index 370f219010e..82ed1fffeb4 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/JsonHubProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/JsonHubProtocol.cs @@ -47,11 +47,10 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol public bool TryParseMessages(ReadOnlySpan<byte> input, IInvocationBinder binder, out IList<HubMessage> messages) { - var reader = new BytesReader(input.ToArray()); messages = new List<HubMessage>(); var parser = new TextMessageParser(); - while (parser.TryParseMessage(ref reader, out var payload)) + while (parser.TryParseMessage(ref input, out var payload)) { // TODO: Need a span-native JSON parser! using (var memoryStream = new MemoryStream(payload.ToArray())) @@ -63,9 +62,8 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol return messages.Count > 0; } - public bool TryWriteMessage(HubMessage message, IOutput output) + public bool TryWriteMessage(HubMessage message, Stream output) { - // TODO: Need IOutput-compatible JSON serializer! using (var memoryStream = new MemoryStream()) { WriteMessage(message, memoryStream); 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 e783c7c4916..2c479940fe8 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj +++ b/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj @@ -12,13 +12,10 @@ <RootNamespace>Microsoft.AspNetCore.SignalR</RootNamespace> </PropertyGroup> - <ItemGroup> - <Compile Include="../Common/IOutputExtensions.cs" Link="IOutputExtensions.cs" /> - </ItemGroup> - <ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" /> - <PackageReference Include="System.IO.Pipelines.Text.Primitives" Version="$(CoreFxLabsVersion)" /> + <PackageReference Include="System.Buffers.Primitives" Version="$(CoreFxLabsVersion)" /> + <PackageReference Include="System.Binary" Version="$(CoreFxLabsVersion)" /> </ItemGroup> </Project> diff --git a/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs b/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs index dd80dc5afc0..13308c22247 100644 --- a/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs +++ b/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs @@ -5,15 +5,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; -using System.IO.Pipelines; -using System.IO.Pipelines.Text.Primitives; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.Sockets; -using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; @@ -313,7 +310,7 @@ namespace Microsoft.AspNetCore.SignalR.Redis private async Task WriteAsync(ConnectionContext connection, HubMessage hubMessage) { var protocol = connection.Metadata.Get<IHubProtocol>(HubConnectionMetadataNames.HubProtocol); - var data = await protocol.WriteToArrayAsync(hubMessage); + var data = protocol.WriteToArray(hubMessage); while (await connection.Transport.Output.WaitToWriteAsync()) { diff --git a/src/Microsoft.AspNetCore.SignalR/DefaultHubLifetimeManager.cs b/src/Microsoft.AspNetCore.SignalR/DefaultHubLifetimeManager.cs index 15218eb4d6b..4d1ee56803b 100644 --- a/src/Microsoft.AspNetCore.SignalR/DefaultHubLifetimeManager.cs +++ b/src/Microsoft.AspNetCore.SignalR/DefaultHubLifetimeManager.cs @@ -4,14 +4,11 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Pipelines; -using System.IO.Pipelines.Text.Primitives; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.Sockets; -using Microsoft.AspNetCore.Sockets.Internal.Formatters; namespace Microsoft.AspNetCore.SignalR { @@ -125,7 +122,7 @@ namespace Microsoft.AspNetCore.SignalR private async Task WriteAsync(ConnectionContext connection, HubMessage hubMessage) { var protocol = connection.Metadata.Get<IHubProtocol>(HubConnectionMetadataNames.HubProtocol); - var payload = await protocol.WriteToArrayAsync(hubMessage); + var payload = protocol.WriteToArray(hubMessage); while (await connection.Transport.Output.WaitToWriteAsync()) { diff --git a/src/Microsoft.AspNetCore.SignalR/HubEndPoint.cs b/src/Microsoft.AspNetCore.SignalR/HubEndPoint.cs index 139920dd059..cd04d3acd7c 100644 --- a/src/Microsoft.AspNetCore.SignalR/HubEndPoint.cs +++ b/src/Microsoft.AspNetCore.SignalR/HubEndPoint.cs @@ -230,7 +230,7 @@ namespace Microsoft.AspNetCore.SignalR private async Task SendMessageAsync(ConnectionContext connection, IHubProtocol protocol, HubMessage hubMessage) { - var payload = await protocol.WriteToArrayAsync(hubMessage); + var payload = protocol.WriteToArray(hubMessage); while (await connection.Transport.Output.WaitToWriteAsync()) { diff --git a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs index 5a7ed4af4ed..e5a5b70d17e 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.IO.Pipelines; using System.Text; using System.Threading; using System.Threading.Tasks; diff --git a/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsMessageFormatter.cs b/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsMessageFormatter.cs index 896134e1a54..54b6a6ca87b 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsMessageFormatter.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsMessageFormatter.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Binary; using System.Buffers; +using System.IO; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters private const byte LineFeed = (byte)'\n'; - public static bool TryWriteMessage(ReadOnlySpan<byte> payload, IOutput output) + public static bool TryWriteMessage(ReadOnlySpan<byte> payload, MemoryStream output) { // Write the payload if (!TryWritePayload(payload, output)) @@ -22,15 +22,12 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters return false; } - if (!output.TryWrite(Newline)) - { - return false; - } + output.Write(Newline, 0, Newline.Length); return true; } - private static bool TryWritePayload(ReadOnlySpan<byte> payload, IOutput output) + private static bool TryWritePayload(ReadOnlySpan<byte> payload, Stream output) { // Short-cut for empty payload if (payload.Length == 0) @@ -88,22 +85,16 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters return true; } - private static bool TryWriteLine(ReadOnlySpan<byte> line, IOutput output) + private static bool TryWriteLine(ReadOnlySpan<byte> payload, Stream output) { - if (!output.TryWrite(DataPrefix)) - { - return false; - } + output.Write(DataPrefix, 0, DataPrefix.Length); - if (!output.TryWrite(line)) - { - return false; - } + var buffer = ArrayPool<byte>.Shared.Rent(payload.Length); + payload.CopyTo(buffer); + output.Write(buffer, 0, payload.Length); + ArrayPool<byte>.Shared.Return(buffer); - if (!output.TryWrite(Newline)) - { - return false; - } + output.Write(Newline, 0, Newline.Length); return true; } diff --git a/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsTransport.cs b/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsTransport.cs index a0d9b8fc998..a2007c6dfc9 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsTransport.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsTransport.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO.Pipelines; -using System.IO.Pipelines.Text.Primitives; -using System.Text; +using System.IO; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Channels; @@ -44,17 +42,15 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Transports await context.Response.WriteAsync(":\r\n"); await context.Response.Body.FlushAsync(); - var pipe = context.Response.Body.AsPipelineWriter(); - var output = new PipelineTextOutput(pipe, TextEncoder.Utf8); // We don't need the Encoder, but it's harmless to set. - try { + var ms = new MemoryStream(); while (await _application.WaitToReadAsync(token)) { while (_application.TryRead(out var buffer)) { _logger.SSEWritingMessage(_connectionId, buffer.Length); - if (!ServerSentEventsMessageFormatter.TryWriteMessage(buffer, output)) + if (!ServerSentEventsMessageFormatter.TryWriteMessage(buffer, ms)) { // We ran out of space to write, even after trying to enlarge. // This should only happen in a significant lack-of-memory scenario. @@ -64,11 +60,12 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Transports // Throwing InvalidOperationException here, but it's not quite an invalid operation... throw new InvalidOperationException("Ran out of space to format messages!"); } - - await output.FlushAsync(); } } + ms.Seek(0, SeekOrigin.Begin); + await ms.CopyToAsync(context.Response.Body); + await _application.Completion; } catch (OperationCanceledException) diff --git a/src/Microsoft.AspNetCore.Sockets.Http/Microsoft.AspNetCore.Sockets.Http.csproj b/src/Microsoft.AspNetCore.Sockets.Http/Microsoft.AspNetCore.Sockets.Http.csproj index 971786f6cae..93e9f92f600 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/Microsoft.AspNetCore.Sockets.Http.csproj +++ b/src/Microsoft.AspNetCore.Sockets.Http/Microsoft.AspNetCore.Sockets.Http.csproj @@ -11,10 +11,6 @@ <EnableApiCheck>false</EnableApiCheck> </PropertyGroup> - <ItemGroup> - <Compile Include="../Common/IOutputExtensions.cs" Link="IOutputExtensions.cs" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="..\Microsoft.AspNetCore.Sockets\Microsoft.AspNetCore.Sockets.csproj" /> <ProjectReference Include="..\Microsoft.AspNetCore.Sockets.Common.Http\Microsoft.AspNetCore.Sockets.Common.Http.csproj" /> @@ -24,7 +20,8 @@ <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="$(AspNetCoreVersion)" /> <PackageReference Include="Microsoft.Extensions.SecurityHelper.Sources" Version="$(AspNetCoreVersion)" PrivateAssets="All" /> <PackageReference Include="System.Threading.Tasks.Channels" Version="$(CoreFxLabsVersion)" /> - <PackageReference Include="System.IO.Pipelines.Text.Primitives" Version="$(CoreFxLabsVersion)" /> + <PackageReference Include="System.Memory" Version="$(CoreFxVersion)" /> + <PackageReference Include="System.Binary" Version="$(CoreFxLabsVersion)" /> <PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" /> </ItemGroup> diff --git a/test/Common/ArrayOutput.cs b/test/Common/ArrayOutput.cs deleted file mode 100644 index 463bf205808..00000000000 --- a/test/Common/ArrayOutput.cs +++ /dev/null @@ -1,75 +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; -using System.Diagnostics; -using System.Linq; - -namespace Microsoft.AspNetCore.Sockets.Tests.Internal -{ - internal class ArrayOutput : IOutput - { - private IList<ArraySegment<byte>> _buffers = new List<ArraySegment<byte>>(); - - private int _chunkSize; - private byte[] _activeBuffer; - private int _offset; - - public Span<byte> Buffer => _activeBuffer.Slice(_offset); - - public ArrayOutput(int chunkSize) - { - _chunkSize = chunkSize; - AdvanceChunk(); - } - - public void Advance(int bytes) - { - // Determine the new location - _offset += bytes; - Debug.Assert(_offset <= _activeBuffer.Length, "How did we write more data than we had space?"); - } - - public void Enlarge(int desiredBufferLength = 0) - { - if (desiredBufferLength == 0 || _activeBuffer.Length - _offset < desiredBufferLength) - { - AdvanceChunk(); - } - } - - public byte[] ToArray() - { - var totalLength = _buffers.Sum(b => b.Count) + _offset; - - var arr = new byte[totalLength]; - - int offset = 0; - foreach (var buffer in _buffers) - { - System.Buffer.BlockCopy(buffer.Array, 0, arr, offset, buffer.Count); - offset += buffer.Count; - } - - if (_offset > 0) - { - System.Buffer.BlockCopy(_activeBuffer, 0, arr, offset, _offset); - } - - return arr; - } - - private void AdvanceChunk() - { - if (_activeBuffer != null) - { - _buffers.Add(new ArraySegment<byte>(_activeBuffer, 0, _offset)); - } - - _activeBuffer = new byte[_chunkSize]; - _offset = 0; - } - } -} diff --git a/test/Common/ByteArrayExtensions.cs b/test/Common/ByteArrayExtensions.cs deleted file mode 100644 index 9b02403b271..00000000000 --- a/test/Common/ByteArrayExtensions.cs +++ /dev/null @@ -1,47 +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.Buffers; -using System.Collections.Generic; - -namespace System -{ - internal static class ByteArrayExtensions - { - public static ReadOnlyBytes ToChunkedReadOnlyBytes(this byte[] data, int chunkSize) - { - if (chunkSize == 0) - { - return new ReadOnlyBytes(data); - } - - var chunks = new List<byte[]>(); - for (var i = 0; i < data.Length; i += chunkSize) - { - var thisChunkSize = Math.Min(chunkSize, data.Length - i); - var chunk = new byte[thisChunkSize]; - for (var j = 0; j < thisChunkSize; j++) - { - chunk[j] = data[i + j]; - } - chunks.Add(chunk); - } - - chunks.Reverse(); - - ReadOnlyBytes? bytes = null; - foreach (var chunk in chunks) - { - if (bytes == null) - { - bytes = new ReadOnlyBytes(chunk); - } - else - { - bytes = new ReadOnlyBytes(chunk, bytes); - } - } - return bytes.Value; - } - } -} diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs index 7b97e55a18d..cf15f6688c5 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Internal.Protocol; @@ -191,7 +192,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests throw new InvalidOperationException("No Parsed Message provided"); } - public bool TryWriteMessage(HubMessage message, IOutput output) + public bool TryWriteMessage(HubMessage message, Stream output) { WriteCalls += 1; 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 c564fcbb8aa..71be053e5d3 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 @@ -9,7 +9,6 @@ <ItemGroup> <Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" /> - <Compile Include="..\Common\ArrayOutput.cs" Link="ArrayOutput.cs" /> <Compile Include="..\Common\ChannelExtensions.cs" Link="ChannelExtensions.cs" /> </ItemGroup> diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs index 5e5104ced9e..a2ab964f01d 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs @@ -2,15 +2,14 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Channels; -using Microsoft.AspNetCore.Sockets; using Microsoft.AspNetCore.Sockets.Client; using Microsoft.AspNetCore.Sockets.Internal.Formatters; -using Microsoft.AspNetCore.Sockets.Tests.Internal; using Newtonsoft.Json; using Xunit; @@ -88,7 +87,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests private byte[] FormatMessageToArray(byte[] message) { - var output = new ArrayOutput(1024); + var output = new MemoryStream(); Assert.True(TextMessageFormatter.TryWriteMessage(message, output)); return output.ToArray(); } 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 e26aad5d855..d36d1b16d66 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.Sockets.Internal.Formatters; -using Microsoft.AspNetCore.Sockets.Tests.Internal; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Xunit; @@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [Theory] [MemberData(nameof(ProtocolTestData))] - public async Task WriteMessage(HubMessage message, bool camelCase, NullValueHandling nullValueHandling, string expectedOutput) + public void WriteMessage(HubMessage message, bool camelCase, NullValueHandling nullValueHandling, string expectedOutput) { expectedOutput = Frame(expectedOutput); @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol }; var protocol = new JsonHubProtocol(jsonSerializer); - var encoded = await protocol.WriteToArrayAsync(message); + var encoded = protocol.WriteToArray(message); var json = Encoding.UTF8.GetString(encoded); Assert.Equal(expectedOutput, json); @@ -142,7 +142,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol private static byte[] FormatMessageToArray(byte[] message) { - var output = new ArrayOutput(1024); + var output = new MemoryStream(); Assert.True(TextMessageFormatter.TryWriteMessage(message, output)); return output.ToArray(); } diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Microsoft.AspNetCore.SignalR.Common.Tests.csproj b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Microsoft.AspNetCore.SignalR.Common.Tests.csproj index c9fe4062b99..7518cdb42cd 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Microsoft.AspNetCore.SignalR.Common.Tests.csproj +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Microsoft.AspNetCore.SignalR.Common.Tests.csproj @@ -7,10 +7,6 @@ <TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netcoreapp2.0</TargetFrameworks> </PropertyGroup> - <ItemGroup> - <Compile Include="..\Common\ArrayOutput.cs" Link="ArrayOutput.cs" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="..\..\src\Microsoft.AspNetCore.SignalR.Common\Microsoft.AspNetCore.SignalR.Common.csproj" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" /> diff --git a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs index 40443b692a2..20331dea063 100644 --- a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs +++ b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs @@ -1,8 +1,8 @@ using System; using System.Buffers; +using System.IO; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Sockets.Internal.Formatters; -using Microsoft.AspNetCore.Sockets.Tests.Internal; namespace Microsoft.AspNetCore.SignalR.Microbenchmarks { @@ -12,8 +12,8 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks private static readonly Random Random = new Random(); private readonly TextMessageParser _textMessageParser = new TextMessageParser(); private readonly BinaryMessageParser _binaryMessageParser = new BinaryMessageParser(); - private ReadOnlyBytes _binaryInput; - private ReadOnlyBytes _textInput; + private ReadOnlyBuffer<byte> _binaryInput; + private ReadOnlyBuffer<byte> _textInput; [Params(32, 64)] public int ChunkSize { get; set; } @@ -26,30 +26,30 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks { var buffer = new byte[MessageLength]; Random.NextBytes(buffer); - var output = new ArrayOutput(MessageLength + 32); + var output = new MemoryStream(); if (!BinaryMessageFormatter.TryWriteMessage(buffer, output)) { throw new InvalidOperationException("Failed to format message"); } - _binaryInput = output.ToArray().ToChunkedReadOnlyBytes(ChunkSize); + _binaryInput = output.ToArray(); buffer = new byte[MessageLength]; Random.NextBytes(buffer); - output = new ArrayOutput(MessageLength + 32); + output = new MemoryStream(); if (!TextMessageFormatter.TryWriteMessage(buffer, output)) { throw new InvalidOperationException("Failed to format message"); } - _textInput = output.ToArray().ToChunkedReadOnlyBytes(ChunkSize); + _textInput = output.ToArray(); } [Benchmark] public void SingleBinaryMessage() { - var reader = new BytesReader(_binaryInput); - if (!_binaryMessageParser.TryParseMessage(ref reader, out _)) + var buffer = _binaryInput.Span; + if (!_binaryMessageParser.TryParseMessage(ref buffer, out _)) { throw new InvalidOperationException("Failed to parse"); } @@ -58,8 +58,8 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks [Benchmark] public void SingleTextMessage() { - var reader = new BytesReader(_textInput); - if (!_textMessageParser.TryParseMessage(ref reader, out _)) + var buffer = _textInput.Span; + if (!_textMessageParser.TryParseMessage(ref buffer, out _)) { throw new InvalidOperationException("Failed to parse"); } diff --git a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj index af3aa9a7b21..f838587df27 100644 --- a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj +++ b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj @@ -7,11 +7,6 @@ <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks> </PropertyGroup> - <ItemGroup> - <Compile Include="..\Common\ByteArrayExtensions.cs" Link="ByteArrayExtensions.cs" /> - <Compile Include="..\Common\ArrayOutput.cs" Link="ArrayOutput.cs" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="..\..\src\Microsoft.AspNetCore.SignalR.Common\Microsoft.AspNetCore.SignalR.Common.csproj" /> <PackageReference Include="BenchmarkDotNet" Version="0.10.3" /> diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageFormatterTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageFormatterTests.cs index 595cc5641fd..ef47d6e9bff 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageFormatterTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageFormatterTests.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.IO; using System.Text; using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Xunit; @@ -28,7 +29,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters Encoding.UTF8.GetBytes("Hello,\r\nWorld!") }; - var output = new ArrayOutput(chunkSize: 8); // Use small chunks to test Advance/Enlarge and partial payload writing + var output = new MemoryStream(); // Use small chunks to test Advance/Enlarge and partial payload writing foreach (var message in messages) { Assert.True(BinaryMessageFormatter.TryWriteMessage(message, output)); @@ -46,11 +47,11 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters [InlineData(0, 256, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })] public void WriteBinaryMessage(int offset, int chunkSize, byte[] encoded, byte[] payload) { - var output = new ArrayOutput(chunkSize); + var output = new MemoryStream(); if (offset > 0) { - output.Advance(offset); + output.Seek(offset, SeekOrigin.Begin); } Assert.True(BinaryMessageFormatter.TryWriteMessage(payload, output)); @@ -66,11 +67,11 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters public void WriteTextMessage(int offset, int chunkSize, byte[] encoded, string payload) { var message = Encoding.UTF8.GetBytes(payload); - var output = new ArrayOutput(chunkSize); + var output = new MemoryStream(); if (offset > 0) { - output.Advance(offset); + output.Seek(offset, SeekOrigin.Begin); } Assert.True(BinaryMessageFormatter.TryWriteMessage(message, output)); diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageParserTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageParserTests.cs index 9ae8d374bd6..d1ba3651ad5 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageParserTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageParserTests.cs @@ -19,9 +19,9 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters public void ReadMessage(byte[] encoded, string payload) { var parser = new BinaryMessageParser(); - var reader = new BytesReader(encoded); - Assert.True(parser.TryParseMessage(ref reader, out var message)); - Assert.Equal(reader.Index, encoded.Length); + ReadOnlySpan<byte> span = encoded.AsSpan(); + Assert.True(parser.TryParseMessage(ref span, out var message)); + Assert.Equal(0, span.Length); Assert.Equal(Encoding.UTF8.GetBytes(payload), message.ToArray()); } @@ -32,18 +32,14 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters public void ReadBinaryMessage(byte[] encoded, byte[] payload) { var parser = new BinaryMessageParser(); - var reader = new BytesReader(encoded); - Assert.True(parser.TryParseMessage(ref reader, out var message)); - Assert.Equal(reader.Index, encoded.Length); + ReadOnlySpan<byte> span = encoded.AsSpan(); + Assert.True(parser.TryParseMessage(ref span, out var message)); + Assert.Equal(0, span.Length); Assert.Equal(payload, message.ToArray()); } - [Theory] - [InlineData(0)] // No chunking - [InlineData(4)] - [InlineData(8)] - [InlineData(256)] - public void ReadMultipleMessages(int chunkSize) + [Fact] + public void ReadMultipleMessages() { var encoded = new byte[] { @@ -53,16 +49,15 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters /* body: */ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2C, 0x0D, 0x0A, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, }; var parser = new BinaryMessageParser(); - var buffer = encoded.ToChunkedReadOnlyBytes(chunkSize); - var reader = new BytesReader(buffer); + ReadOnlySpan<byte> span = encoded.AsSpan(); var messages = new List<byte[]>(); - while (parser.TryParseMessage(ref reader, out var message)) + while (parser.TryParseMessage(ref span, out var message)) { messages.Add(message.ToArray()); } - Assert.Equal(encoded.Length, reader.Index); + Assert.Equal(0, span.Length); Assert.Equal(2, messages.Count); Assert.Equal(new byte[0], messages[0]); @@ -75,9 +70,9 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters public void ReadIncompleteMessages(byte[] encoded) { var parser = new BinaryMessageParser(); - var reader = new BytesReader(new ReadOnlyBytes(encoded)); - Assert.False(parser.TryParseMessage(ref reader, out var message)); - Assert.Equal(encoded.Length, reader.Index); + ReadOnlySpan<byte> span = encoded.AsSpan(); + Assert.False(parser.TryParseMessage(ref span, out var message)); + Assert.Equal(0, span.Length); } } } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageFormatterTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageFormatterTests.cs index 10677c24074..e3c78c89409 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageFormatterTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageFormatterTests.cs @@ -2,7 +2,7 @@ // 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.IO; using System.Text; using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Xunit; @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters Encoding.UTF8.GetBytes("Hello,\r\nWorld!") }; - var output = new ArrayOutput(chunkSize: 8); // Use small chunks to test Advance/Enlarge and partial payload writing + var output = new MemoryStream(); foreach (var message in messages) { Assert.True(TextMessageFormatter.TryWriteMessage(message, output)); @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters public void WriteMessage(int chunkSize, string encoded, string payload) { var message = Encoding.UTF8.GetBytes(payload); - var output = new ArrayOutput(chunkSize); // Use small chunks to test Advance/Enlarge and partial payload writing + var output = new MemoryStream(); Assert.True(TextMessageFormatter.TryWriteMessage(message, output)); diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageParserTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageParserTests.cs index 6397a7ee17e..ef2caaa8297 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageParserTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageParserTests.cs @@ -21,35 +21,28 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters { var parser = new TextMessageParser(); var buffer = Encoding.UTF8.GetBytes(encoded); - var reader = new BytesReader(buffer.ToChunkedReadOnlyBytes(chunkSize)); + ReadOnlySpan<byte> span = buffer.AsSpan(); - Assert.True(parser.TryParseMessage(ref reader, out var message)); - Assert.Equal(reader.Index, buffer.Length); + Assert.True(parser.TryParseMessage(ref span, out var message)); + Assert.Equal(0, span.Length); Assert.Equal(Encoding.UTF8.GetBytes(payload), message.ToArray()); } - [Theory] - [InlineData(0)] // Not chunked - [InlineData(4)] - [InlineData(8)] - public void ReadMultipleMessages(int chunkSize) + [Fact] + public void ReadMultipleMessages() { const string encoded = "0:;14:Hello,\r\nWorld!;"; var parser = new TextMessageParser(); var data = Encoding.UTF8.GetBytes(encoded); - var buffer = chunkSize > 0 ? - data.ToChunkedReadOnlyBytes(chunkSize) : - new ReadOnlyBytes(data); - - var reader = new BytesReader(buffer); + ReadOnlySpan<byte> span = data.AsSpan(); var messages = new List<byte[]>(); - while (parser.TryParseMessage(ref reader, out var message)) + while (parser.TryParseMessage(ref span, out var message)) { messages.Add(message.ToArray()); } - Assert.Equal(reader.Index, Encoding.UTF8.GetByteCount(encoded)); + Assert.Equal(0, span.Length); Assert.Equal(2, messages.Count); Assert.Equal(new byte[0], messages[0]); @@ -68,8 +61,8 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters { var parser = new TextMessageParser(); var buffer = Encoding.UTF8.GetBytes(encoded); - var reader = new BytesReader(buffer); - Assert.False(parser.TryParseMessage(ref reader, out _)); + ReadOnlySpan<byte> span = buffer.AsSpan(); + Assert.False(parser.TryParseMessage(ref span, out _)); } [Theory] @@ -82,8 +75,11 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters { var parser = new TextMessageParser(); var buffer = Encoding.UTF8.GetBytes(encoded); - var reader = new BytesReader(buffer); - var ex = Assert.Throws<FormatException>(() => parser.TryParseMessage(ref reader, out _)); + var ex = Assert.Throws<FormatException>(() => + { + ReadOnlySpan<byte> span = buffer.AsSpan(); + parser.TryParseMessage(ref span, out _); + }); Assert.Equal(expectedMessage, ex.Message); } @@ -96,8 +92,12 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters // We need to include the ':' so that var buffer = new byte[] { 0x48, 0x65, 0x80, 0x6C, 0x6F, (byte)':' }; var reader = new BytesReader(buffer); - var ex = Assert.Throws<FormatException>(() => parser.TryParseMessage(ref reader, out _)); - Assert.Equal("Invalid length", ex.Message); + var ex = Assert.Throws<FormatException>(() => + { + ReadOnlySpan<byte> span = buffer.AsSpan(); + parser.TryParseMessage(ref span, out _); + }); + Assert.Equal("Invalid length: 'He�lo'", ex.Message); } } } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj b/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj index 25d5d021c4b..852e041f782 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj @@ -16,8 +16,6 @@ <ItemGroup> <Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" /> - <Compile Include="..\Common\ArrayOutput.cs" Link="ArrayOutput.cs" /> - <Compile Include="..\Common\ByteArrayExtensions.cs" Link="ByteArrayExtensions.cs" /> </ItemGroup> <ItemGroup> diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/TestClient.cs b/test/Microsoft.AspNetCore.SignalR.Tests/TestClient.cs index 7e10ba23982..c21c90cdb23 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/TestClient.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/TestClient.cs @@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests public async Task<string> SendInvocationAsync(string methodName, params object[] args) { var invocationId = GetInvocationId(); - var payload = await _protocol.WriteToArrayAsync(new InvocationMessage(invocationId, nonBlocking: false, target: methodName, arguments: args)); + var payload = _protocol.WriteToArray(new InvocationMessage(invocationId, nonBlocking: false, target: methodName, arguments: args)); await Application.Output.WriteAsync(payload); diff --git a/test/Microsoft.AspNetCore.Sockets.Tests/Microsoft.AspNetCore.Sockets.Tests.csproj b/test/Microsoft.AspNetCore.Sockets.Tests/Microsoft.AspNetCore.Sockets.Tests.csproj index e17e1972478..4e0dcb7d967 100644 --- a/test/Microsoft.AspNetCore.Sockets.Tests/Microsoft.AspNetCore.Sockets.Tests.csproj +++ b/test/Microsoft.AspNetCore.Sockets.Tests/Microsoft.AspNetCore.Sockets.Tests.csproj @@ -8,7 +8,6 @@ </PropertyGroup> <ItemGroup> - <Compile Include="..\Common\ArrayOutput.cs" Link="ArrayOutput.cs" /> <Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" /> </ItemGroup> diff --git a/test/Microsoft.AspNetCore.Sockets.Tests/ServerSentEventsMessageFormatterTests.cs b/test/Microsoft.AspNetCore.Sockets.Tests/ServerSentEventsMessageFormatterTests.cs index d5c7d739cc7..44c7939720d 100644 --- a/test/Microsoft.AspNetCore.Sockets.Tests/ServerSentEventsMessageFormatterTests.cs +++ b/test/Microsoft.AspNetCore.Sockets.Tests/ServerSentEventsMessageFormatterTests.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.IO; using System.Text; using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Xunit; @@ -19,7 +20,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters [InlineData("data: Hello\r\ndata: \r\n\r\n", "Hello\r\n")] public void WriteTextMessage(string encoded, string payload) { - var output = new ArrayOutput(chunkSize: 8); // Use small chunks to test Advance/Enlarge and partial payload writing + var output = new MemoryStream(); Assert.True(ServerSentEventsMessageFormatter.TryWriteMessage(Encoding.UTF8.GetBytes(payload), output)); Assert.Equal(encoded, Encoding.UTF8.GetString(output.ToArray())); -- GitLab