Skip to content

Commit 4c1a6ba

Browse files
Re-introduce a scanner to lower allocations and cleanup code
1 parent 78e6fcd commit 4c1a6ba

20 files changed

+762
-542
lines changed

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,20 @@
1+
using System.Buffers;
2+
3+
using GenHTTP.Api.Protocol;
4+
5+
namespace GenHTTP.Engine.Protocol.Parser.Conversion
6+
{
7+
8+
internal class MethodConverter
9+
{
10+
11+
internal static FlexibleRequestMethod ToRequestMethod(ReadOnlySequence<byte> value)
12+
{
13+
if (ValueConverter.CompareTo(value, "GET")) return FlexibleRequestMethod.Get(RequestMethod.GET);
14+
15+
return FlexibleRequestMethod.Get(ValueConverter.GetString(value));
16+
}
17+
18+
}
19+
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System.Buffers;
2+
using System.Collections.Generic;
3+
4+
using GenHTTP.Api.Routing;
5+
6+
namespace GenHTTP.Engine.Protocol.Parser.Conversion
7+
{
8+
9+
internal static class PathConverter
10+
{
11+
12+
private static readonly WebPath ROOT = new(new List<WebPathPart>(), true);
13+
14+
internal static WebPath ToPath(ReadOnlySequence<byte> value)
15+
{
16+
if (value.Length == 1)
17+
{
18+
return ROOT;
19+
}
20+
21+
var reader = new SequenceReader<byte>(value);
22+
23+
reader.Advance(1);
24+
25+
var parts = new List<WebPathPart>(4);
26+
27+
while (reader.TryReadTo(out ReadOnlySequence<byte> segment, (byte)'/'))
28+
{
29+
parts.Add(new(ValueConverter.GetString(segment)));
30+
}
31+
32+
if (!reader.End)
33+
{
34+
var remainder = reader.Sequence.Slice(reader.Position);
35+
parts.Add(new(ValueConverter.GetString(remainder)));
36+
37+
return new WebPath(parts, false);
38+
}
39+
40+
return new WebPath(parts, true);
41+
}
42+
43+
}
44+
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Buffers;
2+
3+
using GenHTTP.Api.Protocol;
4+
5+
namespace GenHTTP.Engine.Protocol.Parser.Conversion
6+
{
7+
8+
internal static class ProtocolConverter
9+
{
10+
11+
internal static HttpProtocol ToProtocol(ReadOnlySequence<byte> value)
12+
{
13+
var reader = new SequenceReader<byte>(value);
14+
15+
if (value.Length != 8)
16+
{
17+
throw new ProtocolException("HTTP protocol version expected");
18+
}
19+
20+
reader.Advance(5);
21+
22+
var version = reader.Sequence.Slice(reader.Position);
23+
24+
if (ValueConverter.CompareTo(version, "1.1"))
25+
{
26+
return HttpProtocol.Http_1_1;
27+
}
28+
else if (ValueConverter.CompareTo(version, "1.0"))
29+
{
30+
return HttpProtocol.Http_1_0;
31+
}
32+
33+
var versionString = ValueConverter.GetString(version);
34+
35+
throw new ProtocolException($"Unexpected protocol version '{versionString}'");
36+
}
37+
38+
}
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
using System.Buffers;
3+
4+
namespace GenHTTP.Engine.Protocol.Parser.Conversion
5+
{
6+
7+
internal static class QueryConverter
8+
{
9+
10+
internal static RequestQuery? ToQuery(ReadOnlySequence<byte> value)
11+
{
12+
if (!value.IsEmpty)
13+
{
14+
var query = new RequestQuery();
15+
16+
var reader = new SequenceReader<byte>(value);
17+
18+
while (reader.TryReadTo(out ReadOnlySequence<byte> segment, (byte)'&'))
19+
{
20+
AppendSegment(query, segment);
21+
}
22+
23+
if (!reader.End)
24+
{
25+
var remainder = reader.Sequence.Slice(reader.Position);
26+
AppendSegment(query, remainder);
27+
}
28+
29+
return query;
30+
}
31+
32+
return null;
33+
}
34+
35+
private static void AppendSegment(RequestQuery query, ReadOnlySequence<byte> segment)
36+
{
37+
if (!segment.IsEmpty)
38+
{
39+
var reader = new SequenceReader<byte>(segment);
40+
41+
string? name, value = null;
42+
43+
if (reader.TryReadTo(out ReadOnlySequence<byte> firstSegment, (byte)'='))
44+
{
45+
name = ValueConverter.GetString(firstSegment);
46+
47+
if (!reader.End)
48+
{
49+
var remainingValue = reader.Sequence.Slice(reader.Position);
50+
value = ValueConverter.GetString(remainingValue);
51+
}
52+
}
53+
else
54+
{
55+
var remainingName = reader.Sequence.Slice(reader.Position);
56+
name = ValueConverter.GetString(remainingName);
57+
}
58+
59+
query[Uri.UnescapeDataString(name)] = (value != null) ? Uri.UnescapeDataString(value) : string.Empty;
60+
}
61+
}
62+
63+
}
64+
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.Buffers;
2+
using System.Text;
3+
4+
namespace GenHTTP.Engine.Protocol.Parser.Conversion
5+
{
6+
7+
internal static class ValueConverter
8+
{
9+
10+
private static readonly Encoding ASCII = Encoding.ASCII;
11+
12+
internal static bool CompareTo(ReadOnlySequence<byte> buffer, string expected)
13+
{
14+
var i = 0;
15+
16+
if (buffer.Length != expected.Length)
17+
{
18+
return false;
19+
}
20+
21+
foreach (var segment in buffer)
22+
{
23+
for (int j = 0; j < segment.Length; j++)
24+
{
25+
if (segment.Span[j] != expected[i++])
26+
{
27+
return false;
28+
}
29+
}
30+
}
31+
32+
return true;
33+
}
34+
35+
internal static string GetString(ReadOnlySequence<byte> buffer)
36+
{
37+
return string.Create((int)buffer.Length, buffer, (span, sequence) =>
38+
{
39+
foreach (var segment in sequence)
40+
{
41+
ASCII.GetChars(segment.Span, span);
42+
span = span[segment.Length..];
43+
}
44+
});
45+
}
46+
47+
}
48+
49+
}

0 commit comments

Comments
 (0)