[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
|
||||
{
|
||||
public struct AsciiString : IEquatable<AsciiString>
|
||||
public readonly struct AsciiString : IEquatable<AsciiString>
|
||||
{
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
|
|
@ -10,57 +9,55 @@ using Utf8Json;
|
|||
|
||||
namespace PlatformBenchmarks
|
||||
{
|
||||
public class BenchmarkApplication : HttpConnection
|
||||
public partial class BenchmarkApplication
|
||||
{
|
||||
private static AsciiString _crlf = "\r\n";
|
||||
private static AsciiString _eoh = "\r\n\r\n"; // End Of Headers
|
||||
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 _applicationName = "Kestrel Platform-Level Application";
|
||||
public static AsciiString ApplicationName => _applicationName;
|
||||
|
||||
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 static AsciiString Json = "/json";
|
||||
public readonly static AsciiString Plaintext = "/plaintext";
|
||||
public readonly static AsciiString Json = "/json";
|
||||
}
|
||||
|
||||
private bool _isPlainText;
|
||||
private bool _isJson;
|
||||
private RequestType _requestType;
|
||||
|
||||
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;
|
||||
}
|
||||
else if (path.StartsWith(Paths.Json) && method == HttpMethod.Get)
|
||||
{
|
||||
_isJson = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isPlainText = false;
|
||||
_isJson = false;
|
||||
if (Paths.Plaintext.Length <= path.Length && path.StartsWith(Paths.Plaintext))
|
||||
{
|
||||
requestType = RequestType.PlainText;
|
||||
}
|
||||
else if (Paths.Json.Length <= path.Length && path.StartsWith(Paths.Json))
|
||||
{
|
||||
requestType = RequestType.Json;
|
||||
}
|
||||
}
|
||||
|
||||
_requestType = requestType;
|
||||
}
|
||||
|
||||
public override void OnHeader(Span<byte> name, Span<byte> value)
|
||||
public ValueTask ProcessRequestAsync()
|
||||
{
|
||||
}
|
||||
|
||||
public override ValueTask ProcessRequestAsync()
|
||||
{
|
||||
if (_isPlainText)
|
||||
if (_requestType == RequestType.PlainText)
|
||||
{
|
||||
PlainText(Writer);
|
||||
}
|
||||
else if (_isJson)
|
||||
else if (_requestType == RequestType.Json)
|
||||
{
|
||||
Json(Writer);
|
||||
}
|
||||
|
|
@ -72,13 +69,10 @@ namespace PlatformBenchmarks
|
|||
return default;
|
||||
}
|
||||
|
||||
public override async ValueTask OnReadCompletedAsync()
|
||||
{
|
||||
await Writer.FlushAsync();
|
||||
}
|
||||
private static void PlainText(PipeWriter pipeWriter)
|
||||
{
|
||||
var writer = new CountingBufferWriter<PipeWriter>(pipeWriter);
|
||||
var writer = GetWriter(pipeWriter);
|
||||
|
||||
// HTTP 1.1 OK
|
||||
writer.Write(_http11OK);
|
||||
|
||||
|
|
@ -93,7 +87,7 @@ namespace PlatformBenchmarks
|
|||
|
||||
// Content-Length header
|
||||
writer.Write(_headerContentLength);
|
||||
writer.WriteNumeric((ulong)_plainTextBody.Length);
|
||||
writer.WriteNumeric((uint)_plainTextBody.Length);
|
||||
|
||||
// End of headers
|
||||
writer.Write(_eoh);
|
||||
|
|
@ -105,7 +99,7 @@ namespace PlatformBenchmarks
|
|||
|
||||
private static void Json(PipeWriter pipeWriter)
|
||||
{
|
||||
var writer = new CountingBufferWriter<PipeWriter>(pipeWriter);
|
||||
var writer = GetWriter(pipeWriter);
|
||||
|
||||
// HTTP 1.1 OK
|
||||
writer.Write(_http11OK);
|
||||
|
|
@ -122,7 +116,7 @@ namespace PlatformBenchmarks
|
|||
// Content-Length header
|
||||
writer.Write(_headerContentLength);
|
||||
var jsonPayload = JsonSerializer.SerializeUnsafe(new { message = "Hello, World!" });
|
||||
writer.WriteNumeric((ulong)jsonPayload.Count);
|
||||
writer.WriteNumeric((uint)jsonPayload.Count);
|
||||
|
||||
// End of headers
|
||||
writer.Write(_eoh);
|
||||
|
|
@ -134,7 +128,7 @@ namespace PlatformBenchmarks
|
|||
|
||||
private static void Default(PipeWriter pipeWriter)
|
||||
{
|
||||
var writer = new CountingBufferWriter<PipeWriter>(pipeWriter);
|
||||
var writer = GetWriter(pipeWriter);
|
||||
|
||||
// HTTP 1.1 OK
|
||||
writer.Write(_http11OK);
|
||||
|
|
@ -152,5 +146,12 @@ namespace PlatformBenchmarks
|
|||
writer.Write(_crlf);
|
||||
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 + 1] = (byte)'\n';
|
||||
SetDateValues(DateTimeOffset.UtcNow);
|
||||
SyncDateTimer();
|
||||
}
|
||||
|
||||
public static void SyncDateTimer() => s_timer.Change(1000, 1000);
|
||||
|
||||
public static ReadOnlySpan<byte> HeaderBytes => s_headerBytesMaster;
|
||||
|
||||
private static void SetDateValues(DateTimeOffset value)
|
||||
|
|
|
|||
|
|
@ -1,161 +1,29 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
|
||||
namespace PlatformBenchmarks
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public class HttpApplication<TConnection> where TConnection : HttpConnection, new()
|
||||
public class HttpApplication<TConnection> where TConnection : IHttpConnection, new()
|
||||
{
|
||||
public Task ExecuteAsync(ConnectionContext connection)
|
||||
{
|
||||
var parser = new HttpParser<HttpConnection>();
|
||||
|
||||
var httpConnection = new TConnection
|
||||
{
|
||||
Parser = parser,
|
||||
Reader = connection.Transport.Input,
|
||||
Writer = connection.Transport.Output
|
||||
};
|
||||
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.Net;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace PlatformBenchmarks
|
||||
{
|
||||
|
|
@ -16,6 +13,11 @@ namespace PlatformBenchmarks
|
|||
{
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace PlatformBenchmarks
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue