Serialize NegotiateResponse with IBufferWriter (#1881)
This commit is contained in:
parent
f632330d7f
commit
acc0b7ad0d
|
|
@ -11,6 +11,7 @@ using BenchmarkDotNet.Attributes;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.SignalR.Core;
|
using Microsoft.AspNetCore.SignalR.Core;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.SignalR.Internal;
|
||||||
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared;
|
using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ using BenchmarkDotNet.Attributes;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared;
|
using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared;
|
using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
|
namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Microsoft.AspNetCore.Http.Connections.Internal;
|
||||||
|
using Microsoft.AspNetCore.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
|
||||||
|
{
|
||||||
|
public class NegotiateProtocolBenchmark
|
||||||
|
{
|
||||||
|
private NegotiationResponse _negotiateResponse;
|
||||||
|
private Stream _stream;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void GlobalSetup()
|
||||||
|
{
|
||||||
|
_negotiateResponse = new NegotiationResponse
|
||||||
|
{
|
||||||
|
ConnectionId = "d100338e-8c01-4281-92c2-9a967fdeebcb",
|
||||||
|
AvailableTransports = new List<AvailableTransport>
|
||||||
|
{
|
||||||
|
new AvailableTransport
|
||||||
|
{
|
||||||
|
Transport = "WebSockets",
|
||||||
|
TransferFormats = new List<string>
|
||||||
|
{
|
||||||
|
"Text",
|
||||||
|
"Binary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_stream = Stream.Null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public Task WriteResponse_MemoryBufferWriter()
|
||||||
|
{
|
||||||
|
var writer = new MemoryBufferWriter();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
NegotiateProtocol.WriteResponse(_negotiateResponse, writer);
|
||||||
|
return writer.CopyToAsync(_stream);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
writer.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Internal
|
||||||
|
{
|
||||||
|
internal sealed class MemoryBufferWriter : IBufferWriter<byte>
|
||||||
|
{
|
||||||
|
[ThreadStatic]
|
||||||
|
private static MemoryBufferWriter _cachedInstance;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
private bool _inUse;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private readonly int _segmentSize;
|
||||||
|
private int _bytesWritten;
|
||||||
|
|
||||||
|
private List<byte[]> _fullSegments;
|
||||||
|
private byte[] _currentSegment;
|
||||||
|
private int _position;
|
||||||
|
|
||||||
|
public MemoryBufferWriter(int segmentSize = 2048)
|
||||||
|
{
|
||||||
|
_segmentSize = segmentSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Length => _bytesWritten;
|
||||||
|
|
||||||
|
public static MemoryBufferWriter Get()
|
||||||
|
{
|
||||||
|
var writer = _cachedInstance;
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
writer = new MemoryBufferWriter();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken off the thread static
|
||||||
|
_cachedInstance = null;
|
||||||
|
#if DEBUG
|
||||||
|
if (writer._inUse)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The reader wasn't returned!");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer._inUse = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Return(MemoryBufferWriter writer)
|
||||||
|
{
|
||||||
|
_cachedInstance = writer;
|
||||||
|
#if DEBUG
|
||||||
|
writer._inUse = false;
|
||||||
|
#endif
|
||||||
|
writer.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
if (_fullSegments != null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _fullSegments.Count; i++)
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(_fullSegments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
_fullSegments.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentSegment != null)
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(_currentSegment);
|
||||||
|
_currentSegment = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_bytesWritten = 0;
|
||||||
|
_position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Advance(int count)
|
||||||
|
{
|
||||||
|
_bytesWritten += count;
|
||||||
|
_position += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Memory<byte> GetMemory(int sizeHint = 0)
|
||||||
|
{
|
||||||
|
// TODO: Use sizeHint
|
||||||
|
if (_currentSegment == null)
|
||||||
|
{
|
||||||
|
_currentSegment = ArrayPool<byte>.Shared.Rent(_segmentSize);
|
||||||
|
_position = 0;
|
||||||
|
}
|
||||||
|
else if (_position == _segmentSize)
|
||||||
|
{
|
||||||
|
if (_fullSegments == null)
|
||||||
|
{
|
||||||
|
_fullSegments = new List<byte[]>();
|
||||||
|
}
|
||||||
|
_fullSegments.Add(_currentSegment);
|
||||||
|
_currentSegment = ArrayPool<byte>.Shared.Rent(_segmentSize);
|
||||||
|
_position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _currentSegment.AsMemory(_position, _currentSegment.Length - _position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Span<byte> GetSpan(int sizeHint = 0)
|
||||||
|
{
|
||||||
|
return GetMemory(sizeHint).Span;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task CopyToAsync(Stream stream)
|
||||||
|
{
|
||||||
|
if (_fullSegments == null)
|
||||||
|
{
|
||||||
|
// There is only one segment so write without async
|
||||||
|
return stream.WriteAsync(_currentSegment, 0, _position);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CopyToSlowAsync(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CopyToSlowAsync(Stream stream)
|
||||||
|
{
|
||||||
|
if (_fullSegments != null)
|
||||||
|
{
|
||||||
|
// Copy full segments
|
||||||
|
for (var i = 0; i < _fullSegments.Count - 1; i++)
|
||||||
|
{
|
||||||
|
await stream.WriteAsync(_fullSegments[i], 0, _segmentSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await stream.WriteAsync(_currentSegment, 0, _position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ToArray()
|
||||||
|
{
|
||||||
|
if (_currentSegment == null)
|
||||||
|
{
|
||||||
|
return Array.Empty<byte>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new byte[_bytesWritten];
|
||||||
|
|
||||||
|
var totalWritten = 0;
|
||||||
|
|
||||||
|
if (_fullSegments != null)
|
||||||
|
{
|
||||||
|
// Copy full segments
|
||||||
|
for (var i = 0; i < _fullSegments.Count; i++)
|
||||||
|
{
|
||||||
|
_fullSegments[i].CopyTo(result, totalWritten);
|
||||||
|
|
||||||
|
totalWritten += _segmentSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy current incomplete segment
|
||||||
|
_currentSegment.AsSpan(0, _position).CopyTo(result.AsSpan(totalWritten));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ using System.Text;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Internal
|
namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
{
|
{
|
||||||
public class Utf8BufferTextReader : TextReader
|
internal sealed class Utf8BufferTextReader : TextReader
|
||||||
{
|
{
|
||||||
private readonly Decoder _decoder;
|
private readonly Decoder _decoder;
|
||||||
private ReadOnlySequence<byte> _utf8Buffer;
|
private ReadOnlySequence<byte> _utf8Buffer;
|
||||||
|
|
@ -9,9 +9,9 @@ using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Internal
|
namespace Microsoft.AspNetCore.Internal
|
||||||
{
|
{
|
||||||
public sealed class Utf8BufferTextWriter : TextWriter
|
internal sealed class Utf8BufferTextWriter : TextWriter
|
||||||
{
|
{
|
||||||
private static readonly UTF8Encoding _utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
private static readonly UTF8Encoding _utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||||
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -12,44 +13,50 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
{
|
{
|
||||||
public static class NegotiateProtocol
|
public static class NegotiateProtocol
|
||||||
{
|
{
|
||||||
private static readonly UTF8Encoding _utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
|
||||||
|
|
||||||
private const string ConnectionIdPropertyName = "connectionId";
|
private const string ConnectionIdPropertyName = "connectionId";
|
||||||
private const string AvailableTransportsPropertyName = "availableTransports";
|
private const string AvailableTransportsPropertyName = "availableTransports";
|
||||||
private const string TransportPropertyName = "transport";
|
private const string TransportPropertyName = "transport";
|
||||||
private const string TransferFormatsPropertyName = "transferFormats";
|
private const string TransferFormatsPropertyName = "transferFormats";
|
||||||
|
|
||||||
public static void WriteResponse(NegotiationResponse response, Stream output)
|
public static void WriteResponse(NegotiationResponse response, IBufferWriter<byte> output)
|
||||||
{
|
{
|
||||||
using (var jsonWriter = JsonUtils.CreateJsonTextWriter(new StreamWriter(output, _utf8NoBom, 1024, leaveOpen: true)))
|
var textWriter = Utf8BufferTextWriter.Get(output);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
jsonWriter.WriteStartObject();
|
using (var jsonWriter = JsonUtils.CreateJsonTextWriter(textWriter))
|
||||||
jsonWriter.WritePropertyName(ConnectionIdPropertyName);
|
|
||||||
jsonWriter.WriteValue(response.ConnectionId);
|
|
||||||
jsonWriter.WritePropertyName(AvailableTransportsPropertyName);
|
|
||||||
jsonWriter.WriteStartArray();
|
|
||||||
|
|
||||||
foreach (var availableTransport in response.AvailableTransports)
|
|
||||||
{
|
{
|
||||||
jsonWriter.WriteStartObject();
|
jsonWriter.WriteStartObject();
|
||||||
jsonWriter.WritePropertyName(TransportPropertyName);
|
jsonWriter.WritePropertyName(ConnectionIdPropertyName);
|
||||||
jsonWriter.WriteValue(availableTransport.Transport);
|
jsonWriter.WriteValue(response.ConnectionId);
|
||||||
jsonWriter.WritePropertyName(TransferFormatsPropertyName);
|
jsonWriter.WritePropertyName(AvailableTransportsPropertyName);
|
||||||
jsonWriter.WriteStartArray();
|
jsonWriter.WriteStartArray();
|
||||||
|
|
||||||
foreach (var transferFormat in availableTransport.TransferFormats)
|
foreach (var availableTransport in response.AvailableTransports)
|
||||||
{
|
{
|
||||||
jsonWriter.WriteValue(transferFormat);
|
jsonWriter.WriteStartObject();
|
||||||
|
jsonWriter.WritePropertyName(TransportPropertyName);
|
||||||
|
jsonWriter.WriteValue(availableTransport.Transport);
|
||||||
|
jsonWriter.WritePropertyName(TransferFormatsPropertyName);
|
||||||
|
jsonWriter.WriteStartArray();
|
||||||
|
|
||||||
|
foreach (var transferFormat in availableTransport.TransferFormats)
|
||||||
|
{
|
||||||
|
jsonWriter.WriteValue(transferFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonWriter.WriteEndArray();
|
||||||
|
jsonWriter.WriteEndObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonWriter.WriteEndArray();
|
jsonWriter.WriteEndArray();
|
||||||
jsonWriter.WriteEndObject();
|
jsonWriter.WriteEndObject();
|
||||||
|
|
||||||
|
jsonWriter.Flush();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
jsonWriter.WriteEndArray();
|
finally
|
||||||
jsonWriter.WriteEndObject();
|
{
|
||||||
|
Utf8BufferTextWriter.Return(textWriter);
|
||||||
jsonWriter.Flush();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@
|
||||||
<Description>Common primitives for ASP.NET Connection Handlers and clients</Description>
|
<Description>Common primitives for ASP.NET Connection Handlers and clients</Description>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<RootNamespace>Microsoft.AspNetCore.Http.Connections</RootNamespace>
|
<RootNamespace>Microsoft.AspNetCore.Http.Connections</RootNamespace>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\Common\JsonUtils.cs" Link="Internal\JsonUtils.cs" />
|
<Compile Include="..\Common\JsonUtils.cs" Link="Internal\JsonUtils.cs" />
|
||||||
|
<Compile Include="..\Common\Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="$(MicrosoftAspNetCoreConnectionsAbstractionsPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="$(MicrosoftAspNetCoreConnectionsAbstractionsPackageVersion)" />
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
@ -14,6 +15,7 @@ using Microsoft.AspNetCore.Http.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Http.Connections.Internal;
|
using Microsoft.AspNetCore.Http.Connections.Internal;
|
||||||
using Microsoft.AspNetCore.Http.Connections.Internal.Transports;
|
using Microsoft.AspNetCore.Http.Connections.Internal.Transports;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
|
@ -373,7 +375,7 @@ namespace Microsoft.AspNetCore.Http.Connections
|
||||||
await connectionDelegate(connection);
|
await connectionDelegate(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task ProcessNegotiate(HttpContext context, HttpConnectionOptions options, ConnectionLogScope logScope)
|
private async Task ProcessNegotiate(HttpContext context, HttpConnectionOptions options, ConnectionLogScope logScope)
|
||||||
{
|
{
|
||||||
context.Response.ContentType = "application/json";
|
context.Response.ContentType = "application/json";
|
||||||
|
|
||||||
|
|
@ -384,17 +386,27 @@ namespace Microsoft.AspNetCore.Http.Connections
|
||||||
// Connection ID metadata set.
|
// Connection ID metadata set.
|
||||||
logScope.ConnectionId = connection.ConnectionId;
|
logScope.ConnectionId = connection.ConnectionId;
|
||||||
|
|
||||||
// Get the bytes for the connection id
|
// Don't use thread static instance here because writer is used with async
|
||||||
var negotiateResponseBuffer = GetNegotiatePayload(connection.ConnectionId, context, options);
|
var writer = new MemoryBufferWriter();
|
||||||
|
|
||||||
Log.NegotiationRequest(_logger);
|
try
|
||||||
|
{
|
||||||
|
// Get the bytes for the connection id
|
||||||
|
WriteNegotiatePayload(writer, connection.ConnectionId, context, options);
|
||||||
|
|
||||||
// Write it out to the response with the right content length
|
Log.NegotiationRequest(_logger);
|
||||||
context.Response.ContentLength = negotiateResponseBuffer.Length;
|
|
||||||
return context.Response.Body.WriteAsync(negotiateResponseBuffer, 0, negotiateResponseBuffer.Length);
|
// Write it out to the response with the right content length
|
||||||
|
context.Response.ContentLength = writer.Length;
|
||||||
|
await writer.CopyToAsync(context.Response.Body);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
writer.Reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] GetNegotiatePayload(string connectionId, HttpContext context, HttpConnectionOptions options)
|
private static void WriteNegotiatePayload(IBufferWriter<byte> writer, string connectionId, HttpContext context, HttpConnectionOptions options)
|
||||||
{
|
{
|
||||||
var response = new NegotiationResponse();
|
var response = new NegotiationResponse();
|
||||||
response.ConnectionId = connectionId;
|
response.ConnectionId = connectionId;
|
||||||
|
|
@ -415,10 +427,7 @@ namespace Microsoft.AspNetCore.Http.Connections
|
||||||
response.AvailableTransports.Add(_longPollingAvailableTransport);
|
response.AvailableTransports.Add(_longPollingAvailableTransport);
|
||||||
}
|
}
|
||||||
|
|
||||||
var ms = new MemoryStream();
|
NegotiateProtocol.WriteResponse(response, writer);
|
||||||
NegotiateProtocol.WriteResponse(response, ms);
|
|
||||||
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ServerHasWebSockets(IFeatureCollection features)
|
private static bool ServerHasWebSockets(IFeatureCollection features)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="..\Common\MemoryBufferWriter.cs" Link="MemoryBufferWriter.cs" />
|
||||||
<Compile Include="..\Common\PipeWriterStream.cs" Link="PipeWriterStream.cs" />
|
<Compile Include="..\Common\PipeWriterStream.cs" Link="PipeWriterStream.cs" />
|
||||||
<Compile Include="..\Common\WebSocketExtensions.cs" Link="WebSocketExtensions.cs" />
|
<Compile Include="..\Common\WebSocketExtensions.cs" Link="WebSocketExtensions.cs" />
|
||||||
<Compile Include="..\Common\StreamExtensions.cs" Link="StreamExtensions.cs" />
|
<Compile Include="..\Common\StreamExtensions.cs" Link="StreamExtensions.cs" />
|
||||||
|
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Internal
|
|
||||||
{
|
|
||||||
public sealed class MemoryBufferWriter : IBufferWriter<byte>
|
|
||||||
{
|
|
||||||
[ThreadStatic]
|
|
||||||
private static MemoryBufferWriter _cachedInstance;
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
private bool _inUse;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private readonly int _segmentSize;
|
|
||||||
private int _bytesWritten;
|
|
||||||
|
|
||||||
private List<byte[]> _segments;
|
|
||||||
private int _position;
|
|
||||||
|
|
||||||
private MemoryBufferWriter(int segmentSize = 2048)
|
|
||||||
{
|
|
||||||
_segmentSize = segmentSize;
|
|
||||||
|
|
||||||
_segments = new List<byte[]>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MemoryBufferWriter Get()
|
|
||||||
{
|
|
||||||
var writer = _cachedInstance;
|
|
||||||
if (writer == null)
|
|
||||||
{
|
|
||||||
writer = new MemoryBufferWriter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Taken off the thread static
|
|
||||||
_cachedInstance = null;
|
|
||||||
#if DEBUG
|
|
||||||
if (writer._inUse)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("The reader wasn't returned!");
|
|
||||||
}
|
|
||||||
|
|
||||||
writer._inUse = true;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Return(MemoryBufferWriter writer)
|
|
||||||
{
|
|
||||||
_cachedInstance = writer;
|
|
||||||
#if DEBUG
|
|
||||||
writer._inUse = false;
|
|
||||||
#endif
|
|
||||||
for (var i = 0; i < writer._segments.Count; i++)
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(writer._segments[i]);
|
|
||||||
}
|
|
||||||
writer._segments.Clear();
|
|
||||||
writer._bytesWritten = 0;
|
|
||||||
writer._position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Memory<byte> CurrentSegment => _segments[_segments.Count - 1];
|
|
||||||
|
|
||||||
public void Advance(int count)
|
|
||||||
{
|
|
||||||
_bytesWritten += count;
|
|
||||||
_position += count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Memory<byte> GetMemory(int sizeHint = 0)
|
|
||||||
{
|
|
||||||
// TODO: Use sizeHint
|
|
||||||
|
|
||||||
if (_segments.Count == 0 || _position == _segmentSize)
|
|
||||||
{
|
|
||||||
_segments.Add(ArrayPool<byte>.Shared.Rent(_segmentSize));
|
|
||||||
_position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache property access
|
|
||||||
var currentSegment = CurrentSegment;
|
|
||||||
return currentSegment.Slice(_position, currentSegment.Length - _position);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Span<byte> GetSpan(int sizeHint = 0)
|
|
||||||
{
|
|
||||||
return GetMemory(sizeHint).Span;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] ToArray()
|
|
||||||
{
|
|
||||||
if (_segments.Count == 0)
|
|
||||||
{
|
|
||||||
return Array.Empty<byte>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = new byte[_bytesWritten];
|
|
||||||
|
|
||||||
var totalWritten = 0;
|
|
||||||
|
|
||||||
// Copy full segments
|
|
||||||
for (var i = 0; i < _segments.Count - 1; i++)
|
|
||||||
{
|
|
||||||
_segments[i].AsMemory().CopyTo(result.AsMemory(totalWritten, _segmentSize));
|
|
||||||
|
|
||||||
totalWritten += _segmentSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy current incomplete segment
|
|
||||||
CurrentSegment.Slice(0, _position).CopyTo(result.AsMemory(totalWritten, _position));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.AspNetCore.Internal;
|
using Microsoft.AspNetCore.Internal;
|
||||||
|
|
@ -16,6 +17,22 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
|
||||||
private const string ErrorPropertyName = "error";
|
private const string ErrorPropertyName = "error";
|
||||||
private const string TypePropertyName = "type";
|
private const string TypePropertyName = "type";
|
||||||
|
|
||||||
|
public static ReadOnlyMemory<byte> SuccessHandshakeData { get; }
|
||||||
|
|
||||||
|
static HandshakeProtocol()
|
||||||
|
{
|
||||||
|
var memoryBufferWriter = MemoryBufferWriter.Get();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
WriteResponseMessage(HandshakeResponseMessage.Empty, memoryBufferWriter);
|
||||||
|
SuccessHandshakeData = memoryBufferWriter.ToArray();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
MemoryBufferWriter.Return(memoryBufferWriter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void WriteRequestMessage(HandshakeRequestMessage requestMessage, IBufferWriter<byte> output)
|
public static void WriteRequestMessage(HandshakeRequestMessage requestMessage, IBufferWriter<byte> output)
|
||||||
{
|
{
|
||||||
var textWriter = Utf8BufferTextWriter.Get(output);
|
var textWriter = Utf8BufferTextWriter.Get(output);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
|
namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
|
||||||
{
|
{
|
||||||
public static class HubProtocolExtensions
|
public static class HubProtocolExtensions
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\Common\JsonUtils.cs" Link="Internal\JsonUtils.cs" />
|
<Compile Include="..\Common\JsonUtils.cs" Link="Internal\JsonUtils.cs" />
|
||||||
|
<Compile Include="..\Common\MemoryBufferWriter.cs" Link="Internal\MemoryBufferWriter.cs" />
|
||||||
|
<Compile Include="..\Common\Utf8BufferTextReader.cs" Link="Internal\Utf8BufferTextReader.cs" />
|
||||||
|
<Compile Include="..\Common\Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Core;
|
using Microsoft.AspNetCore.SignalR.Core;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.SignalR.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
|
|
@ -24,7 +25,6 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
public class HubConnectionContext
|
public class HubConnectionContext
|
||||||
{
|
{
|
||||||
private static readonly Action<object> _abortedCallback = AbortConnection;
|
private static readonly Action<object> _abortedCallback = AbortConnection;
|
||||||
private static readonly byte[] _successHandshakeResponseData;
|
|
||||||
|
|
||||||
private readonly ConnectionContext _connectionContext;
|
private readonly ConnectionContext _connectionContext;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
@ -36,20 +36,6 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
private long _lastSendTimestamp = Stopwatch.GetTimestamp();
|
private long _lastSendTimestamp = Stopwatch.GetTimestamp();
|
||||||
private byte[] _cachedPingMessage;
|
private byte[] _cachedPingMessage;
|
||||||
|
|
||||||
static HubConnectionContext()
|
|
||||||
{
|
|
||||||
var memoryBufferWriter = MemoryBufferWriter.Get();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HandshakeProtocol.WriteResponseMessage(HandshakeResponseMessage.Empty, memoryBufferWriter);
|
|
||||||
_successHandshakeResponseData = memoryBufferWriter.ToArray();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
MemoryBufferWriter.Return(memoryBufferWriter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public HubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory)
|
public HubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
_connectionContext = connectionContext;
|
_connectionContext = connectionContext;
|
||||||
|
|
@ -264,7 +250,7 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
if (message == HandshakeResponseMessage.Empty)
|
if (message == HandshakeResponseMessage.Empty)
|
||||||
{
|
{
|
||||||
// success response is always an empty object so send cached data
|
// success response is always an empty object so send cached data
|
||||||
_connectionContext.Transport.Output.Write(_successHandshakeResponseData);
|
_connectionContext.Transport.Output.Write(HandshakeProtocol.SuccessHandshakeData.Span);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,13 @@
|
||||||
<Description>Implements the SignalR Hub Protocol over JSON.</Description>
|
<Description>Implements the SignalR Hub Protocol over JSON.</Description>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<RootNamespace>Microsoft.AspNetCore.SignalR</RootNamespace>
|
<RootNamespace>Microsoft.AspNetCore.SignalR</RootNamespace>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\Common\JsonUtils.cs" Link="Internal\JsonUtils.cs" />
|
<Compile Include="..\Common\JsonUtils.cs" Link="Internal\JsonUtils.cs" />
|
||||||
|
<Compile Include="..\Common\Utf8BufferTextReader.cs" Link="Utf8BufferTextReader.cs" />
|
||||||
|
<Compile Include="..\Common\Utf8BufferTextWriter.cs" Link="Utf8BufferTextWriter.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\..\src\Common\MemoryBufferWriter.cs" Link="MemoryBufferWriter.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="..\xunit.runner.json" Link="xunit.runner.json">
|
<Content Include="..\xunit.runner.json" Link="xunit.runner.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
@ -75,17 +75,21 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
var s = await ReadSentTextMessageAsync();
|
var s = await ReadSentTextMessageAsync();
|
||||||
|
|
||||||
|
byte[] response;
|
||||||
|
|
||||||
var output = MemoryBufferWriter.Get();
|
var output = MemoryBufferWriter.Get();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
HandshakeProtocol.WriteResponseMessage(HandshakeResponseMessage.Empty, output);
|
HandshakeProtocol.WriteResponseMessage(HandshakeResponseMessage.Empty, output);
|
||||||
await Application.Output.WriteAsync(output.ToArray());
|
response = output.ToArray();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
MemoryBufferWriter.Return(output);
|
MemoryBufferWriter.Return(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Application.Output.WriteAsync(response);
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.SignalR.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
@ -16,7 +17,6 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
|
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
|
||||||
{
|
{
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
|
||||||
using static HubMessageHelpers;
|
using static HubMessageHelpers;
|
||||||
|
|
||||||
public class JsonHubProtocolTests
|
public class JsonHubProtocolTests
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
using MsgPack;
|
using MsgPack;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.SignalR.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal;
|
using Microsoft.AspNetCore.SignalR.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
|
|
||||||
|
|
@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
{
|
{
|
||||||
public class TestClient : ITransferFormatFeature, IConnectionHeartbeatFeature, IDisposable
|
public class TestClient : ITransferFormatFeature, IConnectionHeartbeatFeature, IDisposable
|
||||||
{
|
{
|
||||||
private object _heartbeatLock = new object();
|
private readonly object _heartbeatLock = new object();
|
||||||
private List<(Action<object> handler, object state)> _heartbeatHandlers;
|
private List<(Action<object> handler, object state)> _heartbeatHandlers;
|
||||||
|
|
||||||
private static int _id;
|
private static int _id;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue