Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/aspnetcore.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrennan Conroy <brecon@microsoft.com>2022-11-12 03:49:06 +0300
committerBrennan Conroy <brecon@microsoft.com>2022-11-12 03:49:06 +0300
commit9c27c9af2db8c02d905398b1f4ff55998f752aa5 (patch)
tree95c047c21db4bcd79201928bb65764f2cefd1bca
parent04bb460447cc6b84ec88080a777b298ba70ce7d7 (diff)
Remove array allocation from multi-span header parsingbrecon/multispan
-rw-r--r--src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs58
-rw-r--r--src/Servers/Kestrel/Core/test/HttpParserTests.cs16
-rw-r--r--src/Servers/Kestrel/perf/Microbenchmarks/HttpParserBenchmark.cs66
3 files changed, 101 insertions, 39 deletions
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs
index 6bc2b7c168..6fb8c2c1e7 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs
@@ -288,6 +288,11 @@ public class HttpParser<TRequestHandler> : IHttpParser<TRequestHandler> where TR
// This was a multi-line header. Advance the reader.
reader.Advance(length);
+ if (reader.End)
+ {
+ return true;
+ }
+
continue;
}
@@ -384,36 +389,51 @@ public class HttpParser<TRequestHandler> : IHttpParser<TRequestHandler> where TR
header = currentSlice.Slice(reader.Position, lineEnd);
}
- var headerSpan = header.ToSpan();
+ byte[]? array = null;
+ int length = (int)header.Length;
+ Span<byte> span = length <= 256 ? stackalloc byte[length] : array = ArrayPool<byte>.Shared.Rent(length);
- // 'a:b\n' or 'a:b\r\n'
- var minHeaderSpan = _disableHttp1LineFeedTerminators ? 5 : 4;
- if (headerSpan.Length < minHeaderSpan)
+ try
{
- RejectRequestHeader(headerSpan);
- }
+ header.CopyTo(span);
+ span = span.Slice(0, length);
- var terminatorSize = -1;
+ // 'a:b\n' or 'a:b\r\n'
+ var minHeaderSpan = _disableHttp1LineFeedTerminators ? 5 : 4;
+ if (span.Length < minHeaderSpan)
+ {
+ RejectRequestHeader(span);
+ }
- if (headerSpan[^1] == ByteLF)
- {
- if (headerSpan[^2] == ByteCR)
+ var terminatorSize = -1;
+
+ if (span[^1] == ByteLF)
{
- terminatorSize = 2;
+ if (span[^2] == ByteCR)
+ {
+ terminatorSize = 2;
+ }
+ else if (!_disableHttp1LineFeedTerminators)
+ {
+ terminatorSize = 1;
+ }
}
- else if (!_disableHttp1LineFeedTerminators)
+
+ // Last chance to bail if the terminator size is not valid or the header doesn't parse.
+ if (terminatorSize == -1 || !TryTakeSingleHeader(handler, span.Slice(0, span.Length - terminatorSize)))
{
- terminatorSize = 1;
+ RejectRequestHeader(span);
}
- }
- // Last chance to bail if the terminator size is not valid or the header doesn't parse.
- if (terminatorSize == -1 || !TryTakeSingleHeader(handler, headerSpan.Slice(0, headerSpan.Length - terminatorSize)))
+ return span.Length;
+ }
+ finally
{
- RejectRequestHeader(headerSpan);
+ if (array is not null)
+ {
+ ArrayPool<byte>.Shared.Return(array);
+ }
}
-
- return headerSpan.Length;
}
private static bool TryTakeSingleHeader(TRequestHandler handler, ReadOnlySpan<byte> headerLine)
diff --git a/src/Servers/Kestrel/Core/test/HttpParserTests.cs b/src/Servers/Kestrel/Core/test/HttpParserTests.cs
index 553cdbdfc8..9b69d0cc98 100644
--- a/src/Servers/Kestrel/Core/test/HttpParserTests.cs
+++ b/src/Servers/Kestrel/Core/test/HttpParserTests.cs
@@ -679,6 +679,22 @@ public class HttpParserTests : LoggedTest
Assert.True(result);
}
+ [Fact]
+ public void ParseLargeHeaderLineWithGratuitouslySplitBuffers()
+ {
+ // Must be greater than the stackalloc we use (256) to test the array pool path
+ var stringLength = 300;
+ var headers = $"Host: {new string('a', stringLength)}\r\nConnection: keep-alive\r\n";
+ var parser = CreateParser(_nullTrace, false);
+ var buffer = BytePerSegmentTestSequenceFactory.Instance.CreateWithContent(headers);
+
+ var requestHandler = new RequestHandler();
+ var reader = new SequenceReader<byte>(buffer);
+ var result = parser.ParseHeaders(requestHandler, ref reader);
+
+ Assert.True(result);
+ }
+
private bool ParseRequestLine(IHttpParser<RequestHandler> parser, RequestHandler requestHandler, ReadOnlySequence<byte> readableBuffer, out SequencePosition consumed, out SequencePosition examined)
{
var reader = new SequenceReader<byte>(readableBuffer);
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/HttpParserBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/HttpParserBenchmark.cs
index 672aa710db..ee1d9b7eb1 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/HttpParserBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/HttpParserBenchmark.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
+using System.IO.Pipelines;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
@@ -12,43 +13,68 @@ public class HttpParserBenchmark : IHttpRequestLineHandler, IHttpHeadersHandler
private readonly HttpParser<Adapter> _parser = new HttpParser<Adapter>();
private ReadOnlySequence<byte> _buffer;
+ private ReadOnlySequence<byte> _multispanHeader;
- [Benchmark(Baseline = true, OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
- public void PlaintextTechEmpower()
+ [GlobalSetup]
+ public void Setup()
{
- for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
- {
- InsertData(RequestParsingData.PlaintextTechEmpowerRequest);
- ParseData();
- }
+ var segment = new BufferSegment();
+ var split = RequestParsingData.UnicodeRequest.Length / 2;
+ segment.SetOwnedMemory(RequestParsingData.UnicodeRequest.AsSpan(0, split).ToArray());
+ segment.End = split;
+ var next = new BufferSegment();
+ next.SetOwnedMemory(RequestParsingData.UnicodeRequest.AsSpan(split).ToArray());
+ next.End = split;
+ segment.SetNext(next);
+ _multispanHeader = new ReadOnlySequence<byte>(segment, 0, next, next.Memory.Length);
}
- [Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
- public void JsonTechEmpower()
- {
- for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
- {
- InsertData(RequestParsingData.JsonTechEmpowerRequest);
- ParseData();
- }
- }
+ //[Benchmark(Baseline = true, OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
+ //public void PlaintextTechEmpower()
+ //{
+ // for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
+ // {
+ // InsertData(RequestParsingData.PlaintextTechEmpowerRequest);
+ // ParseData();
+ // }
+ //}
+
+ //[Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
+ //public void JsonTechEmpower()
+ //{
+ // for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
+ // {
+ // InsertData(RequestParsingData.JsonTechEmpowerRequest);
+ // ParseData();
+ // }
+ //}
+
+ //[Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
+ //public void LiveAspNet()
+ //{
+ // for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
+ // {
+ // InsertData(RequestParsingData.LiveaspnetRequest);
+ // ParseData();
+ // }
+ //}
[Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
- public void LiveAspNet()
+ public void Unicode()
{
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
{
- InsertData(RequestParsingData.LiveaspnetRequest);
+ InsertData(RequestParsingData.UnicodeRequest);
ParseData();
}
}
[Benchmark(OperationsPerInvoke = RequestParsingData.InnerLoopCount)]
- public void Unicode()
+ public void MultispanUnicodeHeader()
{
for (var i = 0; i < RequestParsingData.InnerLoopCount; i++)
{
- InsertData(RequestParsingData.UnicodeRequest);
+ _buffer = _multispanHeader;
ParseData();
}
}