[Platform] Speedups (#2569)
- Move ProcessRequest into ParseHttpRequest (out of state machine) - Use struct adapter for parsing - Non-virtual calls - RequestType to enum - Readonly statics and early init statics - Early init path statics - Faster Json fall through - CountingBufferWriter -> Smaller BufferWriter - Pre-init date static - Smaller WriteNumeric
This commit is contained in:
parent
84ca10a45d
commit
c52a3bf534
|
|
@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
|
||||||
namespace PlatformBenchmarks
|
namespace PlatformBenchmarks
|
||||||
{
|
{
|
||||||
public struct AsciiString : IEquatable<AsciiString>
|
public readonly struct AsciiString : IEquatable<AsciiString>
|
||||||
{
|
{
|
||||||
private readonly byte[] _data;
|
private readonly byte[] _data;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
|
|
||||||
|
namespace PlatformBenchmarks
|
||||||
|
{
|
||||||
|
public partial class BenchmarkApplication : IHttpConnection
|
||||||
|
{
|
||||||
|
private State _state;
|
||||||
|
|
||||||
|
public PipeReader Reader { get; set; }
|
||||||
|
public PipeWriter Writer { get; set; }
|
||||||
|
|
||||||
|
private HttpParser<ParsingAdapter> Parser { get; } = new HttpParser<ParsingAdapter>();
|
||||||
|
|
||||||
|
public async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ProcessRequestsAsync();
|
||||||
|
|
||||||
|
Reader.Complete();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Reader.Complete(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Writer.Complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessRequestsAsync()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var task = Reader.ReadAsync();
|
||||||
|
|
||||||
|
if (!task.IsCompleted)
|
||||||
|
{
|
||||||
|
// No more data in the input
|
||||||
|
await OnReadCompletedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await task;
|
||||||
|
if (!ParseHttpRequest(ref result))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_state == State.Body)
|
||||||
|
{
|
||||||
|
await ProcessRequestAsync();
|
||||||
|
|
||||||
|
_state = State.StartLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be `in` but ReadResult isn't readonly struct
|
||||||
|
private bool ParseHttpRequest(ref ReadResult result)
|
||||||
|
{
|
||||||
|
var buffer = result.Buffer;
|
||||||
|
var consumed = buffer.Start;
|
||||||
|
var examined = buffer.End;
|
||||||
|
var state = _state;
|
||||||
|
|
||||||
|
if (!buffer.IsEmpty)
|
||||||
|
{
|
||||||
|
var parsingStartLine = state == State.StartLine;
|
||||||
|
if (parsingStartLine)
|
||||||
|
{
|
||||||
|
if (Parser.ParseRequestLine(new ParsingAdapter(this), buffer, out consumed, out examined))
|
||||||
|
{
|
||||||
|
state = State.Headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == State.Headers)
|
||||||
|
{
|
||||||
|
if (parsingStartLine)
|
||||||
|
{
|
||||||
|
buffer = buffer.Slice(consumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Parser.ParseHeaders(new ParsingAdapter(this), buffer, out consumed, out examined, out int consumedBytes))
|
||||||
|
{
|
||||||
|
state = State.Body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state != State.Body && result.IsCompleted)
|
||||||
|
{
|
||||||
|
ThrowUnexpectedEndOfData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (result.IsCompleted)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_state = state;
|
||||||
|
Reader.AdvanceTo(consumed, examined);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnHeader(Span<byte> name, Span<byte> value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnReadCompletedAsync()
|
||||||
|
{
|
||||||
|
await Writer.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ThrowUnexpectedEndOfData()
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Unexpected end of data!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
StartLine,
|
||||||
|
Headers,
|
||||||
|
Body
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static BufferWriter<WriterAdapter> GetWriter(PipeWriter pipeWriter)
|
||||||
|
=> new BufferWriter<WriterAdapter>(new WriterAdapter(pipeWriter));
|
||||||
|
|
||||||
|
private struct WriterAdapter : IBufferWriter<byte>
|
||||||
|
{
|
||||||
|
public PipeWriter Writer;
|
||||||
|
|
||||||
|
public WriterAdapter(PipeWriter writer)
|
||||||
|
=> Writer = writer;
|
||||||
|
|
||||||
|
public void Advance(int count)
|
||||||
|
=> Writer.Advance(count);
|
||||||
|
|
||||||
|
public Memory<byte> GetMemory(int sizeHint = 0)
|
||||||
|
=> Writer.GetMemory(sizeHint);
|
||||||
|
|
||||||
|
public Span<byte> GetSpan(int sizeHint = 0)
|
||||||
|
=> Writer.GetSpan(sizeHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ParsingAdapter : IHttpRequestLineHandler, IHttpHeadersHandler
|
||||||
|
{
|
||||||
|
public BenchmarkApplication RequestHandler;
|
||||||
|
|
||||||
|
public ParsingAdapter(BenchmarkApplication requestHandler)
|
||||||
|
=> RequestHandler = requestHandler;
|
||||||
|
|
||||||
|
public void OnHeader(Span<byte> name, Span<byte> value)
|
||||||
|
=> RequestHandler.OnHeader(name, value);
|
||||||
|
|
||||||
|
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
||||||
|
=> RequestHandler.OnStartLine(method, version, target, path, query, customMethod, pathEncoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
|
|
@ -10,57 +9,55 @@ using Utf8Json;
|
||||||
|
|
||||||
namespace PlatformBenchmarks
|
namespace PlatformBenchmarks
|
||||||
{
|
{
|
||||||
public class BenchmarkApplication : HttpConnection
|
public partial class BenchmarkApplication
|
||||||
{
|
{
|
||||||
private static AsciiString _crlf = "\r\n";
|
private readonly static AsciiString _applicationName = "Kestrel Platform-Level Application";
|
||||||
private static AsciiString _eoh = "\r\n\r\n"; // End Of Headers
|
public static AsciiString ApplicationName => _applicationName;
|
||||||
private static AsciiString _http11OK = "HTTP/1.1 200 OK\r\n";
|
|
||||||
private static AsciiString _headerServer = "Server: Custom";
|
|
||||||
private static AsciiString _headerContentLength = "Content-Length: ";
|
|
||||||
private static AsciiString _headerContentLengthZero = "Content-Length: 0\r\n";
|
|
||||||
private static AsciiString _headerContentTypeText = "Content-Type: text/plain\r\n";
|
|
||||||
private static AsciiString _headerContentTypeJson = "Content-Type: application/json\r\n";
|
|
||||||
|
|
||||||
|
private readonly static AsciiString _crlf = "\r\n";
|
||||||
|
private readonly static AsciiString _eoh = "\r\n\r\n"; // End Of Headers
|
||||||
|
private readonly static AsciiString _http11OK = "HTTP/1.1 200 OK\r\n";
|
||||||
|
private readonly static AsciiString _headerServer = "Server: Custom";
|
||||||
|
private readonly static AsciiString _headerContentLength = "Content-Length: ";
|
||||||
|
private readonly static AsciiString _headerContentLengthZero = "Content-Length: 0\r\n";
|
||||||
|
private readonly static AsciiString _headerContentTypeText = "Content-Type: text/plain\r\n";
|
||||||
|
private readonly static AsciiString _headerContentTypeJson = "Content-Type: application/json\r\n";
|
||||||
|
|
||||||
private static AsciiString _plainTextBody = "Hello, World!";
|
private readonly static AsciiString _plainTextBody = "Hello, World!";
|
||||||
|
|
||||||
private static class Paths
|
public static class Paths
|
||||||
{
|
{
|
||||||
public static AsciiString Plaintext = "/plaintext";
|
public readonly static AsciiString Plaintext = "/plaintext";
|
||||||
public static AsciiString Json = "/json";
|
public readonly static AsciiString Json = "/json";
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isPlainText;
|
private RequestType _requestType;
|
||||||
private bool _isJson;
|
|
||||||
|
|
||||||
public override void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
||||||
{
|
{
|
||||||
if (path.StartsWith(Paths.Plaintext) && method == HttpMethod.Get)
|
var requestType = RequestType.NotRecognized;
|
||||||
|
if (method == HttpMethod.Get)
|
||||||
{
|
{
|
||||||
_isPlainText = true;
|
if (Paths.Plaintext.Length <= path.Length && path.StartsWith(Paths.Plaintext))
|
||||||
}
|
{
|
||||||
else if (path.StartsWith(Paths.Json) && method == HttpMethod.Get)
|
requestType = RequestType.PlainText;
|
||||||
{
|
}
|
||||||
_isJson = true;
|
else if (Paths.Json.Length <= path.Length && path.StartsWith(Paths.Json))
|
||||||
}
|
{
|
||||||
else
|
requestType = RequestType.Json;
|
||||||
{
|
}
|
||||||
_isPlainText = false;
|
|
||||||
_isJson = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_requestType = requestType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnHeader(Span<byte> name, Span<byte> value)
|
public ValueTask ProcessRequestAsync()
|
||||||
{
|
{
|
||||||
}
|
if (_requestType == RequestType.PlainText)
|
||||||
|
|
||||||
public override ValueTask ProcessRequestAsync()
|
|
||||||
{
|
|
||||||
if (_isPlainText)
|
|
||||||
{
|
{
|
||||||
PlainText(Writer);
|
PlainText(Writer);
|
||||||
}
|
}
|
||||||
else if (_isJson)
|
else if (_requestType == RequestType.Json)
|
||||||
{
|
{
|
||||||
Json(Writer);
|
Json(Writer);
|
||||||
}
|
}
|
||||||
|
|
@ -72,13 +69,10 @@ namespace PlatformBenchmarks
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async ValueTask OnReadCompletedAsync()
|
|
||||||
{
|
|
||||||
await Writer.FlushAsync();
|
|
||||||
}
|
|
||||||
private static void PlainText(PipeWriter pipeWriter)
|
private static void PlainText(PipeWriter pipeWriter)
|
||||||
{
|
{
|
||||||
var writer = new CountingBufferWriter<PipeWriter>(pipeWriter);
|
var writer = GetWriter(pipeWriter);
|
||||||
|
|
||||||
// HTTP 1.1 OK
|
// HTTP 1.1 OK
|
||||||
writer.Write(_http11OK);
|
writer.Write(_http11OK);
|
||||||
|
|
||||||
|
|
@ -93,7 +87,7 @@ namespace PlatformBenchmarks
|
||||||
|
|
||||||
// Content-Length header
|
// Content-Length header
|
||||||
writer.Write(_headerContentLength);
|
writer.Write(_headerContentLength);
|
||||||
writer.WriteNumeric((ulong)_plainTextBody.Length);
|
writer.WriteNumeric((uint)_plainTextBody.Length);
|
||||||
|
|
||||||
// End of headers
|
// End of headers
|
||||||
writer.Write(_eoh);
|
writer.Write(_eoh);
|
||||||
|
|
@ -105,7 +99,7 @@ namespace PlatformBenchmarks
|
||||||
|
|
||||||
private static void Json(PipeWriter pipeWriter)
|
private static void Json(PipeWriter pipeWriter)
|
||||||
{
|
{
|
||||||
var writer = new CountingBufferWriter<PipeWriter>(pipeWriter);
|
var writer = GetWriter(pipeWriter);
|
||||||
|
|
||||||
// HTTP 1.1 OK
|
// HTTP 1.1 OK
|
||||||
writer.Write(_http11OK);
|
writer.Write(_http11OK);
|
||||||
|
|
@ -122,7 +116,7 @@ namespace PlatformBenchmarks
|
||||||
// Content-Length header
|
// Content-Length header
|
||||||
writer.Write(_headerContentLength);
|
writer.Write(_headerContentLength);
|
||||||
var jsonPayload = JsonSerializer.SerializeUnsafe(new { message = "Hello, World!" });
|
var jsonPayload = JsonSerializer.SerializeUnsafe(new { message = "Hello, World!" });
|
||||||
writer.WriteNumeric((ulong)jsonPayload.Count);
|
writer.WriteNumeric((uint)jsonPayload.Count);
|
||||||
|
|
||||||
// End of headers
|
// End of headers
|
||||||
writer.Write(_eoh);
|
writer.Write(_eoh);
|
||||||
|
|
@ -134,7 +128,7 @@ namespace PlatformBenchmarks
|
||||||
|
|
||||||
private static void Default(PipeWriter pipeWriter)
|
private static void Default(PipeWriter pipeWriter)
|
||||||
{
|
{
|
||||||
var writer = new CountingBufferWriter<PipeWriter>(pipeWriter);
|
var writer = GetWriter(pipeWriter);
|
||||||
|
|
||||||
// HTTP 1.1 OK
|
// HTTP 1.1 OK
|
||||||
writer.Write(_http11OK);
|
writer.Write(_http11OK);
|
||||||
|
|
@ -152,5 +146,12 @@ namespace PlatformBenchmarks
|
||||||
writer.Write(_crlf);
|
writer.Write(_crlf);
|
||||||
writer.Commit();
|
writer.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum RequestType
|
||||||
|
{
|
||||||
|
NotRecognized,
|
||||||
|
PlainText,
|
||||||
|
Json
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace PlatformBenchmarks
|
||||||
|
{
|
||||||
|
// Same as KestrelHttpServer\src\Kestrel.Core\Internal\Http\PipelineExtensions.cs
|
||||||
|
// However methods accept T : struct, IBufferWriter<byte> rather than PipeWriter.
|
||||||
|
// This allows a struct wrapper to turn CountingBufferWriter into a non-shared generic,
|
||||||
|
// while still offering the WriteNumeric extension.
|
||||||
|
|
||||||
|
public static class BufferExtensions
|
||||||
|
{
|
||||||
|
private const int _maxULongByteLength = 20;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static byte[] _numericBytesScratch;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal static unsafe void WriteNumeric<T>(ref this BufferWriter<T> buffer, uint number)
|
||||||
|
where T : struct, IBufferWriter<byte>
|
||||||
|
{
|
||||||
|
const byte AsciiDigitStart = (byte)'0';
|
||||||
|
|
||||||
|
var span = buffer.Span;
|
||||||
|
var bytesLeftInBlock = span.Length;
|
||||||
|
|
||||||
|
// Fast path, try copying to the available memory directly
|
||||||
|
var advanceBy = 0;
|
||||||
|
fixed (byte* output = &MemoryMarshal.GetReference(span))
|
||||||
|
{
|
||||||
|
var start = output;
|
||||||
|
if (number < 10 && bytesLeftInBlock >= 1)
|
||||||
|
{
|
||||||
|
start[0] = (byte)(number + AsciiDigitStart);
|
||||||
|
advanceBy = 1;
|
||||||
|
}
|
||||||
|
else if (number < 100 && bytesLeftInBlock >= 2)
|
||||||
|
{
|
||||||
|
var tens = (byte)((number * 205u) >> 11); // div10, valid to 1028
|
||||||
|
|
||||||
|
start[0] = (byte)(tens + AsciiDigitStart);
|
||||||
|
start[1] = (byte)(number - (tens * 10) + AsciiDigitStart);
|
||||||
|
advanceBy = 2;
|
||||||
|
}
|
||||||
|
else if (number < 1000 && bytesLeftInBlock >= 3)
|
||||||
|
{
|
||||||
|
var digit0 = (byte)((number * 41u) >> 12); // div100, valid to 1098
|
||||||
|
var digits01 = (byte)((number * 205u) >> 11); // div10, valid to 1028
|
||||||
|
|
||||||
|
start[0] = (byte)(digit0 + AsciiDigitStart);
|
||||||
|
start[1] = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart);
|
||||||
|
start[2] = (byte)(number - (digits01 * 10) + AsciiDigitStart);
|
||||||
|
advanceBy = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (advanceBy > 0)
|
||||||
|
{
|
||||||
|
buffer.Advance(advanceBy);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteNumericMultiWrite(ref buffer, number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static void WriteNumericMultiWrite<T>(ref this BufferWriter<T> buffer, uint number)
|
||||||
|
where T : struct, IBufferWriter<byte>
|
||||||
|
{
|
||||||
|
const byte AsciiDigitStart = (byte)'0';
|
||||||
|
|
||||||
|
var value = number;
|
||||||
|
var position = _maxULongByteLength;
|
||||||
|
var byteBuffer = NumericBytesScratch;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Consider using Math.DivRem() if available
|
||||||
|
var quotient = value / 10;
|
||||||
|
byteBuffer[--position] = (byte)(AsciiDigitStart + (value - quotient * 10)); // 0x30 = '0'
|
||||||
|
value = quotient;
|
||||||
|
}
|
||||||
|
while (value != 0);
|
||||||
|
|
||||||
|
var length = _maxULongByteLength - position;
|
||||||
|
buffer.Write(new ReadOnlySpan<byte>(byteBuffer, position, length));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static byte[] CreateNumericBytesScratch()
|
||||||
|
{
|
||||||
|
var bytes = new byte[_maxULongByteLength];
|
||||||
|
_numericBytesScratch = bytes;
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PlatformBenchmarks
|
||||||
|
{
|
||||||
|
public ref struct BufferWriter<T> where T : IBufferWriter<byte>
|
||||||
|
{
|
||||||
|
private T _output;
|
||||||
|
private Span<byte> _span;
|
||||||
|
private int _buffered;
|
||||||
|
|
||||||
|
public BufferWriter(T output)
|
||||||
|
{
|
||||||
|
_buffered = 0;
|
||||||
|
_output = output;
|
||||||
|
_span = output.GetSpan();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Span<byte> Span => _span;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Commit()
|
||||||
|
{
|
||||||
|
var buffered = _buffered;
|
||||||
|
if (buffered > 0)
|
||||||
|
{
|
||||||
|
_buffered = 0;
|
||||||
|
_output.Advance(buffered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Advance(int count)
|
||||||
|
{
|
||||||
|
_buffered += count;
|
||||||
|
_span = _span.Slice(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Write(ReadOnlySpan<byte> source)
|
||||||
|
{
|
||||||
|
if (_span.Length >= source.Length)
|
||||||
|
{
|
||||||
|
source.CopyTo(_span);
|
||||||
|
Advance(source.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteMultiBuffer(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Ensure(int count = 1)
|
||||||
|
{
|
||||||
|
if (_span.Length < count)
|
||||||
|
{
|
||||||
|
EnsureMore(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private void EnsureMore(int count = 0)
|
||||||
|
{
|
||||||
|
if (_buffered > 0)
|
||||||
|
{
|
||||||
|
Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
_output.GetMemory(count);
|
||||||
|
_span = _output.GetSpan();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
|
||||||
|
{
|
||||||
|
while (source.Length > 0)
|
||||||
|
{
|
||||||
|
if (_span.Length == 0)
|
||||||
|
{
|
||||||
|
EnsureMore();
|
||||||
|
}
|
||||||
|
|
||||||
|
var writable = Math.Min(source.Length, _span.Length);
|
||||||
|
source.Slice(0, writable).CopyTo(_span);
|
||||||
|
source = source.Slice(writable);
|
||||||
|
Advance(writable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -37,8 +37,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
s_headerBytesScratch[suffixIndex] = (byte)'\r';
|
s_headerBytesScratch[suffixIndex] = (byte)'\r';
|
||||||
s_headerBytesScratch[suffixIndex + 1] = (byte)'\n';
|
s_headerBytesScratch[suffixIndex + 1] = (byte)'\n';
|
||||||
SetDateValues(DateTimeOffset.UtcNow);
|
SetDateValues(DateTimeOffset.UtcNow);
|
||||||
|
SyncDateTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SyncDateTimer() => s_timer.Change(1000, 1000);
|
||||||
|
|
||||||
public static ReadOnlySpan<byte> HeaderBytes => s_headerBytesMaster;
|
public static ReadOnlySpan<byte> HeaderBytes => s_headerBytesMaster;
|
||||||
|
|
||||||
private static void SetDateValues(DateTimeOffset value)
|
private static void SetDateValues(DateTimeOffset value)
|
||||||
|
|
|
||||||
|
|
@ -1,161 +1,29 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
|
||||||
|
|
||||||
namespace PlatformBenchmarks
|
namespace PlatformBenchmarks
|
||||||
{
|
{
|
||||||
public static class HttpApplicationConnectionBuilderExtensions
|
public static class HttpApplicationConnectionBuilderExtensions
|
||||||
{
|
{
|
||||||
public static IConnectionBuilder UseHttpApplication<TConnection>(this IConnectionBuilder builder) where TConnection : HttpConnection, new()
|
public static IConnectionBuilder UseHttpApplication<TConnection>(this IConnectionBuilder builder) where TConnection : IHttpConnection, new()
|
||||||
{
|
{
|
||||||
return builder.Use(next => new HttpApplication<TConnection>().ExecuteAsync);
|
return builder.Use(next => new HttpApplication<TConnection>().ExecuteAsync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HttpApplication<TConnection> where TConnection : HttpConnection, new()
|
public class HttpApplication<TConnection> where TConnection : IHttpConnection, new()
|
||||||
{
|
{
|
||||||
public Task ExecuteAsync(ConnectionContext connection)
|
public Task ExecuteAsync(ConnectionContext connection)
|
||||||
{
|
{
|
||||||
var parser = new HttpParser<HttpConnection>();
|
|
||||||
|
|
||||||
var httpConnection = new TConnection
|
var httpConnection = new TConnection
|
||||||
{
|
{
|
||||||
Parser = parser,
|
|
||||||
Reader = connection.Transport.Input,
|
Reader = connection.Transport.Input,
|
||||||
Writer = connection.Transport.Output
|
Writer = connection.Transport.Output
|
||||||
};
|
};
|
||||||
return httpConnection.ExecuteAsync();
|
return httpConnection.ExecuteAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HttpConnection : IHttpHeadersHandler, IHttpRequestLineHandler
|
|
||||||
{
|
|
||||||
private State _state;
|
|
||||||
|
|
||||||
public PipeReader Reader { get; set; }
|
|
||||||
public PipeWriter Writer { get; set; }
|
|
||||||
|
|
||||||
internal HttpParser<HttpConnection> Parser { get; set; }
|
|
||||||
|
|
||||||
public virtual void OnHeader(Span<byte> name, Span<byte> value)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual ValueTask ProcessRequestAsync()
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual ValueTask OnReadCompletedAsync()
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ExecuteAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await ProcessRequestsAsync();
|
|
||||||
|
|
||||||
Reader.Complete();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Reader.Complete(ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Writer.Complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProcessRequestsAsync()
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var task = Reader.ReadAsync();
|
|
||||||
|
|
||||||
if (!task.IsCompleted)
|
|
||||||
{
|
|
||||||
// No more data in the input
|
|
||||||
await OnReadCompletedAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await task;
|
|
||||||
var buffer = result.Buffer;
|
|
||||||
var consumed = buffer.Start;
|
|
||||||
var examined = buffer.End;
|
|
||||||
|
|
||||||
if (!buffer.IsEmpty)
|
|
||||||
{
|
|
||||||
ParseHttpRequest(buffer, out consumed, out examined);
|
|
||||||
|
|
||||||
if (_state != State.Body && result.IsCompleted)
|
|
||||||
{
|
|
||||||
ThrowUnexpectedEndOfData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (result.IsCompleted)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Reader.AdvanceTo(consumed, examined);
|
|
||||||
|
|
||||||
if (_state == State.Body)
|
|
||||||
{
|
|
||||||
await ProcessRequestAsync();
|
|
||||||
|
|
||||||
_state = State.StartLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseHttpRequest(in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
|
|
||||||
{
|
|
||||||
consumed = buffer.Start;
|
|
||||||
examined = buffer.End;
|
|
||||||
|
|
||||||
var parsingStartLine = _state == State.StartLine;
|
|
||||||
if (parsingStartLine)
|
|
||||||
{
|
|
||||||
if (Parser.ParseRequestLine(this, buffer, out consumed, out examined))
|
|
||||||
{
|
|
||||||
_state = State.Headers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_state == State.Headers)
|
|
||||||
{
|
|
||||||
if (Parser.ParseHeaders(this, parsingStartLine ? buffer.Slice(consumed) : buffer, out consumed, out examined, out int consumedBytes))
|
|
||||||
{
|
|
||||||
_state = State.Body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ThrowUnexpectedEndOfData()
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Unexpected end of data!");
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum State
|
|
||||||
{
|
|
||||||
StartLine,
|
|
||||||
Headers,
|
|
||||||
Body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
|
|
||||||
|
namespace PlatformBenchmarks
|
||||||
|
{
|
||||||
|
public interface IHttpConnection : IHttpHeadersHandler, IHttpRequestLineHandler
|
||||||
|
{
|
||||||
|
PipeReader Reader { get; set; }
|
||||||
|
PipeWriter Writer { get; set; }
|
||||||
|
Task ExecuteAsync();
|
||||||
|
ValueTask OnReadCompletedAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,12 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Microsoft.AspNetCore;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace PlatformBenchmarks
|
namespace PlatformBenchmarks
|
||||||
{
|
{
|
||||||
|
|
@ -16,6 +13,11 @@ namespace PlatformBenchmarks
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine(BenchmarkApplication.ApplicationName);
|
||||||
|
Console.WriteLine(BenchmarkApplication.Paths.Plaintext);
|
||||||
|
Console.WriteLine(BenchmarkApplication.Paths.Json);
|
||||||
|
DateHeader.SyncDateTimer();
|
||||||
|
|
||||||
BuildWebHost(args).Run();
|
BuildWebHost(args).Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net.WebSockets;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
|
|
||||||
namespace PlatformBenchmarks
|
namespace PlatformBenchmarks
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue