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; private const int DefaultValueLengthLimit = 1024 * 1024 * 4;
// Used for UTF8/ASCII (precalculated for fast path) // 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> UTF8EqualEncoded => new byte[] { (byte)'=' };
private static ReadOnlySpan<byte> UTF8AndEncoded => 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" // "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)'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', (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 // so this can be hardcoded to 60 bytes for the requestKey + static websocket string
Span<byte> mergedBytes = stackalloc byte[60]; Span<byte> mergedBytes = stackalloc byte[60];
Encoding.UTF8.GetBytes(requestKey, mergedBytes); Encoding.UTF8.GetBytes(requestKey, mergedBytes);
_encodedWebSocketKey.CopyTo(mergedBytes.Slice(24)); EncodedWebSocketKey.CopyTo(mergedBytes.Slice(24));
Span<byte> hashedBytes = stackalloc byte[20]; Span<byte> hashedBytes = stackalloc byte[20];
var success = algorithm.TryComputeHash(mergedBytes, hashedBytes, out var written); var success = algorithm.TryComputeHash(mergedBytes, hashedBytes, out var written);

View File

@ -6,12 +6,14 @@ using System.Buffers;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Text; using System.Text;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
internal static class ChunkWriter 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) 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; count = (total >> 2) + 3;
var offset = 0; var offset = 0;
ref var startHex = ref _hex[0]; ref var startHex = ref MemoryMarshal.GetReference(Hex);
for (shift = total; shift >= 0; shift -= 4) for (shift = total; shift >= 0; shift -= 4)
{ {

View File

@ -14,7 +14,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
/// </summary> /// </summary>
internal class DateHeaderValueManager : IHeartbeatHandler 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; private DateHeaderValues _dateValues;
@ -38,9 +39,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private void SetDateValues(DateTimeOffset value) private void SetDateValues(DateTimeOffset value)
{ {
var dateValue = HeaderUtilities.FormatDate(value); var dateValue = HeaderUtilities.FormatDate(value);
var dateBytes = new byte[_datePreambleBytes.Length + dateValue.Length]; var dateBytes = new byte[DatePreambleBytes.Length + dateValue.Length];
Buffer.BlockCopy(_datePreambleBytes, 0, dateBytes, 0, _datePreambleBytes.Length); DatePreambleBytes.CopyTo(dateBytes);
Encoding.ASCII.GetBytes(dateValue, 0, dateValue.Length, dateBytes, _datePreambleBytes.Length); Encoding.ASCII.GetBytes(dateValue, dateBytes.AsSpan(DatePreambleBytes.Length));
var dateValues = new DateHeaderValues var dateValues = new DateHeaderValues
{ {

View File

@ -14,6 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
internal sealed partial class HttpResponseHeaders : HttpHeaders 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> CrLf => new[] { (byte)'\r', (byte)'\n' };
private static ReadOnlySpan<byte> ColonSpace => new[] { (byte)':', (byte)' ' }; 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 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 = private static readonly PseudoHeaderFields _mandatoryRequestPseudoHeaderFields =
PseudoHeaderFields.Method | PseudoHeaderFields.Path | PseudoHeaderFields.Scheme; 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 HttpConnectionContext _context;
private readonly Http2FrameWriter _frameWriter; private readonly Http2FrameWriter _frameWriter;
private readonly Pipe _input; private readonly Pipe _input;
@ -1175,7 +1165,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
if (headerField == PseudoHeaderFields.Method) if (headerField == PseudoHeaderFields.Method)
{ {
_isMethodConnect = value.SequenceEqual(_connectBytes); _isMethodConnect = value.SequenceEqual(ConnectBytes);
} }
_parsedPseudoHeaderFields |= headerField; _parsedPseudoHeaderFields |= headerField;
@ -1217,23 +1207,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return false; return false;
} }
if (name.SequenceEqual(_pathBytes)) if (name.SequenceEqual(PathBytes))
{ {
headerField = PseudoHeaderFields.Path; headerField = PseudoHeaderFields.Path;
} }
else if (name.SequenceEqual(_methodBytes)) else if (name.SequenceEqual(MethodBytes))
{ {
headerField = PseudoHeaderFields.Method; headerField = PseudoHeaderFields.Method;
} }
else if (name.SequenceEqual(_schemeBytes)) else if (name.SequenceEqual(SchemeBytes))
{ {
headerField = PseudoHeaderFields.Scheme; headerField = PseudoHeaderFields.Scheme;
} }
else if (name.SequenceEqual(_statusBytes)) else if (name.SequenceEqual(StatusBytes))
{ {
headerField = PseudoHeaderFields.Status; headerField = PseudoHeaderFields.Status;
} }
else if (name.SequenceEqual(_authorityBytes)) else if (name.SequenceEqual(AuthorityBytes))
{ {
headerField = PseudoHeaderFields.Authority; 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) 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() private bool TryClose()

View File

@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
internal class Http2FrameWriter internal class Http2FrameWriter
{ {
// Literal Header Field without Indexing - Indexed Name (Index 8 - :status) // 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 static ReadOnlySpan<byte> ContinueBytes => new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' };
private readonly object _writeLock = new object(); 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)] [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2223", FlakyOn.Helix.All)]
public void GeneratedCodeIsUpToDate() public void GeneratedCodeIsUpToDate()
{ {
var httpHeadersGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "HttpHeaders.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 httpProtocolGeneratedPath = Path.Combine(AppContext.BaseDirectory, "shared", "GeneratedContent", "HttpProtocol.Generated.cs");
var httpUtilitiesGeneratedPath = Path.Combine(AppContext.BaseDirectory,"shared", "GeneratedContent", "HttpUtilities.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 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 testHttpHeadersGeneratedPath = Path.GetTempFileName();
var testHttpProtocolGeneratedPath = Path.GetTempFileName(); var testHttpProtocolGeneratedPath = Path.GetTempFileName();
var testHttpUtilitiesGeneratedPath = Path.GetTempFileName(); var testHttpUtilitiesGeneratedPath = Path.GetTempFileName();
var testHttp2ConnectionGeneratedPath = Path.GetTempFileName();
var testTransportConnectionGeneratedPath = Path.GetTempFileName(); var testTransportConnectionGeneratedPath = Path.GetTempFileName();
try try
@ -31,26 +33,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var currentHttpHeadersGenerated = File.ReadAllText(httpHeadersGeneratedPath); var currentHttpHeadersGenerated = File.ReadAllText(httpHeadersGeneratedPath);
var currentHttpProtocolGenerated = File.ReadAllText(httpProtocolGeneratedPath); var currentHttpProtocolGenerated = File.ReadAllText(httpProtocolGeneratedPath);
var currentHttpUtilitiesGenerated = File.ReadAllText(httpUtilitiesGeneratedPath); var currentHttpUtilitiesGenerated = File.ReadAllText(httpUtilitiesGeneratedPath);
var currentHttp2ConnectionGenerated = File.ReadAllText(http2ConnectionGeneratedPath);
var currentTransportConnectionGenerated = File.ReadAllText(transportConnectionGeneratedPath); 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 testHttpHeadersGenerated = File.ReadAllText(testHttpHeadersGeneratedPath);
var testHttpProtocolGenerated = File.ReadAllText(testHttpProtocolGeneratedPath); var testHttpProtocolGenerated = File.ReadAllText(testHttpProtocolGeneratedPath);
var testHttpUtilitiesGenerated = File.ReadAllText(testHttpUtilitiesGeneratedPath); var testHttpUtilitiesGenerated = File.ReadAllText(testHttpUtilitiesGeneratedPath);
var testHttp2ConnectionGenerated = File.ReadAllText(testHttp2ConnectionGeneratedPath);
var testTransportConnectionGenerated = File.ReadAllText(testTransportConnectionGeneratedPath); var testTransportConnectionGenerated = File.ReadAllText(testTransportConnectionGeneratedPath);
Assert.Equal(currentHttpHeadersGenerated, testHttpHeadersGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentHttpHeadersGenerated, testHttpHeadersGenerated, ignoreLineEndingDifferences: true);
Assert.Equal(currentHttpProtocolGenerated, testHttpProtocolGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentHttpProtocolGenerated, testHttpProtocolGenerated, ignoreLineEndingDifferences: true);
Assert.Equal(currentHttpUtilitiesGenerated, testHttpUtilitiesGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentHttpUtilitiesGenerated, testHttpUtilitiesGenerated, ignoreLineEndingDifferences: true);
Assert.Equal(currentHttp2ConnectionGenerated, testHttp2ConnectionGenerated, ignoreLineEndingDifferences: true);
Assert.Equal(currentTransportConnectionGenerated, testTransportConnectionGenerated, ignoreLineEndingDifferences: true); Assert.Equal(currentTransportConnectionGenerated, testTransportConnectionGenerated, ignoreLineEndingDifferences: true);
} }
finally finally
{ {
File.Delete(testHttpHeadersGeneratedPath); File.Delete(testHttpHeadersGeneratedPath);
File.Delete(testHttpProtocolGeneratedPath); File.Delete(testHttpProtocolGeneratedPath);
File.Delete(testHttpUtilitiesGeneratedPath); File.Delete(testHttpUtilitiesGeneratedPath);
File.Delete(testHttp2ConnectionGeneratedPath);
File.Delete(testTransportConnectionGeneratedPath); 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\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\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\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" /> <Content Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup> </ItemGroup>

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@ -18,7 +18,7 @@
<PropertyGroup> <PropertyGroup>
<StartWorkingDirectory>$(MSBuildThisFileDirectory)..\..\</StartWorkingDirectory> <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> </PropertyGroup>
</Project> </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"); Console.Error.WriteLine("Missing path to TransportConnection.Generated.cs");
return 1; 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; return 0;
} }
@ -40,12 +45,14 @@ namespace CodeGenerator
string knownHeadersPath, string knownHeadersPath,
string httpProtocolFeatureCollectionPath, string httpProtocolFeatureCollectionPath,
string httpUtilitiesPath, string httpUtilitiesPath,
string transportConnectionFeatureCollectionPath) string transportConnectionFeatureCollectionPath,
string http2ConnectionPath)
{ {
var knownHeadersContent = KnownHeaders.GeneratedFile(); var knownHeadersContent = KnownHeaders.GeneratedFile();
var httpProtocolFeatureCollectionContent = HttpProtocolFeatureCollection.GenerateFile(); var httpProtocolFeatureCollectionContent = HttpProtocolFeatureCollection.GenerateFile();
var httpUtilitiesContent = HttpUtilities.HttpUtilities.GeneratedFile(); var httpUtilitiesContent = HttpUtilities.HttpUtilities.GeneratedFile();
var transportConnectionFeatureCollectionContent = TransportConnectionFeatureCollection.GenerateFile(); var transportConnectionFeatureCollectionContent = TransportConnectionFeatureCollection.GenerateFile();
var http2ConnectionContent = Http2Connection.GenerateFile();
var existingKnownHeaders = File.Exists(knownHeadersPath) ? File.ReadAllText(knownHeadersPath) : ""; var existingKnownHeaders = File.Exists(knownHeadersPath) ? File.ReadAllText(knownHeadersPath) : "";
if (!string.Equals(knownHeadersContent, existingKnownHeaders)) if (!string.Equals(knownHeadersContent, existingKnownHeaders))
@ -70,6 +77,12 @@ namespace CodeGenerator
{ {
File.WriteAllText(transportConnectionFeatureCollectionPath, transportConnectionFeatureCollectionContent); 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. // Licensed under the Apache License, Version 2.0.
// See THIRD-PARTY-NOTICES.TXT in the project root for license information. // 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 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> 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> BytesStatus101 => new byte[] { (byte)'1', (byte)'0', (byte)'1' };
private static ReadOnlySpan<byte> BytesStatus102 => new byte[] { (byte)'1', (byte)'0', (byte)'2' }; 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 ByteLF = (byte)'\n';
private const byte ByteColon = (byte)':'; private const byte ByteColon = (byte)':';
private static ReadOnlySpan<byte> _dataPrefix => new byte[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)':', (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> _sseLineEnding => new byte[] { (byte)'\r', (byte)'\n' }; 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 static readonly byte[] _newLine = Encoding.UTF8.GetBytes(Environment.NewLine);
private InternalParseState _internalParserState = InternalParseState.ReadMessagePayload; 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\n\bar should be encoded as
// data: foo\r\n // data: foo\r\n
// data: bar\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'"); 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); EnsureStartsWithDataPrefix(line);
// Slice away the 'data: ' // Slice away the 'data: '
var payloadLength = line.Length - (_dataPrefix.Length + _sseLineEnding.Length); var payloadLength = line.Length - (DataPrefix.Length + SseLineEnding.Length);
var newData = line.Slice(_dataPrefix.Length, payloadLength).ToArray(); var newData = line.Slice(DataPrefix.Length, payloadLength).ToArray();
_data.Add(newData); _data.Add(newData);
start = lineEnd; start = lineEnd;
@ -162,7 +163,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
private void EnsureStartsWithDataPrefix(ReadOnlySpan<byte> line) private void EnsureStartsWithDataPrefix(ReadOnlySpan<byte> line)
{ {
if (!line.StartsWith(_dataPrefix)) if (!line.StartsWith(DataPrefix))
{ {
throw new FormatException("Expected the message prefix 'data: '"); 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) 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 public enum ParseResult