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.
This commit is contained in:
parent
d28c1c81c0
commit
41f54d001b
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,13 +123,105 @@ 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
|
||||
{
|
||||
public ParsePhase Phase;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)" />
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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<48>lo'", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\ArrayOutput.cs" Link="ArrayOutput.cs" />
|
||||
<Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
Loading…
Reference in New Issue