Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HttpSys request headers Keys and Count both allocate #45156

Merged
merged 2 commits into from
Nov 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 39 additions & 0 deletions AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebAppSample", "src\Framewo
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F43CC5EA-6032-4A11-A9B2-6D48CB5EB082}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "perf", "perf", "{74377D3E-E0C6-41A4-89ED-11A9C00142A9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.HttpSys.Microbenchmarks", "src\Servers\HttpSys\perf\Microbenchmarks\Microsoft.AspNetCore.Server.HttpSys.Microbenchmarks.csproj", "{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -10513,6 +10517,38 @@ Global
{A8E2AB77-8F57-47C2-A961-2F316793CAFF}.Release|x64.Build.0 = Release|Any CPU
{A8E2AB77-8F57-47C2-A961-2F316793CAFF}.Release|x86.ActiveCfg = Release|Any CPU
{A8E2AB77-8F57-47C2-A961-2F316793CAFF}.Release|x86.Build.0 = Release|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Debug|arm64.ActiveCfg = Debug|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Debug|arm64.Build.0 = Debug|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Debug|x64.ActiveCfg = Debug|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Debug|x64.Build.0 = Debug|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Debug|x86.ActiveCfg = Debug|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Debug|x86.Build.0 = Debug|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Release|Any CPU.Build.0 = Release|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Release|arm64.ActiveCfg = Release|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Release|arm64.Build.0 = Release|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Release|x64.ActiveCfg = Release|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Release|x64.Build.0 = Release|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Release|x86.ActiveCfg = Release|Any CPU
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD}.Release|x86.Build.0 = Release|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Debug|arm64.ActiveCfg = Debug|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Debug|arm64.Build.0 = Debug|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Debug|x64.ActiveCfg = Debug|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Debug|x64.Build.0 = Debug|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Debug|x86.ActiveCfg = Debug|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Debug|x86.Build.0 = Debug|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|Any CPU.Build.0 = Release|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|arm64.ActiveCfg = Release|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|arm64.Build.0 = Release|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|x64.ActiveCfg = Release|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|x64.Build.0 = Release|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|x86.ActiveCfg = Release|Any CPU
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -11378,6 +11414,9 @@ Global
{890B5210-48EF-488F-93A2-F13BCB07C780} = {4DA84F2B-1948-439B-85AB-E99E31331A9C}
{A8E2AB77-8F57-47C2-A961-2F316793CAFF} = {890B5210-48EF-488F-93A2-F13BCB07C780}
{F43CC5EA-6032-4A11-A9B2-6D48CB5EB082} = {4DA84F2B-1948-439B-85AB-E99E31331A9C}
{B6B74AC7-63AE-49E0-8283-C7B230BE12BD} = {33CAD745-5912-47D3-BAF3-5AE580FED275}
{74377D3E-E0C6-41A4-89ED-11A9C00142A9} = {166E48ED-9738-4E13-8618-0D805F6F0F65}
{3C7C65BF-0C13-418E-90BD-EC9C3CD282CB} = {74377D3E-E0C6-41A4-89ED-11A9C00142A9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
Expand Down
1 change: 1 addition & 0 deletions src/Servers/HttpSys/HttpSysServer.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"src\\Security\\Authorization\\Core\\src\\Microsoft.AspNetCore.Authorization.csproj",
"src\\Security\\Authorization\\Policy\\src\\Microsoft.AspNetCore.Authorization.Policy.csproj",
"src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj",
"src\\Servers\\HttpSys\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Server.HttpSys.Microbenchmarks.csproj",
"src\\Servers\\HttpSys\\samples\\HotAddSample\\HotAddSample.csproj",
"src\\Servers\\HttpSys\\samples\\QueueSharing\\QueueSharing.csproj",
"src\\Servers\\HttpSys\\samples\\SelfHostServer\\SelfHostServer.csproj",
Expand Down
4 changes: 4 additions & 0 deletions src/Servers/HttpSys/perf/Microbenchmarks/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<OutputType>Exe</OutputType>
<ServerGarbageCollection>true</ServerGarbageCollection>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TieredCompilation>false</TieredCompilation>
<DefineConstants>$(DefineConstants);IS_BENCHMARKS</DefineConstants>
</PropertyGroup>

<ItemGroup>
<Reference Include="BenchmarkDotNet" />
<Reference Include="Microsoft.AspNetCore.Server.HttpSys" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(SharedSourceRoot)BenchmarkRunner\*.cs" />
</ItemGroup>

</Project>
13 changes: 13 additions & 0 deletions src/Servers/HttpSys/perf/Microbenchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Compile the solution in Release mode (so Kestrel is available in release)

```
build.cmd /p:Configuration=Release
```

To run a specific benchmark add it as parameter

```
dotnet run -c Release --filter RequestHeaderBenchmarks*
```

Using no parameter will list all available benchmarks
130 changes: 130 additions & 0 deletions src/Servers/HttpSys/perf/Microbenchmarks/RequestHeaderBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Runtime.InteropServices;
using System.Text;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.HttpSys.Internal;
using RequestHeaders = Microsoft.AspNetCore.HttpSys.Internal.RequestHeaders;

[SimpleJob, MemoryDiagnoser]
public class RequestHeaderBenchmarks
{
private RequestHeaders _largeRequestHeaders;
private RequestHeaders _smallRequestHeaders;

[GlobalSetup]
public unsafe void Setup()
{
_largeRequestHeaders = CreateRequestHeader(49);
_smallRequestHeaders = CreateRequestHeader(0);
}

[Benchmark]
public int CountSingleHeader()
{
_smallRequestHeaders.ResetFlags();
return _smallRequestHeaders.Count;
}

[Benchmark]
public int CountLargeHeaders()
{
_largeRequestHeaders.ResetFlags();
return _largeRequestHeaders.Count;
}

[Benchmark]
public ICollection<string> KeysSingleHeader()
{
_smallRequestHeaders.ResetFlags();
return _smallRequestHeaders.Keys;
}

[Benchmark]
public ICollection<string> KeysLargeHeaders()
{
_largeRequestHeaders.ResetFlags();
return _largeRequestHeaders.Keys;
}

private unsafe RequestHeaders CreateRequestHeader(int unknowHeaderCount)
{
var nativeContext = new NativeRequestContext(MemoryPool<byte>.Shared, null, 0, false);
var nativeMemory = new Span<byte>(nativeContext.NativeRequest, (int)nativeContext.Size + 8);

var requestStructure = new HttpApiTypes.HTTP_REQUEST();
var remainingMemory = SetUnknownHeaders(nativeMemory, ref requestStructure, GenerateUnknownHeaders(unknowHeaderCount));
SetHostHeader(remainingMemory, ref requestStructure);
MemoryMarshal.Write(nativeMemory, ref requestStructure);

var requestHeaders = new RequestHeaders(nativeContext);
nativeContext.ReleasePins();
return requestHeaders;
}

private unsafe Span<byte> SetHostHeader(Span<byte> nativeMemory, ref HttpApiTypes.HTTP_REQUEST requestStructure)
{
// Writing localhost to Host header
var dataDestination = nativeMemory.Slice(Marshal.SizeOf<HttpApiTypes.HTTP_REQUEST>());
int length = Encoding.ASCII.GetBytes("localhost:5001", dataDestination);
fixed (byte* address = &MemoryMarshal.GetReference(dataDestination))
{
requestStructure.Headers.KnownHeaders_29.pRawValue = address;
requestStructure.Headers.KnownHeaders_29.RawValueLength = (ushort)length;
}
return dataDestination;
}

/// <summary>
/// Writes an array HTTP_UNKNOWN_HEADER and an array of header key-value pairs to nativeMemory. Pointers in the HTTP_UNKNOWN_HEADER structure points to the corresponding key-value pair.
/// </summary>
private unsafe Span<byte> SetUnknownHeaders(Span<byte> nativeMemory, ref HttpApiTypes.HTTP_REQUEST requestStructure, IReadOnlyCollection<(string Key, string Value)> headerNames)
{
var unknownHeaderStructureDestination = nativeMemory.Slice(Marshal.SizeOf<HttpApiTypes.HTTP_REQUEST>());
fixed (byte* address = &MemoryMarshal.GetReference(unknownHeaderStructureDestination))
{
requestStructure.Headers.pUnknownHeaders = (HttpApiTypes.HTTP_UNKNOWN_HEADER*)address;
}
requestStructure.Headers.UnknownHeaderCount += (ushort)headerNames.Count;

var unknownHeadersSize = Marshal.SizeOf<HttpApiTypes.HTTP_UNKNOWN_HEADER>();
var dataDestination = unknownHeaderStructureDestination.Slice(unknownHeadersSize * headerNames.Count);
foreach (var headerName in headerNames)
{
var unknownHeaderStructure = new HttpApiTypes.HTTP_UNKNOWN_HEADER();
int nameLength = Encoding.ASCII.GetBytes(headerName.Key, dataDestination);
fixed (byte* address = &MemoryMarshal.GetReference(dataDestination))
{
unknownHeaderStructure.pName = address;
unknownHeaderStructure.NameLength = (ushort)nameLength;
}
dataDestination = dataDestination.Slice(nameLength);

if (!string.IsNullOrEmpty(headerName.Value))
{
int valueLength = Encoding.ASCII.GetBytes(headerName.Value, dataDestination);
fixed (byte* address = &MemoryMarshal.GetReference(dataDestination))
{
unknownHeaderStructure.pRawValue = address;
unknownHeaderStructure.RawValueLength = (ushort)valueLength;
}
dataDestination = dataDestination.Slice(nameLength);
}
MemoryMarshal.Write(unknownHeaderStructureDestination, ref unknownHeaderStructure);
unknownHeaderStructureDestination = unknownHeaderStructureDestination.Slice(unknownHeadersSize);
}
return dataDestination;
}

private IReadOnlyCollection<(string, string)> GenerateUnknownHeaders(int count)
{
var result = new List<(string, string)>();
for (int i = 0; i < count; i++)
{
result.Add(($"X-Custom-{i}", $"Value-{i}"));
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>ASP.NET Core HTTP server that uses the Windows HTTP Server API.</Description>
Expand Down Expand Up @@ -39,8 +39,10 @@
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Microsoft.AspNetCore.Server.HttpSys.Tests" />
<InternalsVisibleTo Include="Microsoft.AspNetCore.Server.HttpSys.FunctionalTests" />
<InternalsVisibleTo Include="Microsoft.AspNetCore.Server.HttpSys.NonHelixTests" />
<InternalsVisibleTo Include="Microsoft.AspNetCore.Server.HttpSys.Microbenchmarks" />
</ItemGroup>

<ItemGroup>
Expand Down
110 changes: 110 additions & 0 deletions src/Servers/HttpSys/test/FunctionalTests/RequestHeaderTests.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;

namespace Microsoft.AspNetCore.Server.HttpSys;

Expand Down Expand Up @@ -174,6 +177,70 @@ public async Task RequestHeaders_ClientSendTransferEncodingAndContentLength_Cont
}
}

[ConditionalFact]
public async Task RequestHeaders_AllKnownHeadersKeys_Received()
{
string customHeader = "X-KnownHeader";
using var server = Utilities.CreateHttpServer(out var address, httpContext =>
{
var requestHeaders = httpContext.Request.Headers;
Assert.Equal(3, requestHeaders.Count);
Assert.Equal(requestHeaders.Keys.Count, requestHeaders.Count);
Assert.Equal(requestHeaders.Count, requestHeaders.Values.Count);
Assert.Contains(customHeader, requestHeaders.Keys);
Assert.Contains(requestHeaders[customHeader].First(), requestHeaders.Keys);
Assert.Contains(HeaderNames.Host, requestHeaders.Keys);
return Task.FromResult(0);
});

foreach ((HttpSysRequestHeader Key, string Value) testRow in HeaderTestData())
{
var key = testRow.Key.ToString();
var headerDictionary = new Dictionary<string, string>
{
{ key, testRow.Value },
{ customHeader, key }
};
await SendRequestAsync(address, headerDictionary);
}
}

[ConditionalFact]
public async Task RequestHeaders_AllUnknownHeadersKeys_Received()
{
using var server = Utilities.CreateHttpServer(out var address, httpContext =>
{
var requestHeaders = httpContext.Request.Headers;
Assert.Equal(4, requestHeaders.Count);
Assert.Equal(requestHeaders.Keys.Count, requestHeaders.Count);
Assert.Equal(requestHeaders.Count, requestHeaders.Values.Count);
Assert.Contains("X-UnknownHeader-0", requestHeaders.Keys);
Assert.Contains("My-UnknownHeader-1", requestHeaders.Keys);
Assert.Contains("X-UnknownHeader-2", requestHeaders.Keys);
Assert.Contains(HeaderNames.Host, requestHeaders.Keys);
return Task.FromResult(0);
});

var headerDictionary = new Dictionary<string, string>
{
{ "X-UnknownHeader-0", "0" },
{ "My-UnknownHeader-1", "1" },
{ "X-UnknownHeader-2", "2" }
};
await SendRequestAsync(address, headerDictionary);
}

private async Task SendRequestAsync(string uri, IReadOnlyDictionary<string, string> headers)
{
HttpClient client = new HttpClient();
foreach (var header in headers)
{
client.DefaultRequestHeaders.Add(header.Key, header.Value);
}
var result = await client.GetAsync(uri);
result.EnsureSuccessStatusCode();
}

private async Task<string> SendRequestAsync(string uri)
{
using (HttpClient client = new HttpClient())
Expand Down Expand Up @@ -212,4 +279,47 @@ private async Task<string> SendRequestAsync(string address, IHeaderDictionary he
return Encoding.ASCII.GetString(response, 0, read);
}
}

private IEnumerable<(HttpSysRequestHeader, string)> HeaderTestData()
{
// Allow, Expires, TE are response headers, hence excluded from this enumeration.
// Host is sent by HttpClient, hence excluded from this enumeration.
yield return (HttpSysRequestHeader.CacheControl, HeaderNames.CacheControl);
yield return (HttpSysRequestHeader.Connection, HeaderNames.Connection);
yield return (HttpSysRequestHeader.Date, new DateTime(2022, 11, 14).ToString("r", CultureInfo.InvariantCulture));
yield return (HttpSysRequestHeader.KeepAlive, HeaderNames.KeepAlive);
yield return (HttpSysRequestHeader.Pragma, HeaderNames.Pragma);
yield return (HttpSysRequestHeader.Trailer, HeaderNames.Trailer);
yield return (HttpSysRequestHeader.TransferEncoding, HeaderNames.TransferEncoding);
yield return (HttpSysRequestHeader.Upgrade, HeaderNames.Upgrade);
yield return (HttpSysRequestHeader.Via, "1.1 localhost");
yield return (HttpSysRequestHeader.Warning, """199 - "just a test" """);
yield return (HttpSysRequestHeader.ContentLength, "1");
yield return (HttpSysRequestHeader.ContentType, "application/json");
yield return (HttpSysRequestHeader.ContentEncoding, "utf-8");
yield return (HttpSysRequestHeader.ContentLanguage, "en-US");
yield return (HttpSysRequestHeader.ContentLocation, HeaderNames.ContentLocation);
yield return (HttpSysRequestHeader.ContentMd5, HeaderNames.ContentMD5);
yield return (HttpSysRequestHeader.ContentRange, HeaderNames.ContentRange);
yield return (HttpSysRequestHeader.LastModified, HeaderNames.LastModified);
yield return (HttpSysRequestHeader.Accept, "*/*");
yield return (HttpSysRequestHeader.AcceptCharset, HeaderNames.AcceptCharset);
yield return (HttpSysRequestHeader.AcceptEncoding, HeaderNames.AcceptEncoding);
yield return (HttpSysRequestHeader.AcceptLanguage, HeaderNames.AcceptLanguage);
yield return (HttpSysRequestHeader.Authorization, HeaderNames.Authorization);
yield return (HttpSysRequestHeader.Cookie, HeaderNames.Cookie);
yield return (HttpSysRequestHeader.Expect, HeaderNames.Expect);
yield return (HttpSysRequestHeader.From, HeaderNames.From);
yield return (HttpSysRequestHeader.IfMatch, HeaderNames.IfMatch);
yield return (HttpSysRequestHeader.IfModifiedSince, HeaderNames.IfModifiedSince);
yield return (HttpSysRequestHeader.IfNoneMatch, HeaderNames.IfNoneMatch);
yield return (HttpSysRequestHeader.IfRange, HeaderNames.IfRange);
yield return (HttpSysRequestHeader.IfUnmodifiedSince, HeaderNames.IfUnmodifiedSince);
yield return (HttpSysRequestHeader.MaxForwards, HeaderNames.MaxForwards);
yield return (HttpSysRequestHeader.ProxyAuthorization, HeaderNames.ProxyAuthorization);
yield return (HttpSysRequestHeader.Referer, HeaderNames.Referer);
yield return (HttpSysRequestHeader.Range, "bytes=0-4096");
yield return (HttpSysRequestHeader.Translate, HeaderNames.Translate);
yield return (HttpSysRequestHeader.UserAgent, HeaderNames.UserAgent);
}
}