Skip to content

Commit e95724b

Browse files
Further reduce allocations (#477)
1 parent 68f6aa1 commit e95724b

28 files changed

+1066
-696
lines changed

API/Routing/RoutingTarget.cs

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System;
2-
using System.Linq;
2+
using System.Collections.Generic;
33

44
namespace GenHTTP.Api.Routing
55
{
@@ -13,6 +13,8 @@ namespace GenHTTP.Api.Routing
1313
/// </remarks>
1414
public sealed class RoutingTarget
1515
{
16+
private static readonly List<WebPathPart> EMPTY_LIST = new();
17+
1618
private int _Index = 0;
1719

1820
#region Get-/Setters
@@ -72,7 +74,19 @@ public void Advance()
7274
/// Retrieves the part of the path that still needs to be routed.
7375
/// </summary>
7476
/// <returns>The remaining part of the path</returns>
75-
public WebPath GetRemaining() => new(Path.Parts.Skip(_Index).ToList(), Path.TrailingSlash);
77+
public WebPath GetRemaining()
78+
{
79+
var remaining = Path.Parts.Count - _Index;
80+
81+
var resultList = (remaining > 0) ? new List<WebPathPart>(remaining) : EMPTY_LIST;
82+
83+
for (int i = _Index; i < Path.Parts.Count; i++)
84+
{
85+
resultList.Add(Path.Parts[i]);
86+
}
87+
88+
return new(resultList, Path.TrailingSlash);
89+
}
7690

7791
#endregion
7892

Engine/GenHTTP.Engine.csproj

+67-66
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,68 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
2-
3-
<PropertyGroup>
4-
5-
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
6-
7-
<LangVersion>10.0</LangVersion>
8-
<Nullable>enable</Nullable>
9-
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
10-
11-
<AssemblyVersion>8.3.0.0</AssemblyVersion>
12-
<FileVersion>8.3.0.0</FileVersion>
13-
<Version>8.3.0</Version>
14-
15-
<Authors>Andreas Nägeli</Authors>
16-
<Company />
17-
18-
<PackageLicenseFile>LICENSE</PackageLicenseFile>
19-
<PackageProjectUrl>https://genhttp.org/</PackageProjectUrl>
20-
21-
<Description>Lightweight web server written in pure C# with few dependencies to 3rd-party libraries.</Description>
22-
<PackageTags>HTTP Embedded Webserver Website Server Library C# Standard Engine</PackageTags>
23-
24-
<PublishRepositoryUrl>true</PublishRepositoryUrl>
25-
<IncludeSymbols>true</IncludeSymbols>
26-
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
27-
28-
<GenerateDocumentationFile>true</GenerateDocumentationFile>
29-
<NoWarn>CS1591,CS1587,CS1572,CS1573</NoWarn>
30-
31-
<PackageIcon>icon.png</PackageIcon>
32-
33-
</PropertyGroup>
34-
35-
<ItemGroup>
36-
<None Remove="Resources\Error.html" />
37-
<None Remove="Resources\ErrorStacked.html" />
38-
<None Remove="Resources\Template.html" />
39-
</ItemGroup>
40-
41-
<ItemGroup>
42-
<EmbeddedResource Include="Resources\ErrorStacked.html" />
43-
<EmbeddedResource Include="Resources\Error.html" />
44-
<EmbeddedResource Include="Resources\Template.html" />
45-
</ItemGroup>
46-
47-
<ItemGroup>
48-
49-
<None Include="..\LICENSE" Pack="true" PackagePath="\" />
50-
<None Include="..\Resources\icon.png" Pack="true" PackagePath="\" />
51-
52-
</ItemGroup>
53-
54-
<ItemGroup>
55-
56-
<ProjectReference Include="..\API\GenHTTP.Api.csproj" />
57-
<ProjectReference Include="..\Modules\IO\GenHTTP.Modules.IO.csproj" />
58-
<ProjectReference Include="..\Modules\Placeholders\GenHTTP.Modules.Placeholders.csproj" />
59-
<ProjectReference Include="..\Modules\ErrorHandling\GenHTTP.Modules.ErrorHandling.csproj" />
60-
61-
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
62-
63-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
64-
65-
</ItemGroup>
66-
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
5+
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
6+
7+
<LangVersion>10.0</LangVersion>
8+
<Nullable>enable</Nullable>
9+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
10+
11+
<AssemblyVersion>8.3.0.0</AssemblyVersion>
12+
<FileVersion>8.3.0.0</FileVersion>
13+
<Version>8.3.0</Version>
14+
15+
<Authors>Andreas Nägeli</Authors>
16+
<Company />
17+
18+
<PackageLicenseFile>LICENSE</PackageLicenseFile>
19+
<PackageProjectUrl>https://genhttp.org/</PackageProjectUrl>
20+
21+
<Description>Lightweight web server written in pure C# with few dependencies to 3rd-party libraries.</Description>
22+
<PackageTags>HTTP Embedded Webserver Website Server Library C# Standard Engine</PackageTags>
23+
24+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
25+
<IncludeSymbols>true</IncludeSymbols>
26+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
27+
28+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
29+
<NoWarn>CS1591,CS1587,CS1572,CS1573</NoWarn>
30+
31+
<PackageIcon>icon.png</PackageIcon>
32+
33+
</PropertyGroup>
34+
35+
<ItemGroup>
36+
<None Remove="Resources\Error.html" />
37+
<None Remove="Resources\ErrorStacked.html" />
38+
<None Remove="Resources\Template.html" />
39+
</ItemGroup>
40+
41+
<ItemGroup>
42+
<EmbeddedResource Include="Resources\ErrorStacked.html" />
43+
<EmbeddedResource Include="Resources\Error.html" />
44+
<EmbeddedResource Include="Resources\Template.html" />
45+
</ItemGroup>
46+
47+
<ItemGroup>
48+
49+
<None Include="..\LICENSE" Pack="true" PackagePath="\" />
50+
<None Include="..\Resources\icon.png" Pack="true" PackagePath="\" />
51+
52+
</ItemGroup>
53+
54+
<ItemGroup>
55+
56+
<ProjectReference Include="..\API\GenHTTP.Api.csproj" />
57+
<ProjectReference Include="..\Modules\IO\GenHTTP.Modules.IO.csproj" />
58+
<ProjectReference Include="..\Modules\Placeholders\GenHTTP.Modules.Placeholders.csproj" />
59+
<ProjectReference Include="..\Modules\ErrorHandling\GenHTTP.Modules.ErrorHandling.csproj" />
60+
<PackageReference Include="PooledAwait" Version="1.0.49" />
61+
62+
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
63+
64+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
65+
66+
</ItemGroup>
67+
6768
</Project>

Engine/Infrastructure/Endpoints/EndPoint.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
using GenHTTP.Engine.Infrastructure.Configuration;
1111

12+
using PooledAwait;
13+
1214
namespace GenHTTP.Engine.Infrastructure.Endpoints
1315
{
1416

@@ -97,9 +99,9 @@ private void Handle(Socket client)
9799
.ConfigureAwait(false);
98100
}
99101

100-
protected abstract ValueTask Accept(Socket client);
102+
protected abstract PooledValueTask Accept(Socket client);
101103

102-
protected ValueTask Handle(Socket client, Stream inputStream)
104+
protected PooledValueTask Handle(Socket client, Stream inputStream)
103105
{
104106
return new ClientHandler(client, inputStream, Server, this, Configuration).Run();
105107
}

Engine/Infrastructure/Endpoints/InsecureEndPoint.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
using System.Net;
22
using System.Net.Sockets;
3-
using System.Threading.Tasks;
43

54
using GenHTTP.Api.Infrastructure;
65

76
using GenHTTP.Engine.Infrastructure.Configuration;
87
using GenHTTP.Engine.Utilities;
98

9+
using PooledAwait;
10+
1011
namespace GenHTTP.Engine.Infrastructure.Endpoints
1112
{
1213

@@ -31,7 +32,7 @@ internal InsecureEndPoint(IServer server, IPEndPoint endPoint, NetworkConfigurat
3132

3233
#region Functionality
3334

34-
protected override ValueTask Accept(Socket client) => Handle(client, new PoolBufferedStream(new NetworkStream(client)));
35+
protected override PooledValueTask Accept(Socket client) => Handle(client, new PoolBufferedStream(new NetworkStream(client)));
3536

3637
#endregion
3738

Engine/Infrastructure/Endpoints/SecureEndPoint.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
using GenHTTP.Engine.Infrastructure.Configuration;
1212
using GenHTTP.Engine.Utilities;
1313

14+
using PooledAwait;
15+
1416
namespace GenHTTP.Engine.Infrastructure.Endpoints
1517
{
1618

@@ -50,7 +52,7 @@ internal SecureEndPoint(IServer server, IPEndPoint endPoint, SecurityConfigurati
5052

5153
#region Functionality
5254

53-
protected override async ValueTask Accept(Socket client)
55+
protected override async PooledValueTask Accept(Socket client)
5456
{
5557
var stream = await TryAuthenticate(client).ConfigureAwait(false);
5658

Engine/Protocol/ClientHandler.cs

+12-10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
using GenHTTP.Engine.Infrastructure.Configuration;
1212
using GenHTTP.Engine.Protocol;
13+
using GenHTTP.Engine.Protocol.Parser;
14+
15+
using PooledAwait;
1316

1417
namespace GenHTTP.Engine
1518
{
@@ -40,6 +43,8 @@ internal sealed class ClientHandler
4043

4144
private bool? KeepAlive { get; set; }
4245

46+
private ResponseHandler ResponseHandler { get; set; }
47+
4348
#endregion
4449

4550
#region Initialization
@@ -53,13 +58,15 @@ internal ClientHandler(Socket socket, Stream stream, IServer server, IEndPoint e
5358
Connection = socket;
5459

5560
Stream = stream;
61+
62+
ResponseHandler = new ResponseHandler(Server, Stream, Connection, Configuration);
5663
}
5764

5865
#endregion
5966

6067
#region Functionality
6168

62-
internal async ValueTask Run()
69+
internal async PooledValueTask Run()
6370
{
6471
try
6572
{
@@ -98,7 +105,7 @@ internal async ValueTask Run()
98105
}
99106
}
100107

101-
private async ValueTask HandlePipe(PipeReader reader)
108+
private async PooledValueTask HandlePipe(PipeReader reader)
102109
{
103110
try
104111
{
@@ -122,24 +129,19 @@ private async ValueTask HandlePipe(PipeReader reader)
122129
}
123130
}
124131

125-
private async ValueTask<bool> HandleRequest(RequestBuilder builder)
132+
private async PooledValueTask<bool> HandleRequest(RequestBuilder builder)
126133
{
127134
var address = (Connection.RemoteEndPoint as IPEndPoint)?.Address;
128135

129136
using var request = builder.Connection(Server, EndPoint, address).Build();
130137

131-
if (KeepAlive is null)
132-
{
133-
KeepAlive = request["Connection"]?.Equals("Keep-Alive", StringComparison.InvariantCultureIgnoreCase) ?? true;
134-
}
138+
KeepAlive ??= request["Connection"]?.Equals("Keep-Alive", StringComparison.InvariantCultureIgnoreCase) ?? true;
135139

136140
bool keepAlive = (bool)KeepAlive;
137141

138-
var responseHandler = new ResponseHandler(Server, Stream, Connection, Configuration);
139-
140142
using var response = await Server.Handler.HandleAsync(request).ConfigureAwait(false) ?? throw new InvalidOperationException("The root request handler did not return a response");
141143

142-
var success = await responseHandler.Handle(request, response, keepAlive);
144+
var success = await ResponseHandler.Handle(request, response, keepAlive);
143145

144146
if (!success || !keepAlive)
145147
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System.Buffers;
2+
3+
namespace GenHTTP.Engine.Protocol.Parser.Conversion
4+
{
5+
6+
internal static class HeaderConverter
7+
{
8+
private static readonly string[] KNOWN_HEADERS = new[] { "Host", "User-Agent", "Accept", "Content-Type", "Content-Length" };
9+
10+
private static readonly string[] KNOWN_VALUES = new[] { "*/*" };
11+
12+
internal static string ToKey(ReadOnlySequence<byte> value)
13+
{
14+
foreach (var known in KNOWN_HEADERS)
15+
{
16+
if (ValueConverter.CompareTo(value, known)) return known;
17+
}
18+
19+
return ValueConverter.GetString(value);
20+
}
21+
22+
internal static string ToValue(ReadOnlySequence<byte> value)
23+
{
24+
foreach (var known in KNOWN_VALUES)
25+
{
26+
if (ValueConverter.CompareTo(value, known)) return known;
27+
}
28+
29+
return ValueConverter.GetString(value);
30+
}
31+
32+
}
33+
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System.Buffers;
2+
using System.Collections.Generic;
3+
4+
using GenHTTP.Api.Protocol;
5+
6+
namespace GenHTTP.Engine.Protocol.Parser.Conversion
7+
{
8+
9+
internal static class MethodConverter
10+
{
11+
private static readonly Dictionary<string, RequestMethod> KNOWN_METHODS = new(7)
12+
{
13+
{ "GET", RequestMethod.GET },
14+
{ "HEAD", RequestMethod.HEAD },
15+
{ "POST", RequestMethod.POST },
16+
{ "PUT", RequestMethod.PUT },
17+
{ "PATCH", RequestMethod.PATCH },
18+
{ "DELETE", RequestMethod.DELETE },
19+
{ "OPTIONS", RequestMethod.OPTIONS }
20+
};
21+
22+
internal static FlexibleRequestMethod ToRequestMethod(ReadOnlySequence<byte> value)
23+
{
24+
foreach (var kv in KNOWN_METHODS)
25+
{
26+
if (ValueConverter.CompareTo(value, kv.Key))
27+
{
28+
return FlexibleRequestMethod.Get(kv.Value);
29+
}
30+
}
31+
32+
return FlexibleRequestMethod.Get(ValueConverter.GetString(value));
33+
}
34+
35+
}
36+
37+
}

0 commit comments

Comments
 (0)