Use C#'s ReadOnlySpan static data optimization in more places (#14447)

This commit is contained in:
Günther Foidl 2019-11-25 03:04:26 +01:00 committed by Stephen Halter
parent 178197efb8
commit 9e67b7b22e
18 changed files with 200 additions and 47 deletions

View File

@ -29,6 +29,7 @@ namespace Microsoft.AspNetCore.WebUtilities
private const int DefaultValueLengthLimit = 1024 * 1024 * 4;
// Used for UTF8/ASCII (precalculated for fast path)
// This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
private static ReadOnlySpan<byte> UTF8EqualEncoded => new byte[] { (byte)'=' };
private static ReadOnlySpan<byte> UTF8AndEncoded => new byte[] { (byte)'&' };

View File

@ -24,7 +24,8 @@ namespace Microsoft.AspNetCore.WebSockets
};
// "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
private static ReadOnlySpan<byte> _encodedWebSocketKey => new byte[]
// This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
private static ReadOnlySpan<byte> EncodedWebSocketKey => new byte[]
{
(byte)'2', (byte)'5', (byte)'8', (byte)'E', (byte)'A', (byte)'F', (byte)'A', (byte)'5', (byte)'-',
(byte)'E', (byte)'9', (byte)'1', (byte)'4', (byte)'-', (byte)'4', (byte)'7', (byte)'D', (byte)'A',
@ -115,7 +116,7 @@ namespace Microsoft.AspNetCore.WebSockets
// so this can be hardcoded to 60 bytes for the requestKey + static websocket string
Span<byte> mergedBytes = stackalloc byte[60];
Encoding.UTF8.GetBytes(requestKey, mergedBytes);
_encodedWebSocketKey.CopyTo(mergedBytes.Slice(24));
EncodedWebSocketKey.CopyTo(mergedBytes.Slice(24));
Span<byte> hashedBytes = stackalloc byte[20];
var success = algorithm.TryComputeHash(mergedBytes, hashedBytes, out var written);

View File

@ -6,12 +6,14 @@ using System.Buffers;
using System.IO.Pipelines;
using System.Text;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
internal static class ChunkWriter
{
private static readonly byte[] _hex = Encoding.ASCII.GetBytes("0123456789abcdef");
// This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
private static ReadOnlySpan<byte> Hex => new byte[16] { (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f' };
public static int BeginChunkBytes(int dataCount, Span<byte> span)
{
@ -28,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
count = (total >> 2) + 3;
var offset = 0;
ref var startHex = ref _hex[0];
ref var startHex = ref MemoryMarshal.GetReference(Hex);
for (shift = total; shift >= 0; shift -= 4)
{

View File

@ -14,7 +14,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
/// </summary>
internal class DateHeaderValueManager : IHeartbeatHandler
{
private static readonly byte[] _datePreambleBytes = Encoding.ASCII.GetBytes("\r\nDate: ");
// This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
private static ReadOnlySpan<byte> DatePreambleBytes => new byte[8] { (byte)'\r', (byte)'\n', (byte)'D', (byte)'a', (byte)'t', (byte)'e', (byte)':', (byte)' ' };
private DateHeaderValues _dateValues;
@ -38,9 +39,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private void SetDateValues(DateTimeOffset value)
{
var dateValue = HeaderUtilities.FormatDate(value);
var dateBytes = new byte[_datePreambleBytes.Length + dateValue.Length];
Buffer.BlockCopy(_datePreambleBytes, 0, dateBytes, 0, _datePreambleBytes.Length);
Encoding.ASCII.GetBytes(dateValue, 0, dateValue.Length, dateBytes, _datePreambleBytes.Length);
var dateBytes = new byte[DatePreambleBytes.Length + dateValue.Length];
DatePreambleBytes.CopyTo(dateBytes);
Encoding.ASCII.GetBytes(dateValue, dateBytes.AsSpan(DatePreambleBytes.Length));
var dateValues = new DateHeaderValues
{

View File

@ -14,6 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
internal sealed partial class HttpResponseHeaders : HttpHeaders
{
// This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
private static ReadOnlySpan<byte> CrLf => new[] { (byte)'\r', (byte)'\n' };
private static ReadOnlySpan<byte> ColonSpace => new[] { (byte)':', (byte)' ' };

View File

@ -0,0 +1,23 @@
// 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;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
internal partial class Http2Connection
{
// This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
private static ReadOnlySpan<byte> ClientPrefaceBytes => new byte[24] { (byte)'P', (byte)'R', (byte)'I', (byte)' ', (byte)'*', (byte)' ', (byte)'H', (byte)'T', (byte)'T', (byte)'P', (byte)'/', (byte)'2', (byte)'.', (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n', (byte)'S', (byte)'M', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
private static ReadOnlySpan<byte> AuthorityBytes => new byte[10] { (byte)':', (byte)'a', (byte)'u', (byte)'t', (byte)'h', (byte)'o', (byte)'r', (byte)'i', (byte)'t', (byte)'y' };
private static ReadOnlySpan<byte> MethodBytes => new byte[7] { (byte)':', (byte)'m', (byte)'e', (byte)'t', (byte)'h', (byte)'o', (byte)'d' };
private static ReadOnlySpan<byte> PathBytes => new byte[5] { (byte)':', (byte)'p', (byte)'a', (byte)'t', (byte)'h' };
private static ReadOnlySpan<byte> SchemeBytes => new byte[7] { (byte)':', (byte)'s', (byte)'c', (byte)'h', (byte)'e', (byte)'m', (byte)'e' };
private static ReadOnlySpan<byte> StatusBytes => new byte[7] { (byte)':', (byte)'s', (byte)'t', (byte)'a', (byte)'t', (byte)'u', (byte)'s' };
private static ReadOnlySpan<byte> ConnectionBytes => new byte[10] { (byte)'c', (byte)'o', (byte)'n', (byte)'n', (byte)'e', (byte)'c', (byte)'t', (byte)'i', (byte)'o', (byte)'n' };
private static ReadOnlySpan<byte> TeBytes => new byte[2] { (byte)'t', (byte)'e' };
private static ReadOnlySpan<byte> TrailersBytes => new byte[8] { (byte)'t', (byte)'r', (byte)'a', (byte)'i', (byte)'l', (byte)'e', (byte)'r', (byte)'s' };
private static ReadOnlySpan<byte> ConnectBytes => new byte[7] { (byte)'C', (byte)'O', (byte)'N', (byte)'N', (byte)'E', (byte)'C', (byte)'T' };
}
}

View File

@ -27,23 +27,13 @@ using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
internal class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeadersHandler, IRequestProcessor
internal partial class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeadersHandler, IRequestProcessor
{
public static byte[] ClientPreface { get; } = Encoding.ASCII.GetBytes("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
public static ReadOnlySpan<byte> ClientPreface => ClientPrefaceBytes;
private static readonly PseudoHeaderFields _mandatoryRequestPseudoHeaderFields =
PseudoHeaderFields.Method | PseudoHeaderFields.Path | PseudoHeaderFields.Scheme;
private static readonly byte[] _authorityBytes = Encoding.ASCII.GetBytes(HeaderNames.Authority);
private static readonly byte[] _methodBytes = Encoding.ASCII.GetBytes(HeaderNames.Method);
private static readonly byte[] _pathBytes = Encoding.ASCII.GetBytes(HeaderNames.Path);
private static readonly byte[] _schemeBytes = Encoding.ASCII.GetBytes(HeaderNames.Scheme);
private static readonly byte[] _statusBytes = Encoding.ASCII.GetBytes(HeaderNames.Status);
private static readonly byte[] _connectionBytes = Encoding.ASCII.GetBytes("connection");
private static readonly byte[] _teBytes = Encoding.ASCII.GetBytes("te");
private static readonly byte[] _trailersBytes = Encoding.ASCII.GetBytes("trailers");
private static readonly byte[] _connectBytes = Encoding.ASCII.GetBytes("CONNECT");
private readonly HttpConnectionContext _context;
private readonly Http2FrameWriter _frameWriter;
private readonly Pipe _input;
@ -1175,7 +1165,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
if (headerField == PseudoHeaderFields.Method)
{
_isMethodConnect = value.SequenceEqual(_connectBytes);
_isMethodConnect = value.SequenceEqual(ConnectBytes);
}
_parsedPseudoHeaderFields |= headerField;
@ -1217,23 +1207,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return false;
}
if (name.SequenceEqual(_pathBytes))
if (name.SequenceEqual(PathBytes))
{
headerField = PseudoHeaderFields.Path;
}
else if (name.SequenceEqual(_methodBytes))
else if (name.SequenceEqual(MethodBytes))
{
headerField = PseudoHeaderFields.Method;
}
else if (name.SequenceEqual(_schemeBytes))
else if (name.SequenceEqual(SchemeBytes))
{
headerField = PseudoHeaderFields.Scheme;
}
else if (name.SequenceEqual(_statusBytes))
else if (name.SequenceEqual(StatusBytes))
{
headerField = PseudoHeaderFields.Status;
}
else if (name.SequenceEqual(_authorityBytes))
else if (name.SequenceEqual(AuthorityBytes))
{
headerField = PseudoHeaderFields.Authority;
}
@ -1247,7 +1237,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private static bool IsConnectionSpecificHeaderField(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
{
return name.SequenceEqual(_connectionBytes) || (name.SequenceEqual(_teBytes) && !value.SequenceEqual(_trailersBytes));
return name.SequenceEqual(ConnectionBytes) || (name.SequenceEqual(TeBytes) && !value.SequenceEqual(TrailersBytes));
}
private bool TryClose()

View File

@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
internal class Http2FrameWriter
{
// Literal Header Field without Indexing - Indexed Name (Index 8 - :status)
// This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
private static ReadOnlySpan<byte> ContinueBytes => new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' };
private readonly object _writeLock = new object();

View File

@ -16,14 +16,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2223", FlakyOn.Helix.All)]
public void GeneratedCodeIsUpToDate()
{
var httpHeadersGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "HttpHeaders.Generated.cs");
var httpProtocolGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "HttpProtocol.Generated.cs");
var httpUtilitiesGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "HttpUtilities.Generated.cs");
var transportConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "TransportConnection.Generated.cs");
var httpHeadersGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpHeaders.Generated.cs");
var httpProtocolGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpProtocol.Generated.cs");
var httpUtilitiesGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpUtilities.Generated.cs");
var http2ConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "Http2Connection.Generated.cs");
var transportConnectionGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "TransportConnection.Generated.cs");
var testHttpHeadersGeneratedPath = Path.GetTempFileName();
var testHttpProtocolGeneratedPath = Path.GetTempFileName();
var testHttpUtilitiesGeneratedPath = Path.GetTempFileName();
var testHttp2ConnectionGeneratedPath = Path.GetTempFileName();
var testTransportConnectionGeneratedPath = Path.GetTempFileName();
try
@ -31,26 +33,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var currentHttpHeadersGenerated = File.ReadAllText(httpHeadersGeneratedPath);
var currentHttpProtocolGenerated = File.ReadAllText(httpProtocolGeneratedPath);
var currentHttpUtilitiesGenerated = File.ReadAllText(httpUtilitiesGeneratedPath);
var currentHttp2ConnectionGenerated = File.ReadAllText(http2ConnectionGeneratedPath);
var currentTransportConnectionGenerated = File.ReadAllText(transportConnectionGeneratedPath);
CodeGenerator.Program.Run(testHttpHeadersGeneratedPath, testHttpProtocolGeneratedPath, testHttpUtilitiesGeneratedPath, testTransportConnectionGeneratedPath);
CodeGenerator.Program.Run(testHttpHeadersGeneratedPath, testHttpProtocolGeneratedPath, testHttpUtilitiesGeneratedPath, testTransportConnectionGeneratedPath, testHttp2ConnectionGeneratedPath);
var testHttpHeadersGenerated = File.ReadAllText(testHttpHeadersGeneratedPath);
var testHttpProtocolGenerated = File.ReadAllText(testHttpProtocolGeneratedPath);
var testHttpUtilitiesGenerated = File.ReadAllText(testHttpUtilitiesGeneratedPath);
var testHttp2ConnectionGenerated = File.ReadAllText(testHttp2ConnectionGeneratedPath);
var testTransportConnectionGenerated = File.ReadAllText(testTransportConnectionGeneratedPath);
Assert.Equal(currentHttpHeadersGenerated, testHttpHeadersGenerated, ignoreLineEndingDifferences: true);
Assert.Equal(currentHttpProtocolGenerated, testHttpProtocolGenerated, ignoreLineEndingDifferences: true);
Assert.Equal(currentHttpUtilitiesGenerated, testHttpUtilitiesGenerated, ignoreLineEndingDifferences: true);
Assert.Equal(currentHttp2ConnectionGenerated, testHttp2ConnectionGenerated, ignoreLineEndingDifferences: true);
Assert.Equal(currentTransportConnectionGenerated, testTransportConnectionGenerated, ignoreLineEndingDifferences: true);
}
finally
{
File.Delete(testHttpHeadersGeneratedPath);
File.Delete(testHttpProtocolGeneratedPath);
File.Delete(testHttpUtilitiesGeneratedPath);
File.Delete(testHttp2ConnectionGeneratedPath);
File.Delete(testTransportConnectionGeneratedPath);
}
}

View File

@ -12,6 +12,7 @@
<Content Include="$(KestrelRoot)Core\src\Internal\Http\HttpHeaders.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelRoot)Core\src\Internal\Http\HttpProtocol.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelRoot)Core\src\Internal\Infrastructure\HttpUtilities.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelRoot)Core\src\Internal\Http2\Http2Connection.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

View File

@ -340,7 +340,7 @@ namespace http2cat
await writableBuffer.FlushAsync().AsTask().DefaultTimeout();
}
public Task SendPreambleAsync() => SendAsync(new ArraySegment<byte>(Http2Connection.ClientPreface));
public Task SendPreambleAsync() => SendAsync(Http2Connection.ClientPreface);
public async Task SendSettingsAsync()
{

View File

@ -688,7 +688,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await writableBuffer.FlushAsync().AsTask().DefaultTimeout();
}
protected Task SendPreambleAsync() => SendAsync(new ArraySegment<byte>(Http2Connection.ClientPreface));
protected Task SendPreambleAsync() => SendAsync(Http2Connection.ClientPreface);
protected async Task SendSettingsAsync()
{

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@ -18,7 +18,7 @@
<PropertyGroup>
<StartWorkingDirectory>$(MSBuildThisFileDirectory)..\..\</StartWorkingDirectory>
<StartArguments>Core/src/Internal/Http/HttpHeaders.Generated.cs Core/src/Internal/Http/HttpProtocol.Generated.cs Core/src/Internal/Infrastructure/HttpUtilities.Generated.cs shared/TransportConnection.Generated.cs</StartArguments>
<StartArguments>Core/src/Internal/Http/HttpHeaders.Generated.cs Core/src/Internal/Http/HttpProtocol.Generated.cs Core/src/Internal/Infrastructure/HttpUtilities.Generated.cs shared/TransportConnection.Generated.cs Core/src/Internal/Http2/Http2Connection.Generated.cs</StartArguments>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,33 @@
// 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 Microsoft.Net.Http.Headers;
namespace CodeGenerator
{
public static class Http2Connection
{
public static string GenerateFile()
{
return ReadOnlySpanStaticDataGenerator.GenerateFile(
namespaceName: "Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2",
className: "Http2Connection",
allProperties: GetStrings());
}
private static IEnumerable<(string Name, string Value)> GetStrings()
{
yield return ("ClientPreface", "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
yield return ("Authority", HeaderNames.Authority);
yield return ("Method", HeaderNames.Method);
yield return ("Path", HeaderNames.Path);
yield return ("Scheme", HeaderNames.Scheme);
yield return ("Status", HeaderNames.Status);
yield return ("Connection", "connection");
yield return ("Te", "te");
yield return ("Trailers", "trailers");
yield return ("Connect", "CONNECT");
}
}
}

View File

@ -30,8 +30,13 @@ namespace CodeGenerator
Console.Error.WriteLine("Missing path to TransportConnection.Generated.cs");
return 1;
}
else if (args.Length < 5)
{
Console.Error.WriteLine("Missing path to Http2Connection.Generated.cs");
return 1;
}
Run(args[0], args[1], args[2], args[3]);
Run(args[0], args[1], args[2], args[3], args[4]);
return 0;
}
@ -40,12 +45,14 @@ namespace CodeGenerator
string knownHeadersPath,
string httpProtocolFeatureCollectionPath,
string httpUtilitiesPath,
string transportConnectionFeatureCollectionPath)
string transportConnectionFeatureCollectionPath,
string http2ConnectionPath)
{
var knownHeadersContent = KnownHeaders.GeneratedFile();
var httpProtocolFeatureCollectionContent = HttpProtocolFeatureCollection.GenerateFile();
var httpUtilitiesContent = HttpUtilities.HttpUtilities.GeneratedFile();
var transportConnectionFeatureCollectionContent = TransportConnectionFeatureCollection.GenerateFile();
var http2ConnectionContent = Http2Connection.GenerateFile();
var existingKnownHeaders = File.Exists(knownHeadersPath) ? File.ReadAllText(knownHeadersPath) : "";
if (!string.Equals(knownHeadersContent, existingKnownHeaders))
@ -70,6 +77,12 @@ namespace CodeGenerator
{
File.WriteAllText(transportConnectionFeatureCollectionPath, transportConnectionFeatureCollectionContent);
}
var existingHttp2Connection = File.Exists(http2ConnectionPath) ? File.ReadAllText(http2ConnectionPath) : "";
if (!string.Equals(http2ConnectionContent, existingHttp2Connection))
{
File.WriteAllText(http2ConnectionPath, http2ConnectionContent);
}
}
}
}

View File

@ -0,0 +1,78 @@
// 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.Collections.Generic;
using System.Linq;
using System.Text;
namespace CodeGenerator
{
public static class ReadOnlySpanStaticDataGenerator
{
public static string GenerateFile(string namespaceName, string className, IEnumerable<(string Name, string Value)> allProperties)
{
var properties = allProperties.Select((p, index) => new Property
{
Data = p,
Index = index
});
return $@"// 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;
namespace {namespaceName}
{{
internal partial class {className}
{{
// This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
{Each(properties, p => $@"
private static ReadOnlySpan<byte> {p.Data.Name}Bytes => new byte[{p.Data.Value.Length}] {{ {GetDataAsBytes(p.Data.Value)} }};")}
}}
}}
";
}
private static string Each<T>(IEnumerable<T> values, Func<T, string> formatter)
{
return values.Any() ? values.Select(formatter).Aggregate((a, b) => a + b) : "";
}
private static string GetDataAsBytes(string value)
{
var stringBuilder = new StringBuilder();
for (var i = 0; i < value.Length; ++i)
{
var c = value[i];
if (c == '\n')
{
stringBuilder.Append("(byte)'\\n'");
}
else if (c == '\r')
{
stringBuilder.Append("(byte)'\\r'");
}
else
{
stringBuilder.AppendFormat("(byte)'{0}'", c);
}
if (i < value.Length - 1)
{
stringBuilder.Append(", ");
}
}
return stringBuilder.ToString();
}
private class Property
{
public (string Name, string Value) Data;
public int Index;
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0.
// See THIRD-PARTY-NOTICES.TXT in the project root for license information.
@ -9,6 +9,8 @@ namespace System.Net.Http.HPack
{
internal static class StatusCodes
{
// This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
private static ReadOnlySpan<byte> BytesStatus100 => new byte[] { (byte)'1', (byte)'0', (byte)'0' };
private static ReadOnlySpan<byte> BytesStatus101 => new byte[] { (byte)'1', (byte)'0', (byte)'1' };
private static ReadOnlySpan<byte> BytesStatus102 => new byte[] { (byte)'1', (byte)'0', (byte)'2' };

View File

@ -15,8 +15,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
private const byte ByteLF = (byte)'\n';
private const byte ByteColon = (byte)':';
private static ReadOnlySpan<byte> _dataPrefix => new byte[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)':', (byte)' ' };
private static ReadOnlySpan<byte> _sseLineEnding => new byte[] { (byte)'\r', (byte)'\n' };
// This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
private static ReadOnlySpan<byte> DataPrefix => new byte[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)':', (byte)' ' };
private static ReadOnlySpan<byte> SseLineEnding => new byte[] { (byte)'\r', (byte)'\n' };
private static readonly byte[] _newLine = Encoding.UTF8.GetBytes(Environment.NewLine);
private InternalParseState _internalParserState = InternalParseState.ReadMessagePayload;
@ -74,7 +75,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
// data: foo\n\bar should be encoded as
// data: foo\r\n
// data: bar\r\n
else if (line[line.Length - _sseLineEnding.Length] != ByteCR)
else if (line[line.Length - SseLineEnding.Length] != ByteCR)
{
throw new FormatException("Unexpected '\\n' in message. A '\\n' character can only be used as part of the newline sequence '\\r\\n'");
}
@ -90,8 +91,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
EnsureStartsWithDataPrefix(line);
// Slice away the 'data: '
var payloadLength = line.Length - (_dataPrefix.Length + _sseLineEnding.Length);
var newData = line.Slice(_dataPrefix.Length, payloadLength).ToArray();
var payloadLength = line.Length - (DataPrefix.Length + SseLineEnding.Length);
var newData = line.Slice(DataPrefix.Length, payloadLength).ToArray();
_data.Add(newData);
start = lineEnd;
@ -162,7 +163,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
private void EnsureStartsWithDataPrefix(ReadOnlySpan<byte> line)
{
if (!line.StartsWith(_dataPrefix))
if (!line.StartsWith(DataPrefix))
{
throw new FormatException("Expected the message prefix 'data: '");
}
@ -170,7 +171,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
private bool IsMessageEnd(ReadOnlySpan<byte> line)
{
return line.Length == _sseLineEnding.Length && line.SequenceEqual(_sseLineEnding);
return line.Length == SseLineEnding.Length && line.SequenceEqual(SseLineEnding);
}
public enum ParseResult