Skip to content

Commit 78e6fcd

Browse files
Begin to reduce parsing allocations
1 parent 94e530a commit 78e6fcd

File tree

4 files changed

+134
-45
lines changed

4 files changed

+134
-45
lines changed

Engine/Protocol/RequestBuilder.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ public RequestBuilder Protocol(string version)
8888
return this;
8989
}
9090

91-
public RequestBuilder Type(string type)
91+
public RequestBuilder Type(FlexibleRequestMethod type)
9292
{
93-
_RequestMethod = FlexibleRequestMethod.Get(type);
93+
_RequestMethod = type;
9494
return this;
9595
}
9696

Engine/Protocol/RequestParser.cs

+124-38
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
using System;
1+
using GenHTTP.Api.Protocol;
2+
using GenHTTP.Api.Routing;
3+
using GenHTTP.Engine.Infrastructure.Configuration;
4+
using PooledAwait;
5+
using System;
26
using System.Buffers;
37
using System.Collections.Generic;
48
using System.Text;
59
using System.Text.RegularExpressions;
610
using System.Threading.Tasks;
711

8-
using GenHTTP.Api.Protocol;
9-
using GenHTTP.Api.Routing;
10-
11-
using GenHTTP.Engine.Infrastructure.Configuration;
12-
using PooledAwait;
13-
1412
namespace GenHTTP.Engine.Protocol
1513
{
1614

@@ -29,6 +27,8 @@ internal sealed class RequestParser
2927
private static readonly char[] LINE_ENDING = new char[] { '\r' };
3028
private static readonly char[] PATH_ENDING = new char[] { '\r', '\n', '?', ' ' };
3129

30+
private static readonly string[] KNOWN_HEADERS = new[] { "Host", "User-Agent", "Accept" };
31+
3232
private static readonly Encoding ASCII = Encoding.ASCII;
3333

3434
private RequestBuilder? _Builder;
@@ -52,14 +52,16 @@ internal RequestParser(NetworkConfiguration configuration)
5252

5353
#region Functionality
5454

55-
internal async ValueTask<RequestBuilder?> TryParseAsync(RequestBuffer buffer)
55+
internal async PooledValueTask<RequestBuilder?> TryParseAsync(RequestBuffer buffer)
5656
{
5757
WebPath? path;
5858
RequestQuery? query;
5959

60-
string? method, protocol;
60+
FlexibleRequestMethod? method;
6161

62-
if ((method = await ReadToken(buffer, ' ').ConfigureAwait(false)) is not null)
62+
string? protocol;
63+
64+
if ((method = await TryReadMethod(buffer)) is not null)
6365
{
6466
Request.Type(method);
6567
}
@@ -82,7 +84,7 @@ internal RequestParser(NetworkConfiguration configuration)
8284
Request.Query(query);
8385
}
8486

85-
if ((protocol = await ReadToken(buffer, '\r', 1, 5)) is not null)
87+
if ((protocol = await TryReadProtocolVersion(buffer)) is not null)
8688
{
8789
Request.Protocol(protocol);
8890
}
@@ -121,6 +123,49 @@ internal RequestParser(NetworkConfiguration configuration)
121123
return result;
122124
}
123125

126+
private static async PooledValueTask<FlexibleRequestMethod?> TryReadMethod(RequestBuffer buffer)
127+
{
128+
if (await FindPosition(buffer, ' ').ConfigureAwait(false) is null)
129+
{
130+
return null;
131+
}
132+
133+
if (CompareTo(buffer, "GET", 1)) return FlexibleRequestMethod.Get(RequestMethod.GET);
134+
135+
var token = await ReadToken(buffer, ' ');
136+
137+
if (token is not null)
138+
{
139+
return FlexibleRequestMethod.Get(token);
140+
}
141+
142+
return null;
143+
}
144+
145+
private static async PooledValueTask<string?> TryReadProtocolVersion(RequestBuffer buffer)
146+
{
147+
// skip the "HTTP/" part
148+
buffer.Advance(5);
149+
150+
if (await FindPosition(buffer, '\r').ConfigureAwait(false) is null)
151+
{
152+
return null;
153+
}
154+
155+
if (CompareTo(buffer, "1.1", 2))
156+
{
157+
return "1.1";
158+
}
159+
else if (CompareTo(buffer, "1.0", 2))
160+
{
161+
return "1.0";
162+
}
163+
else
164+
{
165+
return null;
166+
}
167+
}
168+
124169
private static async ValueTask<WebPath?> TryReadPath(RequestBuffer buffer)
125170
{
126171
// ignore the slash at the beginning
@@ -180,41 +225,31 @@ private static async ValueTask<bool> TryReadHeader(RequestBuffer buffer, Request
180225
{
181226
string? key, value;
182227

183-
if ((key = await ReadToken(buffer, ':', 1).ConfigureAwait(false)) is not null)
228+
if (await FindPosition(buffer, ':').ConfigureAwait(false) is not null)
184229
{
185-
if ((value = await ReadToken(buffer, '\r', 1)) is not null)
230+
foreach (var knownHeader in KNOWN_HEADERS)
186231
{
187-
request.Header(key, value);
188-
return true;
189-
}
190-
}
191-
192-
return false;
193-
}
194-
195-
private static async PooledValueTask<SequencePosition?> FindPosition(RequestBuffer buffer, char delimiter)
196-
{
197-
if (buffer.ReadRequired)
198-
{
199-
if ((await buffer.Read().ConfigureAwait(false)) is null)
200-
{
201-
return null;
232+
if (CompareTo(buffer, knownHeader, 2))
233+
{
234+
if ((value = await ReadToken(buffer, '\r', 1)) is not null)
235+
{
236+
request.Header(knownHeader, value);
237+
return true;
238+
}
239+
}
202240
}
203-
}
204-
205-
var position = buffer.Data.PositionOf((byte)delimiter);
206241

207-
if (position is null)
208-
{
209-
if ((await buffer.Read(true).ConfigureAwait(false)) is null)
242+
if ((key = await ReadToken(buffer, ':', 1)) is not null)
210243
{
211-
return null;
244+
if ((value = await ReadToken(buffer, '\r', 1)) is not null)
245+
{
246+
request.Header(key, value);
247+
return true;
248+
}
212249
}
213-
214-
position = buffer.Data.PositionOf((byte)delimiter);
215250
}
216251

217-
return position;
252+
return false;
218253
}
219254

220255
private static ValueTask<string?> ReadToken(RequestBuffer buffer, char delimiter, ushort skipNext = 0, ushort skipFirst = 0, bool skipDelimiter = true)
@@ -268,6 +303,57 @@ private static async ValueTask<bool> TryReadHeader(RequestBuffer buffer, Request
268303
return null;
269304
}
270305

306+
private static bool CompareTo(RequestBuffer buffer, string expected, ushort skipNext = 0)
307+
{
308+
var i = 0;
309+
310+
var slice = buffer.Data.Slice(0, expected.Length);
311+
312+
if (slice.Length != expected.Length)
313+
{
314+
return false;
315+
}
316+
317+
foreach (var segment in slice)
318+
{
319+
for (int j = 0; j < segment.Length; j++)
320+
{
321+
if (segment.Span[j] != expected[i++])
322+
{
323+
return false;
324+
}
325+
}
326+
}
327+
328+
buffer.Advance(expected.Length + skipNext);
329+
return true;
330+
}
331+
332+
private static async PooledValueTask<SequencePosition?> FindPosition(RequestBuffer buffer, char delimiter)
333+
{
334+
if (buffer.ReadRequired)
335+
{
336+
if ((await buffer.Read().ConfigureAwait(false)) is null)
337+
{
338+
return null;
339+
}
340+
}
341+
342+
var position = buffer.Data.PositionOf((byte)delimiter);
343+
344+
if (position is null)
345+
{
346+
if ((await buffer.Read(true).ConfigureAwait(false)) is null)
347+
{
348+
return null;
349+
}
350+
351+
position = buffer.Data.PositionOf((byte)delimiter);
352+
}
353+
354+
return position;
355+
}
356+
271357
private static string GetString(ReadOnlySequence<byte> buffer)
272358
{
273359
return string.Create((int)buffer.Length, buffer, (span, sequence) =>

Modules/ClientCaching/Validation/CacheValidationHandler.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public sealed class CacheValidationHandler : IConcern
1414
{
1515
private const string ETAG_HEADER = "ETag";
1616

17+
private static readonly RequestMethod[] _SupportedMethods = new[] { RequestMethod.GET, RequestMethod.HEAD };
18+
1719
#region Get-/Setters
1820

1921
public IHandler Parent { get; }
@@ -38,7 +40,7 @@ public CacheValidationHandler(IHandler parent, Func<IHandler, IHandler> contentF
3840
{
3941
var response = await Content.HandleAsync(request).ConfigureAwait(false);
4042

41-
if (request.HasType(RequestMethod.GET, RequestMethod.HEAD))
43+
if (request.HasType(_SupportedMethods))
4244
{
4345
if ((response is not null) && (response.Content is not null))
4446
{

Playground/Program.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
Host.Create()
77
.Handler(Content.From(Resource.FromString("Hello World")))
8-
.Defaults()
9-
.Development()
10-
.Console()
11-
.Run();
8+
//.Defaults()
9+
//.Development()
10+
//.Console()
11+
.Run();
12+

0 commit comments

Comments
 (0)