Reuse Utf8JsonWriter (#9607)
This commit is contained in:
parent
0303c9e90b
commit
c300c8c97c
|
|
@ -12,6 +12,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" />
|
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" />
|
||||||
<Compile Include="$(SignalRSharedSourceRoot)SystemTextJsonExtensions.cs" Link="Internal\SystemTextJsonExtensions.cs" />
|
<Compile Include="$(SignalRSharedSourceRoot)SystemTextJsonExtensions.cs" Link="Internal\SystemTextJsonExtensions.cs" />
|
||||||
|
<Compile Include="$(SignalRSharedSourceRoot)ReusableUtf8JsonWriter.cs" Link="Internal\ReusableUtf8JsonWriter.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Microsoft.AspNetCore.Internal;
|
using Microsoft.AspNetCore.Internal;
|
||||||
|
|
||||||
|
|
@ -31,65 +30,73 @@ namespace Microsoft.AspNetCore.Http.Connections
|
||||||
private static ReadOnlySpan<byte> ErrorPropertyNameBytes => new byte[] { (byte)'e', (byte)'r', (byte)'r', (byte)'o', (byte)'r' };
|
private static ReadOnlySpan<byte> ErrorPropertyNameBytes => new byte[] { (byte)'e', (byte)'r', (byte)'r', (byte)'o', (byte)'r' };
|
||||||
|
|
||||||
// Used to detect ASP.NET SignalR Server connection attempt
|
// Used to detect ASP.NET SignalR Server connection attempt
|
||||||
private const string ProtocolVersionPropertyName = "ProtocolVersion";
|
|
||||||
private static ReadOnlySpan<byte> ProtocolVersionPropertyNameBytes => new byte[] { (byte)'P', (byte)'r', (byte)'o', (byte)'t', (byte)'o', (byte)'c', (byte)'o', (byte)'l', (byte)'V', (byte)'e', (byte)'r', (byte)'s', (byte)'i', (byte)'o', (byte)'n' };
|
private static ReadOnlySpan<byte> ProtocolVersionPropertyNameBytes => new byte[] { (byte)'P', (byte)'r', (byte)'o', (byte)'t', (byte)'o', (byte)'c', (byte)'o', (byte)'l', (byte)'V', (byte)'e', (byte)'r', (byte)'s', (byte)'i', (byte)'o', (byte)'n' };
|
||||||
|
|
||||||
public static void WriteResponse(NegotiationResponse response, IBufferWriter<byte> output)
|
public static void WriteResponse(NegotiationResponse response, IBufferWriter<byte> output)
|
||||||
{
|
{
|
||||||
using var writer = new Utf8JsonWriter(output, new JsonWriterOptions() { SkipValidation = true });
|
var reusableWriter = ReusableUtf8JsonWriter.Get(output);
|
||||||
writer.WriteStartObject();
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(response.Url))
|
try
|
||||||
{
|
{
|
||||||
writer.WriteString(UrlPropertyNameBytes, response.Url);
|
var writer = reusableWriter.GetJsonWriter();
|
||||||
}
|
writer.WriteStartObject();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(response.AccessToken))
|
if (!string.IsNullOrEmpty(response.Url))
|
||||||
{
|
|
||||||
writer.WriteString(AccessTokenPropertyNameBytes, response.AccessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(response.ConnectionId))
|
|
||||||
{
|
|
||||||
writer.WriteString(ConnectionIdPropertyNameBytes, response.ConnectionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.WriteStartArray(AvailableTransportsPropertyNameBytes);
|
|
||||||
|
|
||||||
if (response.AvailableTransports != null)
|
|
||||||
{
|
|
||||||
foreach (var availableTransport in response.AvailableTransports)
|
|
||||||
{
|
{
|
||||||
writer.WriteStartObject();
|
writer.WriteString(UrlPropertyNameBytes, response.Url);
|
||||||
if (availableTransport.Transport != null)
|
|
||||||
{
|
|
||||||
writer.WriteString(TransportPropertyNameBytes, availableTransport.Transport);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Might be able to remove this after https://github.com/dotnet/corefx/issues/34632 is resolved
|
|
||||||
writer.WriteNull(TransportPropertyNameBytes);
|
|
||||||
}
|
|
||||||
writer.WriteStartArray(TransferFormatsPropertyNameBytes);
|
|
||||||
|
|
||||||
if (availableTransport.TransferFormats != null)
|
|
||||||
{
|
|
||||||
foreach (var transferFormat in availableTransport.TransferFormats)
|
|
||||||
{
|
|
||||||
writer.WriteStringValue(transferFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.WriteEndArray();
|
|
||||||
writer.WriteEndObject();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(response.AccessToken))
|
||||||
|
{
|
||||||
|
writer.WriteString(AccessTokenPropertyNameBytes, response.AccessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(response.ConnectionId))
|
||||||
|
{
|
||||||
|
writer.WriteString(ConnectionIdPropertyNameBytes, response.ConnectionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteStartArray(AvailableTransportsPropertyNameBytes);
|
||||||
|
|
||||||
|
if (response.AvailableTransports != null)
|
||||||
|
{
|
||||||
|
foreach (var availableTransport in response.AvailableTransports)
|
||||||
|
{
|
||||||
|
writer.WriteStartObject();
|
||||||
|
if (availableTransport.Transport != null)
|
||||||
|
{
|
||||||
|
writer.WriteString(TransportPropertyNameBytes, availableTransport.Transport);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Might be able to remove this after https://github.com/dotnet/corefx/issues/34632 is resolved
|
||||||
|
writer.WriteNull(TransportPropertyNameBytes);
|
||||||
|
}
|
||||||
|
writer.WriteStartArray(TransferFormatsPropertyNameBytes);
|
||||||
|
|
||||||
|
if (availableTransport.TransferFormats != null)
|
||||||
|
{
|
||||||
|
foreach (var transferFormat in availableTransport.TransferFormats)
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(transferFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteEndArray();
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteEndArray();
|
||||||
|
writer.WriteEndObject();
|
||||||
|
|
||||||
|
writer.Flush();
|
||||||
|
Debug.Assert(writer.CurrentDepth == 0);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ReusableUtf8JsonWriter.Return(reusableWriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteEndArray();
|
|
||||||
writer.WriteEndObject();
|
|
||||||
|
|
||||||
writer.Flush();
|
|
||||||
Debug.Assert(writer.CurrentDepth == 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NegotiationResponse ParseResponse(ReadOnlySpan<byte> content)
|
public static NegotiationResponse ParseResponse(ReadOnlySpan<byte> content)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Description>Implements the SignalR Hub Protocol using System.Text.Json.</Description>
|
<Description>Implements the SignalR Hub Protocol using System.Text.Json.</Description>
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
<Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="TextMessageParser.cs" />
|
<Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="TextMessageParser.cs" />
|
||||||
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextReader.cs" Link="Utf8BufferTextReader.cs" />
|
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextReader.cs" Link="Utf8BufferTextReader.cs" />
|
||||||
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Utf8BufferTextWriter.cs" />
|
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Utf8BufferTextWriter.cs" />
|
||||||
|
<Compile Include="$(SignalRSharedSourceRoot)ReusableUtf8JsonWriter.cs" Link="ReusableUtf8JsonWriter.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -444,49 +444,57 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||||
|
|
||||||
private void WriteMessageCore(HubMessage message, IBufferWriter<byte> stream)
|
private void WriteMessageCore(HubMessage message, IBufferWriter<byte> stream)
|
||||||
{
|
{
|
||||||
using var writer = new Utf8JsonWriter(stream);
|
var reusableWriter = ReusableUtf8JsonWriter.Get(stream);
|
||||||
|
|
||||||
writer.WriteStartObject();
|
try
|
||||||
switch (message)
|
|
||||||
{
|
{
|
||||||
case InvocationMessage m:
|
var writer = reusableWriter.GetJsonWriter();
|
||||||
WriteMessageType(writer, HubProtocolConstants.InvocationMessageType);
|
writer.WriteStartObject();
|
||||||
WriteHeaders(writer, m);
|
switch (message)
|
||||||
WriteInvocationMessage(m, writer);
|
{
|
||||||
break;
|
case InvocationMessage m:
|
||||||
case StreamInvocationMessage m:
|
WriteMessageType(writer, HubProtocolConstants.InvocationMessageType);
|
||||||
WriteMessageType(writer, HubProtocolConstants.StreamInvocationMessageType);
|
WriteHeaders(writer, m);
|
||||||
WriteHeaders(writer, m);
|
WriteInvocationMessage(m, writer);
|
||||||
WriteStreamInvocationMessage(m, writer);
|
break;
|
||||||
break;
|
case StreamInvocationMessage m:
|
||||||
case StreamItemMessage m:
|
WriteMessageType(writer, HubProtocolConstants.StreamInvocationMessageType);
|
||||||
WriteMessageType(writer, HubProtocolConstants.StreamItemMessageType);
|
WriteHeaders(writer, m);
|
||||||
WriteHeaders(writer, m);
|
WriteStreamInvocationMessage(m, writer);
|
||||||
WriteStreamItemMessage(m, writer);
|
break;
|
||||||
break;
|
case StreamItemMessage m:
|
||||||
case CompletionMessage m:
|
WriteMessageType(writer, HubProtocolConstants.StreamItemMessageType);
|
||||||
WriteMessageType(writer, HubProtocolConstants.CompletionMessageType);
|
WriteHeaders(writer, m);
|
||||||
WriteHeaders(writer, m);
|
WriteStreamItemMessage(m, writer);
|
||||||
WriteCompletionMessage(m, writer);
|
break;
|
||||||
break;
|
case CompletionMessage m:
|
||||||
case CancelInvocationMessage m:
|
WriteMessageType(writer, HubProtocolConstants.CompletionMessageType);
|
||||||
WriteMessageType(writer, HubProtocolConstants.CancelInvocationMessageType);
|
WriteHeaders(writer, m);
|
||||||
WriteHeaders(writer, m);
|
WriteCompletionMessage(m, writer);
|
||||||
WriteCancelInvocationMessage(m, writer);
|
break;
|
||||||
break;
|
case CancelInvocationMessage m:
|
||||||
case PingMessage _:
|
WriteMessageType(writer, HubProtocolConstants.CancelInvocationMessageType);
|
||||||
WriteMessageType(writer, HubProtocolConstants.PingMessageType);
|
WriteHeaders(writer, m);
|
||||||
break;
|
WriteCancelInvocationMessage(m, writer);
|
||||||
case CloseMessage m:
|
break;
|
||||||
WriteMessageType(writer, HubProtocolConstants.CloseMessageType);
|
case PingMessage _:
|
||||||
WriteCloseMessage(m, writer);
|
WriteMessageType(writer, HubProtocolConstants.PingMessageType);
|
||||||
break;
|
break;
|
||||||
default:
|
case CloseMessage m:
|
||||||
throw new InvalidOperationException($"Unsupported message type: {message.GetType().FullName}");
|
WriteMessageType(writer, HubProtocolConstants.CloseMessageType);
|
||||||
|
WriteCloseMessage(m, writer);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"Unsupported message type: {message.GetType().FullName}");
|
||||||
|
}
|
||||||
|
writer.WriteEndObject();
|
||||||
|
writer.Flush();
|
||||||
|
Debug.Assert(writer.CurrentDepth == 0);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ReusableUtf8JsonWriter.Return(reusableWriter);
|
||||||
}
|
}
|
||||||
writer.WriteEndObject();
|
|
||||||
writer.Flush();
|
|
||||||
Debug.Assert(writer.CurrentDepth == 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteHeaders(Utf8JsonWriter writer, HubInvocationMessage message)
|
private void WriteHeaders(Utf8JsonWriter writer, HubInvocationMessage message)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// 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.Text.Json;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Internal
|
||||||
|
{
|
||||||
|
internal sealed class ReusableUtf8JsonWriter
|
||||||
|
{
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ReusableUtf8JsonWriter _cachedInstance;
|
||||||
|
|
||||||
|
private readonly Utf8JsonWriter _writer;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
private bool _inUse;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public ReusableUtf8JsonWriter(IBufferWriter<byte> stream)
|
||||||
|
{
|
||||||
|
_writer = new Utf8JsonWriter(stream, new JsonWriterOptions() { SkipValidation = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReusableUtf8JsonWriter Get(IBufferWriter<byte> stream)
|
||||||
|
{
|
||||||
|
var writer = _cachedInstance;
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
writer = new ReusableUtf8JsonWriter(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken off the thread static
|
||||||
|
_cachedInstance = null;
|
||||||
|
#if DEBUG
|
||||||
|
if (writer._inUse)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The writer wasn't returned!");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer._inUse = true;
|
||||||
|
#endif
|
||||||
|
writer._writer.Reset(stream);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Return(ReusableUtf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
_cachedInstance = writer;
|
||||||
|
|
||||||
|
writer._writer.Reset();
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
writer._inUse = false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public Utf8JsonWriter GetJsonWriter()
|
||||||
|
{
|
||||||
|
return _writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
<Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="Internal\TextMessageParser.cs" />
|
<Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="Internal\TextMessageParser.cs" />
|
||||||
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextReader.cs" Link="Internal\Utf8BufferTextReader.cs" />
|
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextReader.cs" Link="Internal\Utf8BufferTextReader.cs" />
|
||||||
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" />
|
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Internal\Utf8BufferTextWriter.cs" />
|
||||||
|
<Compile Include="$(SignalRSharedSourceRoot)ReusableUtf8JsonWriter.cs" Link="Internal\ReusableUtf8JsonWriter.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -60,14 +60,23 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||||
/// <param name="output">The output writer.</param>
|
/// <param name="output">The output writer.</param>
|
||||||
public static void WriteRequestMessage(HandshakeRequestMessage requestMessage, IBufferWriter<byte> output)
|
public static void WriteRequestMessage(HandshakeRequestMessage requestMessage, IBufferWriter<byte> output)
|
||||||
{
|
{
|
||||||
using var writer = new Utf8JsonWriter(output, new JsonWriterOptions() { SkipValidation = true });
|
var reusableWriter = ReusableUtf8JsonWriter.Get(output);
|
||||||
|
|
||||||
writer.WriteStartObject();
|
try
|
||||||
writer.WriteString(ProtocolPropertyNameBytes, requestMessage.Protocol);
|
{
|
||||||
writer.WriteNumber(ProtocolVersionPropertyNameBytes, requestMessage.Version);
|
var writer = reusableWriter.GetJsonWriter();
|
||||||
writer.WriteEndObject();
|
|
||||||
writer.Flush();
|
writer.WriteStartObject();
|
||||||
Debug.Assert(writer.CurrentDepth == 0);
|
writer.WriteString(ProtocolPropertyNameBytes, requestMessage.Protocol);
|
||||||
|
writer.WriteNumber(ProtocolVersionPropertyNameBytes, requestMessage.Version);
|
||||||
|
writer.WriteEndObject();
|
||||||
|
writer.Flush();
|
||||||
|
Debug.Assert(writer.CurrentDepth == 0);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ReusableUtf8JsonWriter.Return(reusableWriter);
|
||||||
|
}
|
||||||
|
|
||||||
TextMessageFormatter.WriteRecordSeparator(output);
|
TextMessageFormatter.WriteRecordSeparator(output);
|
||||||
}
|
}
|
||||||
|
|
@ -79,19 +88,28 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||||
/// <param name="output">The output writer.</param>
|
/// <param name="output">The output writer.</param>
|
||||||
public static void WriteResponseMessage(HandshakeResponseMessage responseMessage, IBufferWriter<byte> output)
|
public static void WriteResponseMessage(HandshakeResponseMessage responseMessage, IBufferWriter<byte> output)
|
||||||
{
|
{
|
||||||
using var writer = new Utf8JsonWriter(output, new JsonWriterOptions() { SkipValidation = true });
|
var reusableWriter = ReusableUtf8JsonWriter.Get(output);
|
||||||
|
|
||||||
writer.WriteStartObject();
|
try
|
||||||
if (!string.IsNullOrEmpty(responseMessage.Error))
|
|
||||||
{
|
{
|
||||||
writer.WriteString(ErrorPropertyNameBytes, responseMessage.Error);
|
var writer = reusableWriter.GetJsonWriter();
|
||||||
|
|
||||||
|
writer.WriteStartObject();
|
||||||
|
if (!string.IsNullOrEmpty(responseMessage.Error))
|
||||||
|
{
|
||||||
|
writer.WriteString(ErrorPropertyNameBytes, responseMessage.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteNumber(MinorVersionPropertyNameBytes, responseMessage.MinorVersion);
|
||||||
|
|
||||||
|
writer.WriteEndObject();
|
||||||
|
writer.Flush();
|
||||||
|
Debug.Assert(writer.CurrentDepth == 0);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ReusableUtf8JsonWriter.Return(reusableWriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteNumber(MinorVersionPropertyNameBytes, responseMessage.MinorVersion);
|
|
||||||
|
|
||||||
writer.WriteEndObject();
|
|
||||||
writer.Flush();
|
|
||||||
Debug.Assert(writer.CurrentDepth == 0);
|
|
||||||
|
|
||||||
TextMessageFormatter.WriteRecordSeparator(output);
|
TextMessageFormatter.WriteRecordSeparator(output);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue