Use C#'s ReadOnlySpan static data optimization in more places (#14447)
This commit is contained in:
parent
178197efb8
commit
9e67b7b22e
|
|
@ -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)'&' };
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)' ' };
|
||||
|
||||
|
|
|
|||
|
|
@ -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' };
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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' };
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue