This commit is contained in:
Pavel Krymets 2017-02-17 15:38:13 -08:00 коммит произвёл GitHub
Родитель bfe1f06938
Коммит 824ef2c937
43 изменённых файлов: 1421 добавлений и 1792 удалений

Просмотреть файл

@ -4,6 +4,8 @@
<add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json" /> <add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json" />
<add key="AspNetCoreTools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" /> <add key="AspNetCoreTools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" />
<add key="appveyor-bdn" value="https://ci.appveyor.com/nuget/benchmarkdotnet" /> <add key="appveyor-bdn" value="https://ci.appveyor.com/nuget/benchmarkdotnet" />
<add key="dotnet-corefxlab" value="https://dotnet.myget.org/F/dotnet-corefxlab/api/v3/index.json" />
<add key="dotnet-corefx" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" /> <add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources> </packageSources>
</configuration> </configuration>

45
ToProjectReferences.ps1 Normal file
Просмотреть файл

@ -0,0 +1,45 @@
param($references)
$ErrorActionPreference = "Stop";
function ToProjectName($file)
{
return $file.Directory.Name;
}
$projectreferences = ls (Join-Path $references *.csproj) -rec;
$localprojects = ls -rec *.csproj;
foreach ($project in $localprojects)
{
Write-Host "Processing $project";
[Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq") | Out-Null;
$changed = $false
$xDoc = [System.Xml.Linq.XDocument]::Load($project, [System.Xml.Linq.LoadOptions]::PreserveWhitespace);
$endpoints = $xDoc.Descendants("PackageReference") | %{
$packageName = $_.Attribute("Include").Value;
$replacementProject = $projectreferences | ? {
return (ToProjectName($_)) -eq $packageName
};
if ($replacementProject)
{
$changed = $true
Write-Host " Replacing $packageName with $($project.FullName)";
$_.Name = "ProjectReference";
$_.Attribute("Include").Value = $replacementProject.FullName;
}
};
if ($changed)
{
$settings = New-Object System.Xml.XmlWriterSettings
$settings.OmitXmlDeclaration = $true;
$writer = [System.Xml.XmlWriter]::Create($project, $settings)
$xDoc.Save($writer);
$writer.Dispose();
}
}

Просмотреть файл

@ -3,37 +3,40 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Pipelines;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using MemoryPool = Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure.MemoryPool;
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
{ {
public class AdaptedPipeline : IDisposable public class AdaptedPipeline : IDisposable
{ {
private const int MinAllocBufferSize = 2048;
private readonly Stream _filteredStream; private readonly Stream _filteredStream;
public AdaptedPipeline( public AdaptedPipeline(
string connectionId, string connectionId,
Stream filteredStream, Stream filteredStream,
IPipe pipe,
MemoryPool memory, MemoryPool memory,
IKestrelTrace logger, IKestrelTrace logger)
IThreadPool threadPool,
IBufferSizeControl bufferSizeControl)
{ {
SocketInput = new SocketInput(memory, threadPool, bufferSizeControl); Input = pipe;
SocketOutput = new StreamSocketOutput(connectionId, filteredStream, memory, logger); Output = new StreamSocketOutput(connectionId, filteredStream, memory, logger);
_filteredStream = filteredStream; _filteredStream = filteredStream;
} }
public SocketInput SocketInput { get; } public IPipe Input { get; }
public ISocketOutput SocketOutput { get; } public ISocketOutput Output { get; }
public void Dispose() public void Dispose()
{ {
SocketInput.Dispose(); Input.Writer.Complete();
} }
public async Task ReadInputAsync() public async Task ReadInputAsync()
@ -42,21 +45,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
do do
{ {
var block = SocketInput.IncomingStart(); var block = Input.Writer.Alloc(MinAllocBufferSize);
try try
{ {
var count = block.Data.Offset + block.Data.Count - block.End; var array = block.Memory.GetArray();
bytesRead = await _filteredStream.ReadAsync(block.Array, block.End, count); try
{
bytesRead = await _filteredStream.ReadAsync(array.Array, array.Offset, array.Count);
block.Advance(bytesRead);
}
finally
{
await block.FlushAsync();
}
} }
catch (Exception ex) catch (Exception ex)
{ {
SocketInput.IncomingComplete(0, ex); Input.Writer.Complete(ex);
throw; throw;
} }
SocketInput.IncomingComplete(bytesRead, error: null);
} while (bytesRead != 0); } while (bytesRead != 0);
Input.Writer.Complete();
} }
} }
} }

Просмотреть файл

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Pipelines;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
@ -12,12 +13,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
{ {
public class RawStream : Stream public class RawStream : Stream
{ {
private readonly SocketInput _input; private readonly IPipeReader _input;
private readonly ISocketOutput _output; private readonly ISocketOutput _output;
private Task<int> _cachedTask = TaskCache<int>.DefaultCompletedTask; public RawStream(IPipeReader input, ISocketOutput output)
public RawStream(SocketInput input, ISocketOutput output)
{ {
_input = input; _input = input;
_output = output; _output = output;
@ -68,23 +67,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
var task = ReadAsync(new ArraySegment<byte>(buffer, offset, count)); return ReadAsync(new ArraySegment<byte>(buffer, offset, count));
if (task.IsCompletedSuccessfully)
{
if (_cachedTask.Result != task.Result)
{
// Needs .AsTask to match Stream's Async method return types
_cachedTask = task.AsTask();
}
}
else
{
// Needs .AsTask to match Stream's Async method return types
_cachedTask = task.AsTask();
}
return _cachedTask;
} }
public override void Write(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count)
@ -125,10 +108,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
return _output.FlushAsync(cancellationToken); return _output.FlushAsync(cancellationToken);
} }
private async Task<int> ReadAsync(ArraySegment<byte> buffer)
private ValueTask<int> ReadAsync(ArraySegment<byte> buffer)
{ {
return _input.ReadAsync(buffer.Array, buffer.Offset, buffer.Count); while (true)
{
var result = await _input.ReadAsync();
var readableBuffer = result.Buffer;
try
{
if (!readableBuffer.IsEmpty)
{
var count = Math.Min(readableBuffer.Length, buffer.Count);
readableBuffer = readableBuffer.Slice(0, count);
readableBuffer.CopyTo(buffer);
return count;
}
else if (result.IsCompleted || result.IsCancelled)
{
return 0;
}
}
finally
{
_input.Advance(readableBuffer.End, readableBuffer.End);
}
}
} }
#if NET451 #if NET451

Просмотреть файл

@ -1,77 +0,0 @@
using System.Diagnostics;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
public class BufferSizeControl : IBufferSizeControl
{
private readonly long _maxSize;
private readonly IConnectionControl _connectionControl;
private readonly object _lock = new object();
private long _size;
private bool _connectionPaused;
public BufferSizeControl(long maxSize, IConnectionControl connectionControl)
{
_maxSize = maxSize;
_connectionControl = connectionControl;
}
private long Size
{
get
{
return _size;
}
set
{
// Caller should ensure that bytes are never consumed before the producer has called Add()
Debug.Assert(value >= 0);
_size = value;
}
}
public void Add(int count)
{
Debug.Assert(count >= 0);
if (count == 0)
{
// No-op and avoid taking lock to reduce contention
return;
}
lock (_lock)
{
Size += count;
if (!_connectionPaused && Size >= _maxSize)
{
_connectionPaused = true;
_connectionControl.Pause();
}
}
}
public void Subtract(int count)
{
Debug.Assert(count >= 0);
if (count == 0)
{
// No-op and avoid taking lock to reduce contention
return;
}
lock (_lock)
{
Size -= count;
if (_connectionPaused && Size < _maxSize)
{
_connectionPaused = false;
_connectionControl.Resume();
}
}
}
}
}

Просмотреть файл

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Pipelines;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Adapter; using Microsoft.AspNetCore.Server.Kestrel.Adapter;
@ -18,6 +19,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
public class Connection : ConnectionContext, IConnectionControl public class Connection : ConnectionContext, IConnectionControl
{ {
private const int MinAllocBufferSize = 2048;
// Base32 encoding - in ascii sort order for easy text based sorting // Base32 encoding - in ascii sort order for easy text based sorting
private static readonly string _encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; private static readonly string _encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
@ -40,11 +43,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
private Task _readInputTask; private Task _readInputTask;
private TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>(); private TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>();
private BufferSizeControl _bufferSizeControl;
private long _lastTimestamp; private long _lastTimestamp;
private long _timeoutTimestamp = long.MaxValue; private long _timeoutTimestamp = long.MaxValue;
private TimeoutAction _timeoutAction; private TimeoutAction _timeoutAction;
private WritableBuffer? _currentWritableBuffer;
public Connection(ListenerContext context, UvStreamHandle socket) : base(context) public Connection(ListenerContext context, UvStreamHandle socket) : base(context)
{ {
@ -55,12 +58,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
ConnectionId = GenerateConnectionId(Interlocked.Increment(ref _lastConnectionId)); ConnectionId = GenerateConnectionId(Interlocked.Increment(ref _lastConnectionId));
if (ServerOptions.Limits.MaxRequestBufferSize.HasValue) Input = Thread.PipelineFactory.Create(ListenerContext.LibuvPipeOptions);
{
_bufferSizeControl = new BufferSizeControl(ServerOptions.Limits.MaxRequestBufferSize.Value, this);
}
Input = new SocketInput(Thread.Memory, ThreadPool, _bufferSizeControl);
Output = new SocketOutput(Thread, _socket, this, ConnectionId, Log, ThreadPool); Output = new SocketOutput(Thread, _socket, this, ConnectionId, Log, ThreadPool);
var tcpHandle = _socket as UvTcpHandle; var tcpHandle = _socket as UvTcpHandle;
@ -92,6 +90,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// Start socket prior to applying the ConnectionAdapter // Start socket prior to applying the ConnectionAdapter
_socket.ReadStart(_allocCallback, _readCallback, this); _socket.ReadStart(_allocCallback, _readCallback, this);
// Dispatch to a thread pool so if the first read completes synchronously
// we won't be on IO thread
try
{
ThreadPool.UnsafeRun(state => ((Connection)state).StartFrame(), this);
}
catch (Exception e)
{
Log.LogError(0, e, "Connection.StartFrame");
throw;
}
}
private void StartFrame()
{
if (_connectionAdapters.Count == 0) if (_connectionAdapters.Count == 0)
{ {
_frame.Start(); _frame.Start();
@ -107,7 +120,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public Task StopAsync() public Task StopAsync()
{ {
_frame.StopAsync(); _frame.StopAsync();
_frame.Input.CompleteAwaiting(); _frame.Input.Reader.CancelPendingRead();
return _socketClosedTcs.Task; return _socketClosedTcs.Task;
} }
@ -138,11 +151,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var connection2 = (Connection)state2; var connection2 = (Connection)state2;
connection2._filteredStream.Dispose(); connection2._filteredStream.Dispose();
connection2._adaptedPipeline.Dispose(); connection2._adaptedPipeline.Dispose();
Input.Reader.Complete();
}, connection); }, connection);
} }
}, this); }, this);
Input.Dispose(); Input.Writer.Complete(new TaskCanceledException("The request was aborted"));
_socketClosedTcs.TrySetResult(null); _socketClosedTcs.TrySetResult(null);
} }
@ -168,7 +182,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
try try
{ {
var rawStream = new RawStream(Input, Output); var rawStream = new RawStream(Input.Reader, Output);
var adapterContext = new ConnectionAdapterContext(rawStream); var adapterContext = new ConnectionAdapterContext(rawStream);
var adaptedConnections = new IAdaptedConnection[_connectionAdapters.Count]; var adaptedConnections = new IAdaptedConnection[_connectionAdapters.Count];
@ -182,11 +196,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (adapterContext.ConnectionStream != rawStream) if (adapterContext.ConnectionStream != rawStream)
{ {
_filteredStream = adapterContext.ConnectionStream; _filteredStream = adapterContext.ConnectionStream;
_adaptedPipeline = new AdaptedPipeline(ConnectionId, adapterContext.ConnectionStream, _adaptedPipeline = new AdaptedPipeline(
Thread.Memory, Log, ThreadPool, _bufferSizeControl); ConnectionId,
adapterContext.ConnectionStream,
Thread.PipelineFactory.Create(ListenerContext.AdaptedPipeOptions),
Thread.Memory,
Log);
_frame.Input = _adaptedPipeline.SocketInput; _frame.Input = _adaptedPipeline.Input;
_frame.Output = _adaptedPipeline.SocketOutput; _frame.Output = _adaptedPipeline.Output;
// Don't attempt to read input if connection has already closed. // Don't attempt to read input if connection has already closed.
// This can happen if a client opens a connection and immediately closes it. // This can happen if a client opens a connection and immediately closes it.
@ -201,6 +219,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
catch (Exception ex) catch (Exception ex)
{ {
Log.LogError(0, ex, $"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}."); Log.LogError(0, ex, $"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}.");
Input.Reader.Complete();
ConnectionControl.End(ProduceEndType.SocketDisconnect); ConnectionControl.End(ProduceEndType.SocketDisconnect);
} }
} }
@ -210,13 +229,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return ((Connection)state).OnAlloc(handle, suggestedSize); return ((Connection)state).OnAlloc(handle, suggestedSize);
} }
private Libuv.uv_buf_t OnAlloc(UvStreamHandle handle, int suggestedSize) private unsafe Libuv.uv_buf_t OnAlloc(UvStreamHandle handle, int suggestedSize)
{ {
var result = Input.IncomingStart(); Debug.Assert(_currentWritableBuffer == null);
var currentWritableBuffer = Input.Writer.Alloc(MinAllocBufferSize);
_currentWritableBuffer = currentWritableBuffer;
void* dataPtr;
var tryGetPointer = currentWritableBuffer.Memory.TryGetPointer(out dataPtr);
Debug.Assert(tryGetPointer);
return handle.Libuv.buf_init( return handle.Libuv.buf_init(
result.DataArrayPtr + result.End, (IntPtr)dataPtr,
result.Data.Offset + result.Data.Count - result.End); currentWritableBuffer.Memory.Length);
} }
private static void ReadCallback(UvStreamHandle handle, int status, object state) private static void ReadCallback(UvStreamHandle handle, int status, object state)
@ -224,19 +248,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
((Connection)state).OnRead(handle, status); ((Connection)state).OnRead(handle, status);
} }
private void OnRead(UvStreamHandle handle, int status) private async void OnRead(UvStreamHandle handle, int status)
{ {
if (status == 0) var normalRead = status >= 0;
{
// A zero status does not indicate an error or connection end. It indicates
// there is no data to be read right now.
// See the note at http://docs.libuv.org/en/v1.x/stream.html#c.uv_read_cb.
// We need to clean up whatever was allocated by OnAlloc.
Input.IncomingDeferred();
return;
}
var normalRead = status > 0;
var normalDone = status == Constants.EOF; var normalDone = status == Constants.EOF;
var errorDone = !(normalDone || normalRead); var errorDone = !(normalDone || normalRead);
var readCount = normalRead ? status : 0; var readCount = normalRead ? status : 0;
@ -256,6 +270,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
IOException error = null; IOException error = null;
WritableBufferAwaitable? flushTask = null;
if (errorDone) if (errorDone)
{ {
Exception uvError; Exception uvError;
@ -272,13 +287,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
error = new IOException(uvError.Message, uvError); error = new IOException(uvError.Message, uvError);
_currentWritableBuffer?.Commit();
}
else
{
Debug.Assert(_currentWritableBuffer != null);
var currentWritableBuffer = _currentWritableBuffer.Value;
currentWritableBuffer.Advance(readCount);
flushTask = currentWritableBuffer.FlushAsync();
} }
Input.IncomingComplete(readCount, error); _currentWritableBuffer = null;
if (flushTask?.IsCompleted == false)
{
OnPausePosted();
if (await flushTask.Value)
{
OnResumePosted();
}
}
if (!normalRead) if (!normalRead)
{ {
AbortAsync(error); Input.Writer.Complete(error);
var ignore = AbortAsync(error);
} }
} }
@ -289,7 +322,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// Even though this method is called on the event loop already, // Even though this method is called on the event loop already,
// post anyway so the ReadStop() call doesn't get reordered // post anyway so the ReadStop() call doesn't get reordered
// relative to the ReadStart() call made in Resume(). // relative to the ReadStart() call made in Resume().
Thread.Post(state => ((Connection)state).OnPausePosted(), this); Thread.Post(state => state.OnPausePosted(), this);
} }
void IConnectionControl.Resume() void IConnectionControl.Resume()
@ -297,7 +330,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
Log.ConnectionResume(ConnectionId); Log.ConnectionResume(ConnectionId);
// This is called from the consuming thread. // This is called from the consuming thread.
Thread.Post(state => ((Connection)state).OnResumePosted(), this); Thread.Post(state => state.OnResumePosted(), this);
} }
private void OnPausePosted() private void OnPausePosted()
@ -316,14 +349,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
try try
{ {
_socket.ReadStart(_allocCallback, _readCallback, this); _socket.ReadStart(_allocCallback, _readCallback, this);
} }
catch (UvException) catch (UvException)
{ {
// ReadStart() can throw a UvException in some cases (e.g. socket is no longer connected). // ReadStart() can throw a UvException in some cases (e.g. socket is no longer connected).
// This should be treated the same as OnRead() seeing a "normalDone" condition. // This should be treated the same as OnRead() seeing a "normalDone" condition.
Log.ConnectionReadFin(ConnectionId); Log.ConnectionReadFin(ConnectionId);
Input.IncomingComplete(0, null); Input.Writer.Complete();
} }
} }
} }

Просмотреть файл

@ -3,6 +3,7 @@
using System; using System;
using System.Net; using System.Net;
using System.IO.Pipelines;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
@ -20,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public ListenerContext ListenerContext { get; set; } public ListenerContext ListenerContext { get; set; }
public SocketInput Input { get; set; } public IPipe Input { get; set; }
public ISocketOutput Output { get; set; } public ISocketOutput Output { get; set; }

Просмотреть файл

@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
var tcs = new TaskCompletionSource<object>(); var tcs = new TaskCompletionSource<object>();
_thread.Post(state => action((ConnectionManager)state, tcs), this); _thread.Post(state => action(state, tcs), this);
return await Task.WhenAny(tcs.Task, Task.Delay(timeout)).ConfigureAwait(false) == tcs.Task; return await Task.WhenAny(tcs.Task, Task.Delay(timeout)).ConfigureAwait(false) == tcs.Task;
} }

Просмотреть файл

@ -5,10 +5,14 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Pipelines;
using System.IO.Pipelines.Text.Primitives;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Text.Encodings.Web.Utf8;
using System.Text.Utf8;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -95,7 +99,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
public ConnectionContext ConnectionContext { get; } public ConnectionContext ConnectionContext { get; }
public SocketInput Input { get; set; } public IPipe Input { get; set; }
public ISocketOutput Output { get; set; } public ISocketOutput Output { get; set; }
public IEnumerable<IAdaptedConnection> AdaptedConnections { get; set; } public IEnumerable<IAdaptedConnection> AdaptedConnections { get; set; }
@ -386,13 +390,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public void Start() public void Start()
{ {
Reset(); Reset();
_requestProcessingTask = _requestProcessingTask = RequestProcessingAsync();
Task.Factory.StartNew(
(o) => ((Frame)o).RequestProcessingAsync(),
this,
default(CancellationToken),
TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default).Unwrap();
_frameStartedTcs.SetResult(null); _frameStartedTcs.SetResult(null);
} }
@ -986,216 +984,204 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
Output.ProducingComplete(end); Output.ProducingComplete(end);
} }
public RequestLineStatus TakeStartLine(SocketInput input) public bool TakeStartLine(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
{ {
const int MaxInvalidRequestLineChars = 32; var start = buffer.Start;
var end = buffer.Start;
var scan = input.ConsumingStart(); examined = buffer.End;
var start = scan; consumed = buffer.Start;
var consumed = scan;
var end = scan;
try if (_requestProcessingStatus == RequestProcessingStatus.RequestPending)
{ {
// We may hit this when the client has stopped sending data but ConnectionControl.ResetTimeout(_requestHeadersTimeoutMilliseconds, TimeoutAction.SendTimeoutResponse);
// the connection hasn't closed yet, and therefore Frame.Stop() }
// hasn't been called yet.
if (scan.Peek() == -1) _requestProcessingStatus = RequestProcessingStatus.RequestStarted;
var limitedBuffer = buffer;
if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize)
{
limitedBuffer = buffer.Slice(0, ServerOptions.Limits.MaxRequestLineSize);
}
if (ReadCursorOperations.Seek(limitedBuffer.Start, limitedBuffer.End, out end, ByteLF) == -1)
{
if (limitedBuffer.Length == ServerOptions.Limits.MaxRequestLineSize)
{ {
return RequestLineStatus.Empty; RejectRequest(RequestRejectionReason.RequestLineTooLong);
}
if (_requestProcessingStatus == RequestProcessingStatus.RequestPending)
{
ConnectionControl.ResetTimeout(_requestHeadersTimeoutMilliseconds, TimeoutAction.SendTimeoutResponse);
}
_requestProcessingStatus = RequestProcessingStatus.RequestStarted;
int bytesScanned;
if (end.Seek(ByteLF, out bytesScanned, ServerOptions.Limits.MaxRequestLineSize) == -1)
{
if (bytesScanned >= ServerOptions.Limits.MaxRequestLineSize)
{
RejectRequest(RequestRejectionReason.RequestLineTooLong);
}
else
{
return RequestLineStatus.Incomplete;
}
}
end.Take();
string method;
var begin = scan;
if (!begin.GetKnownMethod(out method))
{
if (scan.Seek(ByteSpace, ref end) == -1)
{
RejectRequest(RequestRejectionReason.InvalidRequestLine,
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
}
method = begin.GetAsciiString(ref scan);
if (method == null)
{
RejectRequest(RequestRejectionReason.InvalidRequestLine,
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
}
// Note: We're not in the fast path any more (GetKnownMethod should have handled any HTTP Method we're aware of)
// So we can be a tiny bit slower and more careful here.
for (int i = 0; i < method.Length; i++)
{
if (!IsValidTokenChar(method[i]))
{
RejectRequest(RequestRejectionReason.InvalidRequestLine,
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
}
}
} }
else else
{ {
scan.Skip(method.Length); return false;
}
}
end = buffer.Move(end, 1);
ReadCursor methodEnd;
string method;
if (!buffer.GetKnownMethod(out method))
{
if (ReadCursorOperations.Seek(buffer.Start, end, out methodEnd, ByteSpace) == -1)
{
RejectRequestLine(start, end);
} }
scan.Take(); method = buffer.Slice(buffer.Start, methodEnd).GetAsciiString();
begin = scan;
var needDecode = false; if (method == null)
var chFound = scan.Seek(ByteSpace, ByteQuestionMark, BytePercentage, ref end); {
RejectRequestLine(start, end);
}
// Note: We're not in the fast path any more (GetKnownMethod should have handled any HTTP Method we're aware of)
// So we can be a tiny bit slower and more careful here.
for (int i = 0; i < method.Length; i++)
{
if (!IsValidTokenChar(method[i]))
{
RejectRequestLine(start, end);
}
}
}
else
{
methodEnd = buffer.Slice(method.Length).Start;
}
var needDecode = false;
ReadCursor pathEnd;
var pathBegin = buffer.Move(methodEnd, 1);
var chFound = ReadCursorOperations.Seek(pathBegin, end, out pathEnd, ByteSpace, ByteQuestionMark, BytePercentage);
if (chFound == -1)
{
RejectRequestLine(start, end);
}
else if (chFound == BytePercentage)
{
needDecode = true;
chFound = ReadCursorOperations.Seek(pathBegin, end, out pathEnd, ByteSpace, ByteQuestionMark);
if (chFound == -1) if (chFound == -1)
{ {
RejectRequest(RequestRejectionReason.InvalidRequestLine, RejectRequestLine(start, end);
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
}
else if (chFound == BytePercentage)
{
needDecode = true;
chFound = scan.Seek(ByteSpace, ByteQuestionMark, ref end);
if (chFound == -1)
{
RejectRequest(RequestRejectionReason.InvalidRequestLine,
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
}
} }
};
var pathBegin = begin; var queryString = "";
var pathEnd = scan; ReadCursor queryEnd = pathEnd;
if (chFound == ByteQuestionMark)
var queryString = "";
if (chFound == ByteQuestionMark)
{
begin = scan;
if (scan.Seek(ByteSpace, ref end) == -1)
{
RejectRequest(RequestRejectionReason.InvalidRequestLine,
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
}
queryString = begin.GetAsciiString(ref scan);
}
var queryEnd = scan;
if (pathBegin.Peek() == ByteSpace)
{
RejectRequest(RequestRejectionReason.InvalidRequestLine,
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
}
scan.Take();
begin = scan;
if (scan.Seek(ByteCR, ref end) == -1)
{
RejectRequest(RequestRejectionReason.InvalidRequestLine,
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
}
string httpVersion;
if (!begin.GetKnownVersion(out httpVersion))
{
httpVersion = begin.GetAsciiStringEscaped(scan, 9);
if (httpVersion == string.Empty)
{
RejectRequest(RequestRejectionReason.InvalidRequestLine,
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
}
else
{
RejectRequest(RequestRejectionReason.UnrecognizedHTTPVersion, httpVersion);
}
}
scan.Take(); // consume CR
if (scan.Take() != ByteLF)
{
RejectRequest(RequestRejectionReason.InvalidRequestLine,
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxInvalidRequestLineChars) : string.Empty);
}
// URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
// Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
// then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"
string requestUrlPath;
string rawTarget;
if (needDecode)
{
// Read raw target before mutating memory.
rawTarget = pathBegin.GetAsciiString(ref queryEnd);
// URI was encoded, unescape and then parse as utf8
pathEnd = UrlPathDecoder.Unescape(pathBegin, pathEnd);
requestUrlPath = pathBegin.GetUtf8String(ref pathEnd);
}
else
{
// URI wasn't encoded, parse as ASCII
requestUrlPath = pathBegin.GetAsciiString(ref pathEnd);
if (queryString.Length == 0)
{
// No need to allocate an extra string if the path didn't need
// decoding and there's no query string following it.
rawTarget = requestUrlPath;
}
else
{
rawTarget = pathBegin.GetAsciiString(ref queryEnd);
}
}
var normalizedTarget = PathNormalizer.RemoveDotSegments(requestUrlPath);
consumed = scan;
Method = method;
QueryString = queryString;
RawTarget = rawTarget;
HttpVersion = httpVersion;
bool caseMatches;
if (RequestUrlStartsWithPathBase(normalizedTarget, out caseMatches))
{
PathBase = caseMatches ? _pathBase : normalizedTarget.Substring(0, _pathBase.Length);
Path = normalizedTarget.Substring(_pathBase.Length);
}
else if (rawTarget[0] == '/') // check rawTarget since normalizedTarget can be "" or "/" after dot segment removal
{
Path = normalizedTarget;
}
else
{
Path = string.Empty;
PathBase = string.Empty;
QueryString = string.Empty;
}
return RequestLineStatus.Done;
}
finally
{ {
input.ConsumingComplete(consumed, end); if (ReadCursorOperations.Seek(pathEnd, end, out queryEnd, ByteSpace) == -1)
{
RejectRequestLine(start, end);
}
queryString = buffer.Slice(pathEnd, queryEnd).GetAsciiString();
} }
// No path
if (pathBegin == pathEnd)
{
RejectRequestLine(start, end);
}
ReadCursor versionEnd;
if (ReadCursorOperations.Seek(queryEnd, end, out versionEnd, ByteCR) == -1)
{
RejectRequestLine(start, end);
}
string httpVersion;
var versionBuffer = buffer.Slice(queryEnd, end).Slice(1);
if (!versionBuffer.GetKnownVersion(out httpVersion))
{
httpVersion = versionBuffer.Start.GetAsciiStringEscaped(versionEnd, 9);
if (httpVersion == string.Empty)
{
RejectRequestLine(start, end);
}
else
{
RejectRequest(RequestRejectionReason.UnrecognizedHTTPVersion, httpVersion);
}
}
var lineEnd = buffer.Slice(versionEnd, 2).ToSpan();
if (lineEnd[1] != ByteLF)
{
RejectRequestLine(start, end);
}
var pathBuffer = buffer.Slice(pathBegin, pathEnd);
var targetBuffer = buffer.Slice(pathBegin, queryEnd);
// URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
// Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
// then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"
string requestUrlPath;
string rawTarget;
if (needDecode)
{
// Read raw target before mutating memory.
rawTarget = targetBuffer.GetAsciiString() ?? string.Empty;
// URI was encoded, unescape and then parse as utf8
var pathSpan = pathBuffer.ToSpan();
int pathLength = UrlEncoder.Decode(pathSpan, pathSpan);
requestUrlPath = new Utf8String(pathSpan.Slice(0, pathLength)).ToString();
}
else
{
// URI wasn't encoded, parse as ASCII
requestUrlPath = pathBuffer.GetAsciiString() ?? string.Empty;
if (queryString.Length == 0)
{
// No need to allocate an extra string if the path didn't need
// decoding and there's no query string following it.
rawTarget = requestUrlPath;
}
else
{
rawTarget = targetBuffer.GetAsciiString() ?? string.Empty;
}
}
var normalizedTarget = PathNormalizer.RemoveDotSegments(requestUrlPath);
consumed = end;
examined = end;
Method = method;
QueryString = queryString;
RawTarget = rawTarget;
HttpVersion = httpVersion;
bool caseMatches;
if (RequestUrlStartsWithPathBase(normalizedTarget, out caseMatches))
{
PathBase = caseMatches ? _pathBase : normalizedTarget.Substring(0, _pathBase.Length);
Path = normalizedTarget.Substring(_pathBase.Length);
}
else if (rawTarget[0] == '/') // check rawTarget since normalizedTarget can be "" or "/" after dot segment removal
{
Path = normalizedTarget;
}
else
{
Path = string.Empty;
PathBase = string.Empty;
QueryString = string.Empty;
}
return true;
}
private void RejectRequestLine(ReadCursor start, ReadCursor end)
{
const int MaxRequestLineError = 32;
RejectRequest(RequestRejectionReason.InvalidRequestLine,
Log.IsEnabled(LogLevel.Information) ? start.GetAsciiStringEscaped(end, MaxRequestLineError) : string.Empty);
} }
private static bool IsValidTokenChar(char c) private static bool IsValidTokenChar(char c)
@ -1255,34 +1241,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return true; return true;
} }
public bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders) public bool TakeMessageHeaders(ReadableBuffer buffer, FrameRequestHeaders requestHeaders, out ReadCursor consumed, out ReadCursor examined)
{ {
var scan = input.ConsumingStart(); consumed = buffer.Start;
var consumed = scan; examined = buffer.End;
var end = scan;
try while (true)
{ {
while (!end.IsEnd) var headersEnd = buffer.Slice(0, Math.Min(buffer.Length, 2));
var headersEndSpan = headersEnd.ToSpan();
if (headersEndSpan.Length == 0)
{ {
var ch = end.Peek(); return false;
if (ch == -1) }
{ else
return false; {
} var ch = headersEndSpan[0];
else if (ch == ByteCR) if (ch == ByteCR)
{ {
// Check for final CRLF. // Check for final CRLF.
end.Take(); if (headersEndSpan.Length < 2)
ch = end.Take();
if (ch == -1)
{ {
return false; return false;
} }
else if (ch == ByteLF) else if (headersEndSpan[1] == ByteLF)
{ {
consumed = headersEnd.End;
examined = consumed;
ConnectionControl.CancelTimeout(); ConnectionControl.CancelTimeout();
consumed = end;
return true; return true;
} }
@ -1293,129 +1280,113 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
RejectRequest(RequestRejectionReason.HeaderLineMustNotStartWithWhitespace); RejectRequest(RequestRejectionReason.HeaderLineMustNotStartWithWhitespace);
} }
}
// If we've parsed the max allowed numbers of headers and we're starting a new // If we've parsed the max allowed numbers of headers and we're starting a new
// one, we've gone over the limit. // one, we've gone over the limit.
if (_requestHeadersParsed == ServerOptions.Limits.MaxRequestHeaderCount) if (_requestHeadersParsed == ServerOptions.Limits.MaxRequestHeaderCount)
{
RejectRequest(RequestRejectionReason.TooManyHeaders);
}
ReadCursor lineEnd;
var limitedBuffer = buffer;
if (buffer.Length >= _remainingRequestHeadersBytesAllowed)
{
limitedBuffer = buffer.Slice(0, _remainingRequestHeadersBytesAllowed);
}
if (ReadCursorOperations.Seek(limitedBuffer.Start, limitedBuffer.End, out lineEnd, ByteLF) == -1)
{
if (limitedBuffer.Length == _remainingRequestHeadersBytesAllowed)
{ {
RejectRequest(RequestRejectionReason.TooManyHeaders); RejectRequest(RequestRejectionReason.HeadersExceedMaxTotalSize);
} }
else
int bytesScanned;
if (end.Seek(ByteLF, out bytesScanned, _remainingRequestHeadersBytesAllowed) == -1)
{
if (bytesScanned >= _remainingRequestHeadersBytesAllowed)
{
RejectRequest(RequestRejectionReason.HeadersExceedMaxTotalSize);
}
else
{
return false;
}
}
var beginName = scan;
if (scan.Seek(ByteColon, ref end) == -1)
{
RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
}
var endName = scan;
scan.Take();
var validateName = beginName;
if (validateName.Seek(ByteSpace, ByteTab, ref endName) != -1)
{
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
}
var beginValue = scan;
ch = scan.Take();
while (ch == ByteSpace || ch == ByteTab)
{
beginValue = scan;
ch = scan.Take();
}
scan = beginValue;
if (scan.Seek(ByteCR, ref end) == -1)
{
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
}
scan.Take(); // we know this is '\r'
ch = scan.Take(); // expecting '\n'
end = scan;
if (ch != ByteLF)
{
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
}
var next = scan.Peek();
if (next == -1)
{ {
return false; return false;
} }
else if (next == ByteSpace || next == ByteTab)
{
// From https://tools.ietf.org/html/rfc7230#section-3.2.4:
//
// Historically, HTTP header field values could be extended over
// multiple lines by preceding each extra line with at least one space
// or horizontal tab (obs-fold). This specification deprecates such
// line folding except within the message/http media type
// (Section 8.3.1). A sender MUST NOT generate a message that includes
// line folding (i.e., that has any field-value that contains a match to
// the obs-fold rule) unless the message is intended for packaging
// within the message/http media type.
//
// A server that receives an obs-fold in a request message that is not
// within a message/http container MUST either reject the message by
// sending a 400 (Bad Request), preferably with a representation
// explaining that obsolete line folding is unacceptable, or replace
// each received obs-fold with one or more SP octets prior to
// interpreting the field value or forwarding the message downstream.
RejectRequest(RequestRejectionReason.HeaderValueLineFoldingNotSupported);
}
// Trim trailing whitespace from header value by repeatedly advancing to next
// whitespace or CR.
//
// - If CR is found, this is the end of the header value.
// - If whitespace is found, this is the _tentative_ end of the header value.
// If non-whitespace is found after it and it's not CR, seek again to the next
// whitespace or CR for a new (possibly tentative) end of value.
var ws = beginValue;
var endValue = scan;
do
{
ws.Seek(ByteSpace, ByteTab, ByteCR);
endValue = ws;
ch = ws.Take();
while (ch == ByteSpace || ch == ByteTab)
{
ch = ws.Take();
}
} while (ch != ByteCR);
var name = beginName.GetArraySegment(endName);
var value = beginValue.GetAsciiString(ref endValue);
consumed = scan;
requestHeaders.Append(name.Array, name.Offset, name.Count, value);
_remainingRequestHeadersBytesAllowed -= bytesScanned;
_requestHeadersParsed++;
} }
return false; var beginName = buffer.Start;
} ReadCursor endName;
finally if (ReadCursorOperations.Seek(buffer.Start, lineEnd, out endName, ByteColon) == -1)
{ {
input.ConsumingComplete(consumed, end); RejectRequest(RequestRejectionReason.NoColonCharacterFoundInHeaderLine);
}
ReadCursor whitespace;
if (ReadCursorOperations.Seek(beginName, endName, out whitespace, ByteTab, ByteSpace) != -1)
{
RejectRequest(RequestRejectionReason.WhitespaceIsNotAllowedInHeaderName);
}
ReadCursor endValue;
if (ReadCursorOperations.Seek(beginName, lineEnd, out endValue, ByteCR) == -1)
{
RejectRequest(RequestRejectionReason.MissingCRInHeaderLine);
}
var lineSufix = buffer.Slice(endValue);
if (lineSufix.Length < 3)
{
return false;
}
lineSufix = lineSufix.Slice(0, 3); // \r\n\r
var lineSufixSpan = lineSufix.ToSpan();
// This check and MissingCRInHeaderLine is a bit backwards, we should do it at once instead of having another seek
if (lineSufixSpan[1] != ByteLF)
{
RejectRequest(RequestRejectionReason.HeaderValueMustNotContainCR);
}
var next = lineSufixSpan[2];
if (next == ByteSpace || next == ByteTab)
{
// From https://tools.ietf.org/html/rfc7230#section-3.2.4:
//
// Historically, HTTP header field values could be extended over
// multiple lines by preceding each extra line with at least one space
// or horizontal tab (obs-fold). This specification deprecates such
// line folding except within the message/http media type
// (Section 8.3.1). A sender MUST NOT generate a message that includes
// line folding (i.e., that has any field-value that contains a match to
// the obs-fold rule) unless the message is intended for packaging
// within the message/http media type.
//
// A server that receives an obs-fold in a request message that is not
// within a message/http container MUST either reject the message by
// sending a 400 (Bad Request), preferably with a representation
// explaining that obsolete line folding is unacceptable, or replace
// each received obs-fold with one or more SP octets prior to
// interpreting the field value or forwarding the message downstream.
RejectRequest(RequestRejectionReason.HeaderValueLineFoldingNotSupported);
}
// Trim trailing whitespace from header value by repeatedly advancing to next
// whitespace or CR.
//
// - If CR is found, this is the end of the header value.
// - If whitespace is found, this is the _tentative_ end of the header value.
// If non-whitespace is found after it and it's not CR, seek again to the next
// whitespace or CR for a new (possibly tentative) end of value.
var nameBuffer = buffer.Slice(beginName, endName);
// TODO: TrimStart and TrimEnd are pretty slow
var valueBuffer = buffer.Slice(endName, endValue).Slice(1).TrimStart().TrimEnd();
var name = nameBuffer.ToArraySegment();
var value = valueBuffer.GetAsciiString();
lineEnd = limitedBuffer.Move(lineEnd, 1);
// TODO: bad
_remainingRequestHeadersBytesAllowed -= buffer.Slice(0, lineEnd).Length;
_requestHeadersParsed++;
requestHeaders.Append(name.Array, name.Offset, name.Count, value);
buffer = buffer.Slice(lineEnd);
consumed = buffer.Start;
} }
} }

Просмотреть файл

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Pipelines;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
@ -31,66 +32,95 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
/// </summary> /// </summary>
public override async Task RequestProcessingAsync() public override async Task RequestProcessingAsync()
{ {
var requestLineStatus = RequestLineStatus.Empty; var requestLineStatus = default(RequestLineStatus);
try try
{ {
while (!_requestProcessingStopping) while (!_requestProcessingStopping)
{ {
// If writer completes with an error Input.ReadAsyncDispatched would throw and
// this would not be reset to empty. But it's required by ECONNRESET check lower in the method.
requestLineStatus = RequestLineStatus.Empty;
ConnectionControl.SetTimeout(_keepAliveMilliseconds, TimeoutAction.CloseConnection); ConnectionControl.SetTimeout(_keepAliveMilliseconds, TimeoutAction.CloseConnection);
while (!_requestProcessingStopping) while (!_requestProcessingStopping)
{ {
requestLineStatus = TakeStartLine(Input); var result = await Input.Reader.ReadAsync();
var examined = result.Buffer.End;
var consumed = result.Buffer.End;
try
{
if (!result.Buffer.IsEmpty)
{
requestLineStatus = TakeStartLine(result.Buffer, out consumed, out examined)
? RequestLineStatus.Done : RequestLineStatus.Incomplete;
}
else
{
requestLineStatus = RequestLineStatus.Empty;
}
}
catch (InvalidOperationException)
{
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidRequestLine);
}
finally
{
Input.Reader.Advance(consumed, examined);
}
if (requestLineStatus == RequestLineStatus.Done) if (requestLineStatus == RequestLineStatus.Done)
{ {
break; break;
} }
if (Input.CheckFinOrThrow()) if (result.IsCompleted)
{ {
// We need to attempt to consume start lines and headers even after
// SocketInput.RemoteIntakeFin is set to true to ensure we don't close a
// connection without giving the application a chance to respond to a request
// sent immediately before the a FIN from the client.
requestLineStatus = TakeStartLine(Input);
if (requestLineStatus == RequestLineStatus.Empty) if (requestLineStatus == RequestLineStatus.Empty)
{ {
return; return;
} }
if (requestLineStatus != RequestLineStatus.Done) RejectRequest(RequestRejectionReason.InvalidRequestLine, requestLineStatus.ToString());
{
RejectRequest(RequestRejectionReason.InvalidRequestLine, requestLineStatus.ToString());
}
break;
} }
await Input;
} }
InitializeHeaders(); InitializeHeaders();
while (!_requestProcessingStopping && !TakeMessageHeaders(Input, FrameRequestHeaders)) while (!_requestProcessingStopping)
{ {
if (Input.CheckFinOrThrow())
{
// We need to attempt to consume start lines and headers even after
// SocketInput.RemoteIntakeFin is set to true to ensure we don't close a
// connection without giving the application a chance to respond to a request
// sent immediately before the a FIN from the client.
if (!TakeMessageHeaders(Input, FrameRequestHeaders))
{
RejectRequest(RequestRejectionReason.MalformedRequestInvalidHeaders);
}
var result = await Input.Reader.ReadAsync();
var examined = result.Buffer.End;
var consumed = result.Buffer.End;
bool headersDone;
try
{
headersDone = TakeMessageHeaders(result.Buffer, FrameRequestHeaders, out consumed,
out examined);
}
catch (InvalidOperationException)
{
throw BadHttpRequestException.GetException(RequestRejectionReason.MalformedRequestInvalidHeaders);
}
finally
{
Input.Reader.Advance(consumed, examined);
}
if (headersDone)
{
break; break;
} }
await Input; if (result.IsCompleted)
{
RejectRequest(RequestRejectionReason.MalformedRequestInvalidHeaders);
}
} }
if (!_requestProcessingStopping) if (!_requestProcessingStopping)
@ -216,6 +246,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
try try
{ {
Input.Reader.Complete();
// If _requestAborted is set, the connection has already been closed. // If _requestAborted is set, the connection has already been closed.
if (Volatile.Read(ref _requestAborted) == 0) if (Volatile.Read(ref _requestAborted) == 0)
{ {

Просмотреть файл

@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
Thread.Post(state => Thread.Post(state =>
{ {
var tcs2 = (TaskCompletionSource<int>) state; var tcs2 = state;
try try
{ {
var listener = ((Listener) tcs2.Task.AsyncState); var listener = ((Listener) tcs2.Task.AsyncState);

Просмотреть файл

@ -2,6 +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.IO.Pipelines;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking; using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
@ -40,5 +41,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
} }
public PipeOptions LibuvPipeOptions => new PipeOptions
{
ReaderScheduler = TaskRunScheduler.Default,
WriterScheduler = Thread,
MaximumSizeHigh = ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0,
MaximumSizeLow = ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0
};
public PipeOptions AdaptedPipeOptions => new PipeOptions
{
ReaderScheduler = InlineScheduler.Default,
WriterScheduler = InlineScheduler.Default,
MaximumSizeHigh = ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0,
MaximumSizeLow = ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0
};
} }
} }

Просмотреть файл

@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
DispatchPipe = new UvPipeHandle(Log); DispatchPipe = new UvPipeHandle(Log);
var tcs = new TaskCompletionSource<int>(this); var tcs = new TaskCompletionSource<int>(this);
Thread.Post(state => StartCallback((TaskCompletionSource<int>)state), tcs); Thread.Post(StartCallback, tcs);
return tcs.Task; return tcs.Task;
} }

Просмотреть файл

@ -3,10 +3,10 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Pipelines;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
@ -215,9 +215,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
private void ConsumedBytes(int count) private void ConsumedBytes(int count)
{ {
var scan = _context.Input.ConsumingStart(); var scan = _context.Input.Reader.ReadAsync().GetResult().Buffer;
scan.Skip(count); var consumed = scan.Move(scan.Start, count);
_context.Input.ConsumingComplete(scan, scan); _context.Input.Reader.Advance(consumed, consumed);
OnConsumedBytes(count); OnConsumedBytes(count);
} }
@ -304,7 +304,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
protected override ValueTask<ArraySegment<byte>> PeekAsync(CancellationToken cancellationToken) protected override ValueTask<ArraySegment<byte>> PeekAsync(CancellationToken cancellationToken)
{ {
return _context.Input.PeekAsync(); return _context.Input.Reader.PeekAsync();
} }
} }
@ -351,7 +351,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return new ValueTask<ArraySegment<byte>>(); return new ValueTask<ArraySegment<byte>>();
} }
var task = _context.Input.PeekAsync(); var task = _context.Input.Reader.PeekAsync();
if (task.IsCompleted) if (task.IsCompleted)
{ {
@ -413,7 +413,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
// byte consts don't have a data type annotation so we pre-cast it // byte consts don't have a data type annotation so we pre-cast it
private const byte ByteCR = (byte)'\r'; private const byte ByteCR = (byte)'\r';
private readonly SocketInput _input; private readonly IPipeReader _input;
private readonly FrameRequestHeaders _requestHeaders; private readonly FrameRequestHeaders _requestHeaders;
private int _inputLength; private int _inputLength;
@ -423,7 +423,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
: base(context) : base(context)
{ {
RequestKeepAlive = keepAlive; RequestKeepAlive = keepAlive;
_input = _context.Input; _input = _context.Input.Reader;
_requestHeaders = headers; _requestHeaders = headers;
} }
@ -443,45 +443,71 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
while (_mode == Mode.Prefix) while (_mode == Mode.Prefix)
{ {
var fin = _input.CheckFinOrThrow(); var result = await _input.ReadAsync();
var buffer = result.Buffer;
var consumed = default(ReadCursor);
var examined = default(ReadCursor);
ParseChunkedPrefix(); try
{
ParseChunkedPrefix(buffer, out consumed, out examined);
}
finally
{
_input.Advance(consumed, examined);
}
if (_mode != Mode.Prefix) if (_mode != Mode.Prefix)
{ {
break; break;
} }
else if (fin) else if (result.IsCompleted)
{ {
_context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete); _context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
} }
await _input;
} }
while (_mode == Mode.Extension) while (_mode == Mode.Extension)
{ {
var fin = _input.CheckFinOrThrow(); var result = await _input.ReadAsync();
var buffer = result.Buffer;
var consumed = default(ReadCursor);
var examined = default(ReadCursor);
ParseExtension(); try
{
ParseExtension(buffer, out consumed, out examined);
}
finally
{
_input.Advance(consumed, examined);
}
if (_mode != Mode.Extension) if (_mode != Mode.Extension)
{ {
break; break;
} }
else if (fin) else if (result.IsCompleted)
{ {
_context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete); _context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
} }
await _input;
} }
while (_mode == Mode.Data) while (_mode == Mode.Data)
{ {
var fin = _input.CheckFinOrThrow(); var result = await _input.ReadAsync();
var buffer = result.Buffer;
var segment = PeekChunkedData(); ArraySegment<byte> segment;
try
{
segment = PeekChunkedData(buffer);
}
finally
{
_input.Advance(buffer.Start, buffer.Start);
}
if (segment.Count != 0) if (segment.Count != 0)
{ {
@ -491,195 +517,214 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
break; break;
} }
else if (fin) else if (result.IsCompleted)
{ {
_context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete); _context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
} }
await _input;
} }
while (_mode == Mode.Suffix) while (_mode == Mode.Suffix)
{ {
var fin = _input.CheckFinOrThrow(); var result = await _input.ReadAsync();
var buffer = result.Buffer;
var consumed = default(ReadCursor);
var examined = default(ReadCursor);
ParseChunkedSuffix(); try
{
ParseChunkedSuffix(buffer, out consumed, out examined);
}
finally
{
_input.Advance(consumed, examined);
}
if (_mode != Mode.Suffix) if (_mode != Mode.Suffix)
{ {
break; break;
} }
else if (fin) else if (result.IsCompleted)
{ {
_context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete); _context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
} }
await _input;
} }
} }
// Chunks finished, parse trailers // Chunks finished, parse trailers
while (_mode == Mode.Trailer) while (_mode == Mode.Trailer)
{ {
var fin = _input.CheckFinOrThrow(); var result = await _input.ReadAsync();
var buffer = result.Buffer;
var consumed = default(ReadCursor);
var examined = default(ReadCursor);
ParseChunkedTrailer(); try
{
ParseChunkedTrailer(buffer, out consumed, out examined);
}
finally
{
_input.Advance(consumed, examined);
}
if (_mode != Mode.Trailer) if (_mode != Mode.Trailer)
{ {
break; break;
} }
else if (fin) else if (result.IsCompleted)
{ {
_context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete); _context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
} }
await _input;
} }
if (_mode == Mode.TrailerHeaders) if (_mode == Mode.TrailerHeaders)
{ {
while (!_context.TakeMessageHeaders(_input, _requestHeaders)) while (true)
{ {
if (_input.CheckFinOrThrow()) var result = await _input.ReadAsync();
var buffer = result.Buffer;
if (buffer.IsEmpty && result.IsCompleted)
{ {
if (_context.TakeMessageHeaders(_input, _requestHeaders)) _context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
}
var consumed = default(ReadCursor);
var examined = default(ReadCursor);
try
{
if (_context.TakeMessageHeaders(buffer, _requestHeaders, out consumed, out examined))
{ {
break; break;
} }
else
{
_context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
}
} }
finally
await _input; {
_input.Advance(consumed, examined);
}
} }
_mode = Mode.Complete; _mode = Mode.Complete;
} }
return default(ArraySegment<byte>); return default(ArraySegment<byte>);
} }
private void ParseChunkedPrefix() private void ParseChunkedPrefix(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
{ {
var scan = _input.ConsumingStart(); consumed = buffer.Start;
var consumed = scan; examined = buffer.Start;
try var reader = new ReadableBufferReader(buffer);
var ch1 = reader.Take();
var ch2 = reader.Take();
if (ch1 == -1 || ch2 == -1)
{ {
var ch1 = scan.Take(); examined = reader.Cursor;
var ch2 = scan.Take(); return;
if (ch1 == -1 || ch2 == -1) }
var chunkSize = CalculateChunkSize(ch1, 0);
ch1 = ch2;
do
{
if (ch1 == ';')
{ {
consumed = reader.Cursor;
examined = reader.Cursor;
_inputLength = chunkSize;
_mode = Mode.Extension;
return; return;
} }
var chunkSize = CalculateChunkSize(ch1, 0); ch2 = reader.Take();
if (ch2 == -1)
{
examined = reader.Cursor;
return;
}
if (ch1 == '\r' && ch2 == '\n')
{
consumed = reader.Cursor;
examined = reader.Cursor;
_inputLength = chunkSize;
if (chunkSize > 0)
{
_mode = Mode.Data;
}
else
{
_mode = Mode.Trailer;
}
return;
}
chunkSize = CalculateChunkSize(ch1, chunkSize);
ch1 = ch2; ch1 = ch2;
} while (ch1 != -1);
do
{
if (ch1 == ';')
{
consumed = scan;
_inputLength = chunkSize;
_mode = Mode.Extension;
return;
}
ch2 = scan.Take();
if (ch2 == -1)
{
return;
}
if (ch1 == '\r' && ch2 == '\n')
{
consumed = scan;
_inputLength = chunkSize;
if (chunkSize > 0)
{
_mode = Mode.Data;
}
else
{
_mode = Mode.Trailer;
}
return;
}
chunkSize = CalculateChunkSize(ch1, chunkSize);
ch1 = ch2;
} while (ch1 != -1);
}
finally
{
_input.ConsumingComplete(consumed, scan);
}
} }
private void ParseExtension() private void ParseExtension(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
{ {
var scan = _input.ConsumingStart(); // Chunk-extensions not currently parsed
var consumed = scan; // Just drain the data
try consumed = buffer.Start;
examined = buffer.Start;
do
{ {
// Chunk-extensions not currently parsed ReadCursor extensionCursor;
// Just drain the data if (ReadCursorOperations.Seek(buffer.Start, buffer.End, out extensionCursor, ByteCR) == -1)
do
{ {
if (scan.Seek(ByteCR) == -1) // End marker not found yet
{ examined = buffer.End;
// End marker not found yet return;
consumed = scan; };
return;
};
var ch1 = scan.Take(); var sufixBuffer = buffer.Slice(extensionCursor);
var ch2 = scan.Take(); if (sufixBuffer.Length < 2)
{
examined = buffer.End;
return;
}
if (ch2 == '\n') sufixBuffer = sufixBuffer.Slice(0, 2);
var sufixSpan = sufixBuffer.ToSpan();
if (sufixSpan[1] == '\n')
{
consumed = sufixBuffer.End;
examined = sufixBuffer.End;
if (_inputLength > 0)
{ {
consumed = scan; _mode = Mode.Data;
if (_inputLength > 0)
{
_mode = Mode.Data;
}
else
{
_mode = Mode.Trailer;
}
} }
else if (ch2 == -1) else
{ {
return; _mode = Mode.Trailer;
} }
} while (_mode == Mode.Extension); }
} } while (_mode == Mode.Extension);
finally
{
_input.ConsumingComplete(consumed, scan);
}
} }
private ArraySegment<byte> PeekChunkedData() private ArraySegment<byte> PeekChunkedData(ReadableBuffer buffer)
{ {
if (_inputLength == 0) if (_inputLength == 0)
{ {
_mode = Mode.Suffix; _mode = Mode.Suffix;
return default(ArraySegment<byte>); return default(ArraySegment<byte>);
} }
var segment = buffer.First.GetArray();
var scan = _input.ConsumingStart();
var segment = scan.PeekArraySegment();
int actual = Math.Min(segment.Count, _inputLength); int actual = Math.Min(segment.Count, _inputLength);
// Nothing is consumed yet. ConsumedBytes(int) will move the iterator. // Nothing is consumed yet. ConsumedBytes(int) will move the iterator.
_input.ConsumingComplete(scan, scan);
if (actual == segment.Count) if (actual == segment.Count)
{ {
return segment; return segment;
@ -690,60 +735,54 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
} }
} }
private void ParseChunkedSuffix() private void ParseChunkedSuffix(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
{ {
var scan = _input.ConsumingStart(); consumed = buffer.Start;
var consumed = scan; examined = buffer.Start;
try
if (buffer.Length < 2)
{ {
var ch1 = scan.Take(); examined = buffer.End;
var ch2 = scan.Take(); return;
if (ch1 == -1 || ch2 == -1)
{
return;
}
else if (ch1 == '\r' && ch2 == '\n')
{
consumed = scan;
_mode = Mode.Prefix;
}
else
{
_context.RejectRequest(RequestRejectionReason.BadChunkSuffix);
}
} }
finally
var sufixBuffer = buffer.Slice(0, 2);
var sufixSpan = sufixBuffer.ToSpan();
if (sufixSpan[0] == '\r' && sufixSpan[1] == '\n')
{ {
_input.ConsumingComplete(consumed, scan); consumed = sufixBuffer.End;
examined = sufixBuffer.End;
_mode = Mode.Prefix;
}
else
{
_context.RejectRequest(RequestRejectionReason.BadChunkSuffix);
} }
} }
private void ParseChunkedTrailer() private void ParseChunkedTrailer(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
{ {
var scan = _input.ConsumingStart(); consumed = buffer.Start;
var consumed = scan; examined = buffer.Start;
try
{
var ch1 = scan.Take();
var ch2 = scan.Take();
if (ch1 == -1 || ch2 == -1) if (buffer.Length < 2)
{
return;
}
else if (ch1 == '\r' && ch2 == '\n')
{
consumed = scan;
_mode = Mode.Complete;
}
else
{
_mode = Mode.TrailerHeaders;
}
}
finally
{ {
_input.ConsumingComplete(consumed, scan); examined = buffer.End;
return;
}
var trailerBuffer = buffer.Slice(0, 2);
var trailerSpan = trailerBuffer.ToSpan();
if (trailerSpan[0] == '\r' && trailerSpan[1] == '\n')
{
consumed = trailerBuffer.End;
examined = trailerBuffer.End;
_mode = Mode.Complete;
}
else
{
_mode = Mode.TrailerHeaders;
} }
} }

Просмотреть файл

@ -0,0 +1,108 @@
// 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.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
public static class PipelineExtensions
{
public static ValueTask<ArraySegment<byte>> PeekAsync(this IPipeReader pipelineReader)
{
var input = pipelineReader.ReadAsync();
while (input.IsCompleted)
{
var result = input.GetResult();
try
{
if (!result.Buffer.IsEmpty)
{
var segment = result.Buffer.First;
var data = segment.GetArray();
return new ValueTask<ArraySegment<byte>>(data);
}
else if (result.IsCompleted || result.IsCancelled)
{
return default(ValueTask<ArraySegment<byte>>);
}
}
finally
{
pipelineReader.Advance(result.Buffer.Start, result.Buffer.Start);
}
input = pipelineReader.ReadAsync();
}
return new ValueTask<ArraySegment<byte>>(pipelineReader.PeekAsyncAwaited(input));
}
private static async Task<ArraySegment<byte>> PeekAsyncAwaited(this IPipeReader pipelineReader, ReadableBufferAwaitable readingTask)
{
while (true)
{
var result = await readingTask;
await AwaitableThreadPool.Yield();
try
{
if (!result.Buffer.IsEmpty)
{
var segment = result.Buffer.First;
return segment.GetArray();
}
else if (result.IsCompleted || result.IsCancelled)
{
return default(ArraySegment<byte>);
}
}
finally
{
pipelineReader.Advance(result.Buffer.Start, result.Buffer.Start);
}
readingTask = pipelineReader.ReadAsync();
}
}
private static async Task<ReadResult> ReadAsyncDispatchedAwaited(ReadableBufferAwaitable awaitable)
{
var result = await awaitable;
await AwaitableThreadPool.Yield();
return result;
}
public static Span<byte> ToSpan(this ReadableBuffer buffer)
{
if (buffer.IsSingleSpan)
{
return buffer.First.Span;
}
return buffer.ToArray();
}
public static ArraySegment<byte> ToArraySegment(this ReadableBuffer buffer)
{
if (buffer.IsSingleSpan)
{
return buffer.First.GetArray();
}
return new ArraySegment<byte>(buffer.ToArray());
}
public static ArraySegment<byte> GetArray(this Memory<byte> memory)
{
ArraySegment<byte> result;
if (!memory.TryGetArray(out result))
{
throw new InvalidOperationException("Memory backed by array was expected");
}
return result;
}
}
}

Просмотреть файл

@ -30,6 +30,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
RequestTimeout, RequestTimeout,
FinalTransferCodingNotChunked, FinalTransferCodingNotChunked,
LengthRequired, LengthRequired,
LengthRequiredHttp10, LengthRequiredHttp10
} }
} }

Просмотреть файл

@ -1,351 +0,0 @@
// 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.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
public class SocketInput : ICriticalNotifyCompletion, IDisposable
{
private static readonly Action _awaitableIsCompleted = () => { };
private static readonly Action _awaitableIsNotCompleted = () => { };
private readonly MemoryPool _memory;
private readonly IThreadPool _threadPool;
private readonly IBufferSizeControl _bufferSizeControl;
private readonly ManualResetEventSlim _manualResetEvent = new ManualResetEventSlim(false, 0);
private Action _awaitableState;
private MemoryPoolBlock _head;
private MemoryPoolBlock _tail;
private MemoryPoolBlock _pinned;
private object _sync = new object();
private bool _consuming;
private bool _disposed;
private TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>();
public SocketInput(MemoryPool memory, IThreadPool threadPool, IBufferSizeControl bufferSizeControl = null)
{
_memory = memory;
_threadPool = threadPool;
_bufferSizeControl = bufferSizeControl;
_awaitableState = _awaitableIsNotCompleted;
}
public bool IsCompleted => ReferenceEquals(_awaitableState, _awaitableIsCompleted);
private bool ReadingInput => _tcs.Task.Status == TaskStatus.WaitingForActivation;
public bool CheckFinOrThrow()
{
CheckConnectionError();
return _tcs.Task.Status == TaskStatus.RanToCompletion;
}
public MemoryPoolBlock IncomingStart()
{
lock (_sync)
{
const int minimumSize = 2048;
if (_tail != null && minimumSize <= _tail.Data.Offset + _tail.Data.Count - _tail.End)
{
_pinned = _tail;
}
else
{
_pinned = _memory.Lease();
}
return _pinned;
}
}
public void IncomingComplete(int count, Exception error)
{
Action awaitableState;
lock (_sync)
{
// Must call Add() before bytes are available to consumer, to ensure that Length is >= 0
_bufferSizeControl?.Add(count);
if (_pinned != null)
{
_pinned.End += count;
if (_head == null)
{
_head = _tail = _pinned;
}
else if (_tail == _pinned)
{
// NO-OP: this was a read into unoccupied tail-space
}
else
{
Volatile.Write(ref _tail.Next, _pinned);
_tail = _pinned;
}
_pinned = null;
}
if (error != null)
{
SetConnectionError(error);
}
else if (count == 0)
{
FinReceived();
}
awaitableState = Interlocked.Exchange(ref _awaitableState, _awaitableIsCompleted);
}
Complete(awaitableState);
}
public void IncomingDeferred()
{
Debug.Assert(_pinned != null);
lock (_sync)
{
if (_pinned != null)
{
if (_pinned != _tail)
{
_memory.Return(_pinned);
}
_pinned = null;
}
}
}
private void Complete(Action awaitableState)
{
_manualResetEvent.Set();
if (!ReferenceEquals(awaitableState, _awaitableIsCompleted) &&
!ReferenceEquals(awaitableState, _awaitableIsNotCompleted))
{
_threadPool.Run(awaitableState);
}
}
public MemoryPoolIterator ConsumingStart()
{
MemoryPoolBlock head;
bool isAlreadyConsuming;
lock (_sync)
{
isAlreadyConsuming = _consuming;
head = _head;
_consuming = true;
}
if (isAlreadyConsuming)
{
throw new InvalidOperationException("Already consuming input.");
}
return new MemoryPoolIterator(head);
}
public void ConsumingComplete(
MemoryPoolIterator consumed,
MemoryPoolIterator examined)
{
bool isConsuming;
MemoryPoolBlock returnStart = null;
MemoryPoolBlock returnEnd = null;
lock (_sync)
{
if (!_disposed)
{
if (!consumed.IsDefault)
{
// Compute lengthConsumed before modifying _head or consumed
var lengthConsumed = 0;
if (_bufferSizeControl != null)
{
lengthConsumed = new MemoryPoolIterator(_head).GetLength(consumed);
}
returnStart = _head;
var consumedAll = !consumed.IsDefault && consumed.IsEnd;
if (consumedAll && _pinned != _tail)
{
// Everything has been consumed and no data is being written to the
// _tail block, so return all blocks between _head and _tail inclusive.
_head = null;
_tail = null;
}
else
{
returnEnd = consumed.Block;
_head = consumed.Block;
_head.Start = consumed.Index;
}
// Must call Subtract() after _head has been advanced, to avoid producer starting too early and growing
// buffer beyond max length.
_bufferSizeControl?.Subtract(lengthConsumed);
}
// If _head is null, everything has been consumed and examined.
var examinedAll = (!examined.IsDefault && examined.IsEnd) || _head == null;
if (examinedAll && ReadingInput)
{
_manualResetEvent.Reset();
Interlocked.CompareExchange(
ref _awaitableState,
_awaitableIsNotCompleted,
_awaitableIsCompleted);
}
}
else
{
// Dispose won't have returned the blocks if we were consuming, so return them now
returnStart = _head;
_head = null;
_tail = null;
}
isConsuming = _consuming;
_consuming = false;
}
ReturnBlocks(returnStart, returnEnd);
if (!isConsuming)
{
throw new InvalidOperationException("No ongoing consuming operation to complete.");
}
}
public void CompleteAwaiting()
{
Complete(Interlocked.Exchange(ref _awaitableState, _awaitableIsCompleted));
}
public void AbortAwaiting()
{
SetConnectionError(new TaskCanceledException("The request was aborted"));
CompleteAwaiting();
}
public SocketInput GetAwaiter()
{
return this;
}
public void OnCompleted(Action continuation)
{
var awaitableState = Interlocked.CompareExchange(
ref _awaitableState,
continuation,
_awaitableIsNotCompleted);
if (ReferenceEquals(awaitableState, _awaitableIsCompleted))
{
_threadPool.Run(continuation);
}
else if (!ReferenceEquals(awaitableState, _awaitableIsNotCompleted))
{
SetConnectionError(new InvalidOperationException("Concurrent reads are not supported."));
Interlocked.Exchange(
ref _awaitableState,
_awaitableIsCompleted);
_manualResetEvent.Set();
_threadPool.Run(continuation);
_threadPool.Run(awaitableState);
}
}
public void UnsafeOnCompleted(Action continuation)
{
OnCompleted(continuation);
}
public void GetResult()
{
if (!IsCompleted)
{
_manualResetEvent.Wait();
}
CheckConnectionError();
}
public void Dispose()
{
AbortAwaiting();
MemoryPoolBlock block = null;
lock (_sync)
{
if (!_consuming)
{
block = _head;
_head = null;
_tail = null;
}
_disposed = true;
}
ReturnBlocks(block, null);
}
private static void ReturnBlocks(MemoryPoolBlock block, MemoryPoolBlock end)
{
while (block != end)
{
var returnBlock = block;
block = block.Next;
returnBlock.Pool.Return(returnBlock);
}
}
private void SetConnectionError(Exception error)
{
_tcs.TrySetException(error);
}
private void FinReceived()
{
_tcs.TrySetResult(null);
}
private void CheckConnectionError()
{
var error = _tcs.Task.Exception?.InnerException;
if (error != null)
{
throw error;
}
}
}
}

Просмотреть файл

@ -1,90 +0,0 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
public static class SocketInputExtensions
{
public static ValueTask<int> ReadAsync(this SocketInput input, byte[] buffer, int offset, int count)
{
while (input.IsCompleted)
{
var fin = input.CheckFinOrThrow();
var begin = input.ConsumingStart();
int actual;
var end = begin.CopyTo(buffer, offset, count, out actual);
input.ConsumingComplete(end, end);
if (actual != 0 || fin)
{
return new ValueTask<int>(actual);
}
}
return new ValueTask<int>(input.ReadAsyncAwaited(buffer, offset, count));
}
private static async Task<int> ReadAsyncAwaited(this SocketInput input, byte[] buffer, int offset, int count)
{
while (true)
{
await input;
var fin = input.CheckFinOrThrow();
var begin = input.ConsumingStart();
int actual;
var end = begin.CopyTo(buffer, offset, count, out actual);
input.ConsumingComplete(end, end);
if (actual != 0 || fin)
{
return actual;
}
}
}
public static ValueTask<ArraySegment<byte>> PeekAsync(this SocketInput input)
{
while (input.IsCompleted)
{
var fin = input.CheckFinOrThrow();
var begin = input.ConsumingStart();
var segment = begin.PeekArraySegment();
input.ConsumingComplete(begin, begin);
if (segment.Count != 0 || fin)
{
return new ValueTask<ArraySegment<byte>>(segment);
}
}
return new ValueTask<ArraySegment<byte>>(input.PeekAsyncAwaited());
}
private static async Task<ArraySegment<byte>> PeekAsyncAwaited(this SocketInput input)
{
while (true)
{
await input;
var fin = input.CheckFinOrThrow();
var begin = input.ConsumingStart();
var segment = begin.PeekArraySegment();
input.ConsumingComplete(begin, begin);
if (segment.Count != 0 || fin)
{
return segment;
}
}
}
}
}

Просмотреть файл

@ -327,7 +327,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
private void ScheduleWrite() private void ScheduleWrite()
{ {
_thread.Post(state => ((SocketOutput)state).WriteAllPending(), this); _thread.Post(state => state.WriteAllPending(), this);
} }
// This is called on the libuv event loop // This is called on the libuv event loop

Просмотреть файл

@ -0,0 +1,39 @@
// 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.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
{
internal static class AwaitableThreadPool
{
internal static Awaitable Yield()
{
return new Awaitable();
}
internal struct Awaitable : ICriticalNotifyCompletion
{
public void GetResult()
{
}
public Awaitable GetAwaiter() => this;
public bool IsCompleted => false;
public void OnCompleted(Action continuation)
{
Task.Run(continuation);
}
public void UnsafeOnCompleted(Action continuation)
{
OnCompleted(continuation);
}
}
}
}

Просмотреть файл

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO.Pipelines;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
@ -12,18 +13,17 @@ using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking; using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MemoryPool = Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure.MemoryPool;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Internal
{ {
/// <summary> /// <summary>
/// Summary description for KestrelThread /// Summary description for KestrelThread
/// </summary> /// </summary>
public class KestrelThread public class KestrelThread: IScheduler
{ {
public const long HeartbeatMilliseconds = 1000; public const long HeartbeatMilliseconds = 1000;
private static readonly Action<object, object> _postCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state);
private static readonly Action<object, object> _postAsyncCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state);
private static readonly Libuv.uv_walk_cb _heartbeatWalkCallback = (ptr, arg) => private static readonly Libuv.uv_walk_cb _heartbeatWalkCallback = (ptr, arg) =>
{ {
var streamHandle = UvMemory.FromIntPtr<UvHandle>(ptr) as UvStreamHandle; var streamHandle = UvMemory.FromIntPtr<UvHandle>(ptr) as UvStreamHandle;
@ -78,10 +78,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
QueueCloseHandle = PostCloseHandle; QueueCloseHandle = PostCloseHandle;
QueueCloseAsyncHandle = EnqueueCloseHandle; QueueCloseAsyncHandle = EnqueueCloseHandle;
Memory = new MemoryPool(); Memory = new MemoryPool();
PipelineFactory = new PipeFactory();
WriteReqPool = new WriteReqPool(this, _log); WriteReqPool = new WriteReqPool(this, _log);
ConnectionManager = new ConnectionManager(this, _threadPool); ConnectionManager = new ConnectionManager(this, _threadPool);
} }
// For testing // For testing
internal KestrelThread(KestrelEngine engine, int maxLoops) internal KestrelThread(KestrelEngine engine, int maxLoops)
: this(engine) : this(engine)
@ -93,6 +93,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
public MemoryPool Memory { get; } public MemoryPool Memory { get; }
public PipeFactory PipelineFactory { get; }
public ConnectionManager ConnectionManager { get; } public ConnectionManager ConnectionManager { get; }
public WriteReqPool WriteReqPool { get; } public WriteReqPool WriteReqPool { get; }
@ -180,7 +182,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
var result = await WaitAsync(PostAsync(state => var result = await WaitAsync(PostAsync(state =>
{ {
var listener = (KestrelThread)state; var listener = state;
listener.WriteReqPool.Dispose(); listener.WriteReqPool.Dispose();
}, },
this), _shutdownTimeout).ConfigureAwait(false); this), _shutdownTimeout).ConfigureAwait(false);
@ -193,6 +195,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
finally finally
{ {
Memory.Dispose(); Memory.Dispose();
PipelineFactory.Dispose();
} }
} }
@ -224,13 +227,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
_loop.Stop(); _loop.Stop();
} }
public void Post(Action<object> callback, object state) public void Post<T>(Action<T> callback, T state)
{ {
lock (_workSync) lock (_workSync)
{ {
_workAdding.Enqueue(new Work _workAdding.Enqueue(new Work
{ {
CallbackAdapter = _postCallbackAdapter, CallbackAdapter = CallbackAdapter<T>.PostCallbackAdapter,
Callback = callback, Callback = callback,
State = state State = state
}); });
@ -240,17 +243,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
private void Post(Action<KestrelThread> callback) private void Post(Action<KestrelThread> callback)
{ {
Post(thread => callback((KestrelThread)thread), this); Post(callback, this);
} }
public Task PostAsync(Action<object> callback, object state) public Task PostAsync<T>(Action<T> callback, T state)
{ {
var tcs = new TaskCompletionSource<object>(); var tcs = new TaskCompletionSource<object>();
lock (_workSync) lock (_workSync)
{ {
_workAdding.Enqueue(new Work _workAdding.Enqueue(new Work
{ {
CallbackAdapter = _postAsyncCallbackAdapter, CallbackAdapter = CallbackAdapter<T>.PostAsyncCallbackAdapter,
Callback = callback, Callback = callback,
State = state, State = state,
Completion = tcs Completion = tcs
@ -439,6 +442,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
return await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task; return await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task;
} }
public void Schedule(Action action)
{
Post(state => state(), action);
}
private struct Work private struct Work
{ {
public Action<object, object> CallbackAdapter; public Action<object, object> CallbackAdapter;
@ -452,5 +460,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
public Action<IntPtr> Callback; public Action<IntPtr> Callback;
public IntPtr Handle; public IntPtr Handle;
} }
private class CallbackAdapter<T>
{
public static readonly Action<object, object> PostCallbackAdapter = (callback, state) => ((Action<T>)callback).Invoke((T)state);
public static readonly Action<object, object> PostAsyncCallbackAdapter = (callback, state) => ((Action<T>)callback).Invoke((T)state);
}
} }
} }

Просмотреть файл

@ -3,6 +3,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO.Pipelines;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -69,19 +70,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
} }
} }
public static string GetAsciiStringEscaped(this MemoryPoolIterator start, MemoryPoolIterator end, int maxChars) public static string GetAsciiStringEscaped(this ReadCursor start, ReadCursor end, int maxChars)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
var scan = start; var reader = new ReadableBufferReader(start, end);
while (maxChars > 0 && (scan.Block != end.Block || scan.Index != end.Index)) while (maxChars > 0 && !reader.End)
{ {
var ch = scan.Take(); var ch = reader.Take();
sb.Append(ch < 0x20 || ch >= 0x7F ? $"<0x{ch.ToString("X2")}>" : ((char)ch).ToString()); sb.Append(ch < 0x20 || ch >= 0x7F ? $"<0x{ch:X2}>" : ((char)ch).ToString());
maxChars--; maxChars--;
} }
if (scan.Block != end.Block || scan.Index != end.Index) if (!reader.End)
{ {
sb.Append("..."); sb.Append("...");
} }
@ -130,16 +131,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
/// <param name="knownMethod">A reference to a pre-allocated known string, if the input matches any.</param> /// <param name="knownMethod">A reference to a pre-allocated known string, if the input matches any.</param>
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool GetKnownMethod(this MemoryPoolIterator begin, out string knownMethod) public static bool GetKnownMethod(this ReadableBuffer begin, out string knownMethod)
{ {
knownMethod = null; knownMethod = null;
if (begin.Length < sizeof(ulong))
ulong value;
if (!begin.TryPeekLong(out value))
{ {
return false; return false;
} }
ulong value = begin.ReadLittleEndian<ulong>();
if ((value & _mask4Chars) == _httpGetMethodLong) if ((value & _mask4Chars) == _httpGetMethodLong)
{ {
knownMethod = HttpMethods.Get; knownMethod = HttpMethods.Get;
@ -171,16 +171,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
/// <param name="knownVersion">A reference to a pre-allocated known string, if the input matches any.</param> /// <param name="knownVersion">A reference to a pre-allocated known string, if the input matches any.</param>
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool GetKnownVersion(this MemoryPoolIterator begin, out string knownVersion) public static bool GetKnownVersion(this ReadableBuffer begin, out string knownVersion)
{ {
knownVersion = null; knownVersion = null;
ulong value; if (begin.Length < sizeof(ulong))
if (!begin.TryPeekLong(out value))
{ {
return false; return false;
} }
var value = begin.ReadLittleEndian<ulong>();
if (value == _http11VersionLong) if (value == _http11VersionLong)
{ {
knownVersion = Http11Version; knownVersion = Http11Version;
@ -192,9 +192,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
if (knownVersion != null) if (knownVersion != null)
{ {
begin.Skip(knownVersion.Length); if (begin.Slice(sizeof(ulong)).Peek() != '\r')
if (begin.Peek() != '\r')
{ {
knownVersion = null; knownVersion = null;
} }

Просмотреть файл

@ -18,6 +18,10 @@
<PackageReference Include="Microsoft.Extensions.TaskCache.Sources" Version="1.2.0-*" PrivateAssets="All" /> <PackageReference Include="Microsoft.Extensions.TaskCache.Sources" Version="1.2.0-*" PrivateAssets="All" />
<PackageReference Include="System.Numerics.Vectors" Version="$(CoreFxVersion)" /> <PackageReference Include="System.Numerics.Vectors" Version="$(CoreFxVersion)" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(CoreFxVersion)" /> <PackageReference Include="System.Threading.Tasks.Extensions" Version="$(CoreFxVersion)" />
<PackageReference Include="System.IO.Pipelines" Version="0.1.0-*" />
<PackageReference Include="System.IO.Pipelines.Text.Primitives" Version="0.1.0-*" />
<PackageReference Include="System.Text.Encodings.Web.Utf8" Version="0.1.0-*" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' "> <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">

Просмотреть файл

@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
} }
[Theory] [Theory]
[MemberData("LargeUploadData")] [MemberData(nameof(LargeUploadData))]
public async Task LargeUpload(long? maxRequestBufferSize, bool ssl, bool expectPause) public async Task LargeUpload(long? maxRequestBufferSize, bool ssl, bool expectPause)
{ {
// Parameters // Parameters

Просмотреть файл

@ -3,7 +3,7 @@
<Import Project="..\..\build\common.props" /> <Import Project="..\..\build\common.props" />
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp1.1;net452</TargetFrameworks> <TargetFrameworks>netcoreapp1.1</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp1.1</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp1.1</TargetFrameworks>
<!-- TODO remove rid when https://github.com/dotnet/sdk/issues/396 is resolved --> <!-- TODO remove rid when https://github.com/dotnet/sdk/issues/396 is resolved -->
<RuntimeIdentifier Condition="'$(TargetFramework)' != 'netcoreapp1.1'">win7-x64</RuntimeIdentifier> <RuntimeIdentifier Condition="'$(TargetFramework)' != 'netcoreapp1.1'">win7-x64</RuntimeIdentifier>

Просмотреть файл

@ -1,6 +1,7 @@
// 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.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -87,7 +88,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var client = new HttpClient()) using (var client = new HttpClient())
{ {
var response = await client.GetAsync($"http://localhost:{host.GetPort()}{requestPath}"); var response = await client.GetAsync($"http://127.0.0.1:{host.GetPort()}{requestPath}");
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var responseText = await response.Content.ReadAsStringAsync(); var responseText = await response.Content.ReadAsStringAsync();

Просмотреть файл

@ -60,7 +60,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{ {
for (var i = 0; i < received; i++) for (var i = 0; i < received; i++)
{ {
Assert.Equal((byte)((total + i) % 256), receivedBytes[i]); // Do not use Assert.Equal here, it is to slow for this hot path
Assert.True((byte)((total + i) % 256) == receivedBytes[i], "Data received is incorrect");
} }
} }
@ -143,7 +144,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
client.DefaultRequestHeaders.Connection.Clear(); client.DefaultRequestHeaders.Connection.Clear();
client.DefaultRequestHeaders.Connection.Add("close"); client.DefaultRequestHeaders.Connection.Add("close");
var response = await client.GetAsync($"http://localhost:{host.GetPort()}/"); var response = await client.GetAsync($"http://127.0.0.1:{host.GetPort()}/");
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }
} }

Просмотреть файл

@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var client = new HttpClient()) using (var client = new HttpClient())
{ {
var response = await client.GetAsync($"http://localhost:{host.GetPort()}/"); var response = await client.GetAsync($"http://127.0.0.1:{host.GetPort()}/");
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStreamAsync(); var responseBody = await response.Content.ReadAsStreamAsync();
@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var client = new HttpClient()) using (var client = new HttpClient())
{ {
var response = await client.GetAsync($"http://localhost:{host.GetPort()}/"); var response = await client.GetAsync($"http://127.0.0.1:{host.GetPort()}/");
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var headers = response.Headers; var headers = response.Headers;
@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var client = new HttpClient()) using (var client = new HttpClient())
{ {
var response = await client.GetAsync($"http://localhost:{host.GetPort()}/"); var response = await client.GetAsync($"http://127.0.0.1:{host.GetPort()}/");
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
Assert.False(onStartingCalled); Assert.False(onStartingCalled);
@ -178,7 +178,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var client = new HttpClient()) using (var client = new HttpClient())
{ {
var response = await client.GetAsync($"http://localhost:{host.GetPort()}/"); var response = await client.GetAsync($"http://127.0.0.1:{host.GetPort()}/");
// Despite the error, the response had already started // Despite the error, the response had already started
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);

Просмотреть файл

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var requestTasks = new List<Task<string>>(); var requestTasks = new List<Task<string>>();
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
{ {
var requestTask = client.GetStringAsync($"http://localhost:{host.GetPort()}/"); var requestTask = client.GetStringAsync($"http://127.0.0.1:{host.GetPort()}/");
requestTasks.Add(requestTask); requestTasks.Add(requestTask);
} }

Просмотреть файл

@ -15,7 +15,6 @@
<Compile Include="..\shared\TestKestrelTrace.cs" /> <Compile Include="..\shared\TestKestrelTrace.cs" />
<Compile Include="..\shared\MockConnection.cs" /> <Compile Include="..\shared\MockConnection.cs" />
<Compile Include="..\shared\MockSocketOutput.cs" /> <Compile Include="..\shared\MockSocketOutput.cs" />
<Compile Include="..\shared\SocketInputExtensions.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

Просмотреть файл

@ -0,0 +1,68 @@
// 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.IO.Pipelines;
using System.Text;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
[Config(typeof(CoreConfig))]
public class PipeThroughput
{
private const int _writeLenght = 57;
private const int InnerLoopCount = 512;
private IPipe _pipe;
private PipeFactory _pipelineFactory;
[Setup]
public void Setup()
{
_pipelineFactory = new PipeFactory();
_pipe = _pipelineFactory.Create();
}
[Benchmark(OperationsPerInvoke = InnerLoopCount)]
public void ParseLiveAspNetTwoTasks()
{
var writing = Task.Run(async () =>
{
for (int i = 0; i < InnerLoopCount; i++)
{
var writableBuffer = _pipe.Writer.Alloc(_writeLenght);
writableBuffer.Advance(_writeLenght);
await writableBuffer.FlushAsync();
}
});
var reading = Task.Run(async () =>
{
int remaining = InnerLoopCount * _writeLenght;
while (remaining != 0)
{
var result = await _pipe.Reader.ReadAsync();
remaining -= result.Buffer.Length;
_pipe.Reader.Advance(result.Buffer.End, result.Buffer.End);
}
});
Task.WaitAll(writing, reading);
}
[Benchmark(OperationsPerInvoke = InnerLoopCount)]
public void ParseLiveAspNetInline()
{
for (int i = 0; i < InnerLoopCount; i++)
{
var writableBuffer = _pipe.Writer.Alloc(_writeLenght);
writableBuffer.Advance(_writeLenght);
writableBuffer.FlushAsync().GetAwaiter().GetResult();
var result = _pipe.Reader.ReadAsync().GetAwaiter().GetResult();
_pipe.Reader.Advance(result.Buffer.End, result.Buffer.End);
}
}
}
}

Просмотреть файл

@ -36,6 +36,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{ {
BenchmarkRunner.Run<Writing>(); BenchmarkRunner.Run<Writing>();
} }
if (type.HasFlag(BenchmarkType.Throughput))
{
BenchmarkRunner.Run<PipeThroughput>();
}
} }
} }
@ -44,6 +48,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{ {
RequestParsing = 1, RequestParsing = 1,
Writing = 2, Writing = 2,
Throughput = 4,
// add new ones in powers of two - e.g. 2,4,8,16... // add new ones in powers of two - e.g. 2,4,8,16...
All = uint.MaxValue All = uint.MaxValue

Просмотреть файл

@ -2,6 +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.IO.Pipelines;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
@ -9,6 +10,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using MemoryPool = Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure.MemoryPool;
using RequestLineStatus = Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.RequestLineStatus; using RequestLineStatus = Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.RequestLineStatus;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance namespace Microsoft.AspNetCore.Server.Kestrel.Performance
@ -21,14 +23,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
private const string plaintextRequest = "GET /plaintext HTTP/1.1\r\nHost: www.example.com\r\n\r\n"; private const string plaintextRequest = "GET /plaintext HTTP/1.1\r\nHost: www.example.com\r\n\r\n";
private const string liveaspnetRequest = "GET https://live.asp.net/ HTTP/1.1\r\n" + private const string liveaspnetRequest = "GET https://live.asp.net/ HTTP/1.1\r\n" +
"Host: live.asp.net\r\n" + "Host: live.asp.net\r\n" +
"Connection: keep-alive\r\n" + "Connection: keep-alive\r\n" +
"Upgrade-Insecure-Requests: 1\r\n" + "Upgrade-Insecure-Requests: 1\r\n" +
"User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36\r\n" + "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36\r\n" +
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n" +
"DNT: 1\r\n" + "DNT: 1\r\n" +
"Accept-Encoding: gzip, deflate, sdch, br\r\n" + "Accept-Encoding: gzip, deflate, sdch, br\r\n" +
"Accept-Language: en-US,en;q=0.8\r\n" + "Accept-Language: en-US,en;q=0.8\r\n" +
"Cookie: __unam=7a67379-1s65dc575c4-6d778abe-1; omniID=9519gfde_3347_4762_8762_df51458c8ec2\r\n\r\n"; "Cookie: __unam=7a67379-1s65dc575c4-6d778abe-1; omniID=9519gfde_3347_4762_8762_df51458c8ec2\r\n\r\n";
@ -48,7 +50,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
"Cookie: prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric\r\n\r\n"; "Cookie: prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric\r\n\r\n";
private static readonly byte[] _plaintextPipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(plaintextRequest, Pipelining))); private static readonly byte[] _plaintextPipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(plaintextRequest, Pipelining)));
private static readonly byte[] _plaintextRequest = Encoding.ASCII.GetBytes(plaintextRequest); private static readonly byte[] _plaintextRequest = Encoding.ASCII.GetBytes(plaintextRequest);
private static readonly byte[] _liveaspnentPipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(liveaspnetRequest, Pipelining))); private static readonly byte[] _liveaspnentPipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(liveaspnetRequest, Pipelining)));
private static readonly byte[] _liveaspnentRequest = Encoding.ASCII.GetBytes(liveaspnetRequest); private static readonly byte[] _liveaspnentRequest = Encoding.ASCII.GetBytes(liveaspnetRequest);
@ -56,19 +58,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
private static readonly byte[] _unicodePipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(unicodeRequest, Pipelining))); private static readonly byte[] _unicodePipelinedRequests = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat(unicodeRequest, Pipelining)));
private static readonly byte[] _unicodeRequest = Encoding.ASCII.GetBytes(unicodeRequest); private static readonly byte[] _unicodeRequest = Encoding.ASCII.GetBytes(unicodeRequest);
private KestrelTrace Trace;
private LoggingThreadPool ThreadPool;
private MemoryPool MemoryPool;
private SocketInput SocketInput;
private Frame<object> Frame;
[Benchmark(Baseline = true, OperationsPerInvoke = InnerLoopCount)] [Benchmark(Baseline = true, OperationsPerInvoke = InnerLoopCount)]
public void ParsePlaintext() public void ParsePlaintext()
{ {
for (var i = 0; i < InnerLoopCount; i++) for (var i = 0; i < InnerLoopCount; i++)
{ {
InsertData(_plaintextRequest); InsertData(_plaintextRequest);
ParseData(); ParseData();
} }
} }
@ -79,7 +74,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
for (var i = 0; i < InnerLoopCount; i++) for (var i = 0; i < InnerLoopCount; i++)
{ {
InsertData(_plaintextPipelinedRequests); InsertData(_plaintextPipelinedRequests);
ParseData(); ParseData();
} }
} }
@ -90,7 +84,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
for (var i = 0; i < InnerLoopCount; i++) for (var i = 0; i < InnerLoopCount; i++)
{ {
InsertData(_liveaspnentRequest); InsertData(_liveaspnentRequest);
ParseData(); ParseData();
} }
} }
@ -101,7 +94,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
for (var i = 0; i < InnerLoopCount; i++) for (var i = 0; i < InnerLoopCount; i++)
{ {
InsertData(_liveaspnentPipelinedRequests); InsertData(_liveaspnentPipelinedRequests);
ParseData(); ParseData();
} }
} }
@ -112,7 +104,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
for (var i = 0; i < InnerLoopCount; i++) for (var i = 0; i < InnerLoopCount; i++)
{ {
InsertData(_unicodeRequest); InsertData(_unicodeRequest);
ParseData(); ParseData();
} }
} }
@ -123,34 +114,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
for (var i = 0; i < InnerLoopCount; i++) for (var i = 0; i < InnerLoopCount; i++)
{ {
InsertData(_unicodePipelinedRequests); InsertData(_unicodePipelinedRequests);
ParseData(); ParseData();
} }
} }
private void InsertData(byte[] dataBytes) private void InsertData(byte[] bytes)
{ {
SocketInput.IncomingData(dataBytes, 0, dataBytes.Length); // There should not be any backpressure and task completes immediately
Pipe.Writer.WriteAsync(bytes).GetAwaiter().GetResult();
} }
private void ParseData() private void ParseData()
{ {
while (SocketInput.GetAwaiter().IsCompleted) do
{ {
var awaitable = Pipe.Reader.ReadAsync();
if (!awaitable.IsCompleted)
{
// No more data
return;
}
var result = awaitable.GetAwaiter().GetResult();
var readableBuffer = result.Buffer;
Frame.Reset(); Frame.Reset();
if (Frame.TakeStartLine(SocketInput) != RequestLineStatus.Done) ReadCursor consumed;
ReadCursor examined;
if (!Frame.TakeStartLine(readableBuffer, out consumed, out examined))
{ {
ThrowInvalidStartLine(); ThrowInvalidStartLine();
} }
Pipe.Reader.Advance(consumed, examined);
result = Pipe.Reader.ReadAsync().GetAwaiter().GetResult();
readableBuffer = result.Buffer;
Frame.InitializeHeaders(); Frame.InitializeHeaders();
if (!Frame.TakeMessageHeaders(SocketInput, (FrameRequestHeaders) Frame.RequestHeaders)) if (!Frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)Frame.RequestHeaders, out consumed, out examined))
{ {
ThrowInvalidMessageHeaders(); ThrowInvalidMessageHeaders();
} }
Pipe.Reader.Advance(consumed, examined);
} }
while(true);
} }
private void ThrowInvalidStartLine() private void ThrowInvalidStartLine()
@ -166,23 +175,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
[Setup] [Setup]
public void Setup() public void Setup()
{ {
Trace = new KestrelTrace(new TestKestrelTrace());
ThreadPool = new LoggingThreadPool(Trace);
MemoryPool = new MemoryPool();
SocketInput = new SocketInput(MemoryPool, ThreadPool);
var connectionContext = new MockConnection(new KestrelServerOptions()); var connectionContext = new MockConnection(new KestrelServerOptions());
connectionContext.Input = SocketInput;
Frame = new Frame<object>(application: null, context: connectionContext); Frame = new Frame<object>(application: null, context: connectionContext);
PipelineFactory = new PipeFactory();
Pipe = PipelineFactory.Create();
} }
[Cleanup] public IPipe Pipe { get; set; }
public void Cleanup()
{ public Frame<object> Frame { get; set; }
SocketInput.IncomingFin();
SocketInput.Dispose(); public PipeFactory PipelineFactory { get; set; }
MemoryPool.Dispose();
}
} }
} }

Просмотреть файл

@ -2,6 +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.IO.Pipelines;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -88,9 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
private TestFrame<object> MakeFrame() private TestFrame<object> MakeFrame()
{ {
var ltp = new LoggingThreadPool(Mock.Of<IKestrelTrace>()); var socketInput = new PipeFactory().Create();
var pool = new MemoryPool();
var socketInput = new SocketInput(pool, ltp);
var serviceContext = new ServiceContext var serviceContext = new ServiceContext
{ {

Просмотреть файл

@ -49,11 +49,15 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Libuv.uv_buf_t ignored; Libuv.uv_buf_t ignored;
mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out ignored); mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out ignored);
mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored); mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored);
Assert.False(connection.Input.CheckFinOrThrow());
}, null); var readAwaitable = connection.Input.Reader.ReadAsync();
var result = readAwaitable.GetResult();
Assert.False(result.IsCompleted);
}, (object)null);
connection.ConnectionControl.End(ProduceEndType.SocketDisconnect); connection.ConnectionControl.End(ProduceEndType.SocketDisconnect);
} }
} }
} }
} }

Просмотреть файл

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Pipelines;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -13,7 +14,6 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel;
using Microsoft.AspNetCore.Server.Kestrel.Internal; using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Moq; using Moq;
@ -23,11 +23,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
public class FrameTests : IDisposable public class FrameTests : IDisposable
{ {
private readonly SocketInput _socketInput; private readonly IPipe _socketInput;
private readonly MemoryPool _pool;
private readonly TestFrame<object> _frame; private readonly TestFrame<object> _frame;
private readonly ServiceContext _serviceContext; private readonly ServiceContext _serviceContext;
private readonly ConnectionContext _connectionContext; private readonly ConnectionContext _connectionContext;
private PipeFactory _pipelineFactory;
ReadCursor consumed;
ReadCursor examined;
private class TestFrame<TContext> : Frame<TContext> private class TestFrame<TContext> : Frame<TContext>
{ {
@ -45,9 +48,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
public FrameTests() public FrameTests()
{ {
var trace = new KestrelTrace(new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace); _pipelineFactory = new PipeFactory();
_pool = new MemoryPool(); _socketInput = _pipelineFactory.Create();
_socketInput = new SocketInput(_pool, ltp);
_serviceContext = new ServiceContext _serviceContext = new ServiceContext
{ {
@ -73,27 +75,26 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
public void Dispose() public void Dispose()
{ {
_pool.Dispose(); _socketInput.Reader.Complete();
_socketInput.Dispose(); _socketInput.Writer.Complete();
_pipelineFactory.Dispose();
} }
[Fact] [Fact]
public void CanReadHeaderValueWithoutLeadingWhitespace() public async Task CanReadHeaderValueWithoutLeadingWhitespace()
{ {
_frame.InitializeHeaders(); _frame.InitializeHeaders();
var headerArray = Encoding.ASCII.GetBytes("Header:value\r\n\r\n"); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header:value\r\n\r\n"));
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
var success = _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders) _frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
Assert.True(success); Assert.True(success);
Assert.Equal(1, _frame.RequestHeaders.Count); Assert.Equal(1, _frame.RequestHeaders.Count);
Assert.Equal("value", _frame.RequestHeaders["Header"]); Assert.Equal("value", _frame.RequestHeaders["Header"]);
Assert.Equal(readableBuffer.End, consumed);
// Assert TakeMessageHeaders consumed all the input
var scan = _socketInput.ConsumingStart();
Assert.True(scan.IsEnd);
} }
[Theory] [Theory]
@ -107,20 +108,18 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header: \t\tvalue\r\n\r\n")] [InlineData("Header: \t\tvalue\r\n\r\n")]
[InlineData("Header: \t\t value\r\n\r\n")] [InlineData("Header: \t\t value\r\n\r\n")]
[InlineData("Header: \t \t value\r\n\r\n")] [InlineData("Header: \t \t value\r\n\r\n")]
public void LeadingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders) public async Task LeadingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders)
{ {
var headerArray = Encoding.ASCII.GetBytes(rawHeaders); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
var success = _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
Assert.True(success); Assert.True(success);
Assert.Equal(1, _frame.RequestHeaders.Count); Assert.Equal(1, _frame.RequestHeaders.Count);
Assert.Equal("value", _frame.RequestHeaders["Header"]); Assert.Equal("value", _frame.RequestHeaders["Header"]);
Assert.Equal(readableBuffer.End, consumed);
// Assert TakeMessageHeaders consumed all the input
var scan = _socketInput.ConsumingStart();
Assert.True(scan.IsEnd);
} }
[Theory] [Theory]
@ -133,20 +132,18 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header: value \t\t\r\n\r\n")] [InlineData("Header: value \t\t\r\n\r\n")]
[InlineData("Header: value \t\t \r\n\r\n")] [InlineData("Header: value \t\t \r\n\r\n")]
[InlineData("Header: value \t \t \r\n\r\n")] [InlineData("Header: value \t \t \r\n\r\n")]
public void TrailingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders) public async Task TrailingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders)
{ {
var headerArray = Encoding.ASCII.GetBytes(rawHeaders); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
var success = _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
Assert.True(success); Assert.True(success);
Assert.Equal(1, _frame.RequestHeaders.Count); Assert.Equal(1, _frame.RequestHeaders.Count);
Assert.Equal("value", _frame.RequestHeaders["Header"]); Assert.Equal("value", _frame.RequestHeaders["Header"]);
Assert.Equal(readableBuffer.End, consumed);
// Assert TakeMessageHeaders consumed all the input
var scan = _socketInput.ConsumingStart();
Assert.True(scan.IsEnd);
} }
[Theory] [Theory]
@ -158,20 +155,18 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header: one \ttwo \tthree\r\n\r\n", "one \ttwo \tthree")] [InlineData("Header: one \ttwo \tthree\r\n\r\n", "one \ttwo \tthree")]
[InlineData("Header: one\t two\t three\r\n\r\n", "one\t two\t three")] [InlineData("Header: one\t two\t three\r\n\r\n", "one\t two\t three")]
[InlineData("Header: one \ttwo\t three\r\n\r\n", "one \ttwo\t three")] [InlineData("Header: one \ttwo\t three\r\n\r\n", "one \ttwo\t three")]
public void WhitespaceWithinHeaderValueIsPreserved(string rawHeaders, string expectedValue) public async Task WhitespaceWithinHeaderValueIsPreserved(string rawHeaders, string expectedValue)
{ {
var headerArray = Encoding.ASCII.GetBytes(rawHeaders); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
var success = _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
Assert.True(success); Assert.True(success);
Assert.Equal(1, _frame.RequestHeaders.Count); Assert.Equal(1, _frame.RequestHeaders.Count);
Assert.Equal(expectedValue, _frame.RequestHeaders["Header"]); Assert.Equal(expectedValue, _frame.RequestHeaders["Header"]);
Assert.Equal(readableBuffer.End, consumed);
// Assert TakeMessageHeaders consumed all the input
var scan = _socketInput.ConsumingStart();
Assert.True(scan.IsEnd);
} }
[Theory] [Theory]
@ -183,27 +178,32 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header: line1\r\n\t\tline2\r\n\r\n")] [InlineData("Header: line1\r\n\t\tline2\r\n\r\n")]
[InlineData("Header: line1\r\n \t\t line2\r\n\r\n")] [InlineData("Header: line1\r\n \t\t line2\r\n\r\n")]
[InlineData("Header: line1\r\n \t \t line2\r\n\r\n")] [InlineData("Header: line1\r\n \t \t line2\r\n\r\n")]
public void TakeMessageHeadersThrowsOnHeaderValueWithLineFolding(string rawHeaders) public async Task TakeMessageHeadersThrowsOnHeaderValueWithLineFolding(string rawHeaders)
{ {
var headerArray = Encoding.ASCII.GetBytes(rawHeaders); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
_socketInput.IncomingData(headerArray, 0, headerArray.Length); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders)); var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
Assert.Equal("Header value line folding not supported.", exception.Message); Assert.Equal("Header value line folding not supported.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode); Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
} }
[Fact] [Fact]
public void TakeMessageHeadersThrowsOnHeaderValueWithLineFolding_CharacterNotAvailableOnFirstAttempt() public async Task TakeMessageHeadersThrowsOnHeaderValueWithLineFolding_CharacterNotAvailableOnFirstAttempt()
{ {
var headerArray = Encoding.ASCII.GetBytes("Header-1: value1\r\n"); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header-1: value1\r\n"));
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
Assert.False(_frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders)); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
Assert.False(_frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
_socketInput.IncomingData(Encoding.ASCII.GetBytes(" "), 0, 1); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(" "));
readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders));
Assert.Equal("Header value line folding not supported.", exception.Message); Assert.Equal("Header value line folding not supported.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode); Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
} }
@ -214,13 +214,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header-1: value1\rHeader-2: value2\r\n\r\n")] [InlineData("Header-1: value1\rHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2: value2\r\r\n")] [InlineData("Header-1: value1\r\nHeader-2: value2\r\r\n")]
[InlineData("Header-1: value1\r\nHeader-2: v\ralue2\r\n")] [InlineData("Header-1: value1\r\nHeader-2: v\ralue2\r\n")]
public void TakeMessageHeadersThrowsOnHeaderValueContainingCR(string rawHeaders) public async Task TakeMessageHeadersThrowsOnHeaderValueContainingCR(string rawHeaders)
{ {
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var headerArray = Encoding.ASCII.GetBytes(rawHeaders); var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.IncomingData(headerArray, 0, headerArray.Length); _socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders));
Assert.Equal("Header value must not contain CR characters.", exception.Message); Assert.Equal("Header value must not contain CR characters.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode); Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
} }
@ -229,12 +230,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header-1 value1\r\n\r\n")] [InlineData("Header-1 value1\r\n\r\n")]
[InlineData("Header-1 value1\r\nHeader-2: value2\r\n\r\n")] [InlineData("Header-1 value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2 value2\r\n\r\n")] [InlineData("Header-1: value1\r\nHeader-2 value2\r\n\r\n")]
public void TakeMessageHeadersThrowsOnHeaderLineMissingColon(string rawHeaders) public async Task TakeMessageHeadersThrowsOnHeaderLineMissingColon(string rawHeaders)
{ {
var headerArray = Encoding.ASCII.GetBytes(rawHeaders); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
_socketInput.IncomingData(headerArray, 0, headerArray.Length); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders));
Assert.Equal("No ':' character found in header line.", exception.Message); Assert.Equal("No ':' character found in header line.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode); Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
} }
@ -244,12 +247,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("\tHeader: value\r\n\r\n")] [InlineData("\tHeader: value\r\n\r\n")]
[InlineData(" Header-1: value1\r\nHeader-2: value2\r\n\r\n")] [InlineData(" Header-1: value1\r\nHeader-2: value2\r\n\r\n")]
[InlineData("\tHeader-1: value1\r\nHeader-2: value2\r\n\r\n")] [InlineData("\tHeader-1: value1\r\nHeader-2: value2\r\n\r\n")]
public void TakeMessageHeadersThrowsOnHeaderLineStartingWithWhitespace(string rawHeaders) public async Task TakeMessageHeadersThrowsOnHeaderLineStartingWithWhitespace(string rawHeaders)
{ {
var headerArray = Encoding.ASCII.GetBytes(rawHeaders); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
_socketInput.IncomingData(headerArray, 0, headerArray.Length); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders));
Assert.Equal("Header line must not start with whitespace.", exception.Message); Assert.Equal("Header line must not start with whitespace.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode); Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
} }
@ -263,12 +268,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header-1: value1\r\nHeader 2: value2\r\n\r\n")] [InlineData("Header-1: value1\r\nHeader 2: value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2 : value2\r\n\r\n")] [InlineData("Header-1: value1\r\nHeader-2 : value2\r\n\r\n")]
[InlineData("Header-1: value1\r\nHeader-2\t: value2\r\n\r\n")] [InlineData("Header-1: value1\r\nHeader-2\t: value2\r\n\r\n")]
public void TakeMessageHeadersThrowsOnWhitespaceInHeaderName(string rawHeaders) public async Task TakeMessageHeadersThrowsOnWhitespaceInHeaderName(string rawHeaders)
{ {
var headerArray = Encoding.ASCII.GetBytes(rawHeaders); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
_socketInput.IncomingData(headerArray, 0, headerArray.Length); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders));
Assert.Equal("Whitespace is not allowed in header name.", exception.Message); Assert.Equal("Whitespace is not allowed in header name.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode); Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
} }
@ -277,41 +284,47 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r\r")] [InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r\r")]
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r ")] [InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r ")]
[InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r \n")] [InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r \n")]
public void TakeMessageHeadersThrowsOnHeadersNotEndingInCRLFLine(string rawHeaders) public async Task TakeMessageHeadersThrowsOnHeadersNotEndingInCRLFLine(string rawHeaders)
{ {
var headerArray = Encoding.ASCII.GetBytes(rawHeaders); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
_socketInput.IncomingData(headerArray, 0, headerArray.Length); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders));
Assert.Equal("Headers corrupted, invalid header sequence.", exception.Message); Assert.Equal("Headers corrupted, invalid header sequence.", exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode); Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
} }
[Fact] [Fact]
public void TakeMessageHeadersThrowsWhenHeadersExceedTotalSizeLimit() public async Task TakeMessageHeadersThrowsWhenHeadersExceedTotalSizeLimit()
{ {
const string headerLine = "Header: value\r\n"; const string headerLine = "Header: value\r\n";
_serviceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize = headerLine.Length - 1; _serviceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize = headerLine.Length - 1;
_frame.Reset(); _frame.Reset();
var headerArray = Encoding.ASCII.GetBytes($"{headerLine}\r\n"); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
_socketInput.IncomingData(headerArray, 0, headerArray.Length); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders));
Assert.Equal("Request headers too long.", exception.Message); Assert.Equal("Request headers too long.", exception.Message);
Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode); Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
} }
[Fact] [Fact]
public void TakeMessageHeadersThrowsWhenHeadersExceedCountLimit() public async Task TakeMessageHeadersThrowsWhenHeadersExceedCountLimit()
{ {
const string headerLines = "Header-1: value1\r\nHeader-2: value2\r\n"; const string headerLines = "Header-1: value1\r\nHeader-2: value2\r\n";
_serviceContext.ServerOptions.Limits.MaxRequestHeaderCount = 1; _serviceContext.ServerOptions.Limits.MaxRequestHeaderCount = 1;
var headerArray = Encoding.ASCII.GetBytes($"{headerLines}\r\n"); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
_socketInput.IncomingData(headerArray, 0, headerArray.Length); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders));
Assert.Equal("Request contains too many headers.", exception.Message); Assert.Equal("Request contains too many headers.", exception.Message);
Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode); Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
} }
@ -323,19 +336,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Cookie:\r\nConnection: close\r\n\r\n", 2)] [InlineData("Cookie:\r\nConnection: close\r\n\r\n", 2)]
[InlineData("Connection: close\r\nCookie: \r\n\r\n", 2)] [InlineData("Connection: close\r\nCookie: \r\n\r\n", 2)]
[InlineData("Connection: close\r\nCookie:\r\n\r\n", 2)] [InlineData("Connection: close\r\nCookie:\r\n\r\n", 2)]
public void EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders) public async Task EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders)
{ {
var headerArray = Encoding.ASCII.GetBytes(rawHeaders); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
_socketInput.IncomingData(headerArray, 0, headerArray.Length); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var success = _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders); var success = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
Assert.True(success); Assert.True(success);
Assert.Equal(numHeaders, _frame.RequestHeaders.Count); Assert.Equal(numHeaders, _frame.RequestHeaders.Count);
Assert.Equal(readableBuffer.End, consumed);
// Assert TakeMessageHeaders consumed all the input
var scan = _socketInput.ConsumingStart();
Assert.True(scan.IsEnd);
} }
[Fact] [Fact]
@ -351,7 +362,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
} }
[Fact] [Fact]
public void ResetResetsHeaderLimits() public async Task ResetResetsHeaderLimits()
{ {
const string headerLine1 = "Header-1: value1\r\n"; const string headerLine1 = "Header-1: value1\r\n";
const string headerLine2 = "Header-2: value2\r\n"; const string headerLine2 = "Header-2: value2\r\n";
@ -361,19 +372,25 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
options.Limits.MaxRequestHeaderCount = 1; options.Limits.MaxRequestHeaderCount = 1;
_serviceContext.ServerOptions = options; _serviceContext.ServerOptions = options;
var headerArray1 = Encoding.ASCII.GetBytes($"{headerLine1}\r\n"); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
_socketInput.IncomingData(headerArray1, 0, headerArray1.Length); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
Assert.True(_frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders)); var takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
Assert.True(takeMessageHeaders);
Assert.Equal(1, _frame.RequestHeaders.Count); Assert.Equal(1, _frame.RequestHeaders.Count);
Assert.Equal("value1", _frame.RequestHeaders["Header-1"]); Assert.Equal("value1", _frame.RequestHeaders["Header-1"]);
_frame.Reset(); _frame.Reset();
var headerArray2 = Encoding.ASCII.GetBytes($"{headerLine2}\r\n"); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
_socketInput.IncomingData(headerArray2, 0, headerArray1.Length); readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
Assert.True(_frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders)); takeMessageHeaders = _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
Assert.True(takeMessageHeaders);
Assert.Equal(1, _frame.RequestHeaders.Count); Assert.Equal(1, _frame.RequestHeaders.Count);
Assert.Equal("value2", _frame.RequestHeaders["Header-2"]); Assert.Equal("value2", _frame.RequestHeaders["Header-2"]);
} }
@ -462,78 +479,84 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
} }
[Fact] [Fact]
public void TakeStartLineCallsConsumingCompleteWithFurthestExamined() public async Task TakeStartLineCallsConsumingCompleteWithFurthestExamined()
{ {
var requestLineBytes = Encoding.ASCII.GetBytes("GET / "); var requestLineBytes = Encoding.ASCII.GetBytes("GET / ");
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length); await _socketInput.Writer.WriteAsync(requestLineBytes);
_frame.TakeStartLine(_socketInput); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
Assert.False(_socketInput.IsCompleted);
_frame.TakeStartLine(readableBuffer, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
Assert.Equal(readableBuffer.Start, consumed);
Assert.Equal(readableBuffer.End, examined);
requestLineBytes = Encoding.ASCII.GetBytes("HTTP/1.1\r\n"); requestLineBytes = Encoding.ASCII.GetBytes("HTTP/1.1\r\n");
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length); await _socketInput.Writer.WriteAsync(requestLineBytes);
_frame.TakeStartLine(_socketInput); readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
Assert.False(_socketInput.IsCompleted);
_frame.TakeStartLine(readableBuffer, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
Assert.Equal(readableBuffer.End, consumed);
Assert.Equal(readableBuffer.End, examined);
} }
[Theory] [Theory]
[InlineData("", Frame.RequestLineStatus.Empty)] [InlineData("G")]
[InlineData("G", Frame.RequestLineStatus.Incomplete)] [InlineData("GE")]
[InlineData("GE", Frame.RequestLineStatus.Incomplete)] [InlineData("GET")]
[InlineData("GET", Frame.RequestLineStatus.Incomplete)] [InlineData("GET ")]
[InlineData("GET ", Frame.RequestLineStatus.Incomplete)] [InlineData("GET /")]
[InlineData("GET /", Frame.RequestLineStatus.Incomplete)] [InlineData("GET / ")]
[InlineData("GET / ", Frame.RequestLineStatus.Incomplete)] [InlineData("GET / H")]
[InlineData("GET / H", Frame.RequestLineStatus.Incomplete)] [InlineData("GET / HT")]
[InlineData("GET / HT", Frame.RequestLineStatus.Incomplete)] [InlineData("GET / HTT")]
[InlineData("GET / HTT", Frame.RequestLineStatus.Incomplete)] [InlineData("GET / HTTP")]
[InlineData("GET / HTTP", Frame.RequestLineStatus.Incomplete)] [InlineData("GET / HTTP/")]
[InlineData("GET / HTTP/", Frame.RequestLineStatus.Incomplete)] [InlineData("GET / HTTP/1")]
[InlineData("GET / HTTP/1", Frame.RequestLineStatus.Incomplete)] [InlineData("GET / HTTP/1.")]
[InlineData("GET / HTTP/1.", Frame.RequestLineStatus.Incomplete)] [InlineData("GET / HTTP/1.1")]
[InlineData("GET / HTTP/1.1", Frame.RequestLineStatus.Incomplete)] [InlineData("GET / HTTP/1.1\r")]
[InlineData("GET / HTTP/1.1\r", Frame.RequestLineStatus.Incomplete)] public async Task TakeStartLineReturnsWhenGivenIncompleteRequestLines(string requestLine)
public void TakeStartLineReturnsWhenGivenIncompleteRequestLines(string requestLine, Frame.RequestLineStatus expectedReturnValue)
{ {
var requestLineBytes = Encoding.ASCII.GetBytes(requestLine); var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length); await _socketInput.Writer.WriteAsync(requestLineBytes);
var returnValue = _frame.TakeStartLine(_socketInput); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
Assert.Equal(expectedReturnValue, returnValue); var returnValue = _frame.TakeStartLine(readableBuffer, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
Assert.False(returnValue);
} }
[Fact] [Fact]
public void TakeStartLineStartsRequestHeadersTimeoutOnFirstByteAvailable() public async Task TakeStartLineStartsRequestHeadersTimeoutOnFirstByteAvailable()
{ {
var connectionControl = new Mock<IConnectionControl>(); var connectionControl = new Mock<IConnectionControl>();
_connectionContext.ConnectionControl = connectionControl.Object; _connectionContext.ConnectionControl = connectionControl.Object;
var requestLineBytes = Encoding.ASCII.GetBytes("G"); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("G"));
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
_frame.TakeStartLine((await _socketInput.Reader.ReadAsync()).Buffer, out consumed, out examined);
_socketInput.Reader.Advance(consumed, examined);
_frame.TakeStartLine(_socketInput);
var expectedRequestHeadersTimeout = (long)_serviceContext.ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds; var expectedRequestHeadersTimeout = (long)_serviceContext.ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds;
connectionControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutAction.SendTimeoutResponse)); connectionControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutAction.SendTimeoutResponse));
} }
[Fact] [Fact]
public void TakeStartLineDoesNotStartRequestHeadersTimeoutIfNoDataAvailable() public async Task TakeStartLineThrowsWhenTooLong()
{
var connectionControl = new Mock<IConnectionControl>();
_connectionContext.ConnectionControl = connectionControl.Object;
_frame.TakeStartLine(_socketInput);
connectionControl.Verify(cc => cc.ResetTimeout(It.IsAny<long>(), It.IsAny<TimeoutAction>()), Times.Never);
}
[Fact]
public void TakeStartLineThrowsWhenTooLong()
{ {
_serviceContext.ServerOptions.Limits.MaxRequestLineSize = "GET / HTTP/1.1\r\n".Length; _serviceContext.ServerOptions.Limits.MaxRequestLineSize = "GET / HTTP/1.1\r\n".Length;
var requestLineBytes = Encoding.ASCII.GetBytes("GET /a HTTP/1.1\r\n"); var requestLineBytes = Encoding.ASCII.GetBytes("GET /a HTTP/1.1\r\n");
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length); await _socketInput.Writer.WriteAsync(requestLineBytes);
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(_socketInput));
Assert.Equal("Request line too long.", exception.Message); Assert.Equal("Request line too long.", exception.Message);
Assert.Equal(StatusCodes.Status414UriTooLong, exception.StatusCode); Assert.Equal(StatusCodes.Status414UriTooLong, exception.StatusCode);
} }
@ -550,55 +573,60 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("GET / HTTP/1.1\n", "Invalid request line: GET / HTTP/1.1<0x0A>")] [InlineData("GET / HTTP/1.1\n", "Invalid request line: GET / HTTP/1.1<0x0A>")]
[InlineData("GET / \r\n", "Invalid request line: GET / <0x0D><0x0A>")] [InlineData("GET / \r\n", "Invalid request line: GET / <0x0D><0x0A>")]
[InlineData("GET / HTTP/1.1\ra\n", "Invalid request line: GET / HTTP/1.1<0x0D>a<0x0A>")] [InlineData("GET / HTTP/1.1\ra\n", "Invalid request line: GET / HTTP/1.1<0x0D>a<0x0A>")]
public void TakeStartLineThrowsWhenInvalid(string requestLine, string expectedExceptionMessage) public async Task TakeStartLineThrowsWhenInvalid(string requestLine, string expectedExceptionMessage)
{ {
var requestLineBytes = Encoding.ASCII.GetBytes(requestLine); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(_socketInput));
Assert.Equal(expectedExceptionMessage, exception.Message); Assert.Equal(expectedExceptionMessage, exception.Message);
Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode); Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
} }
[Fact] [Fact]
public void TakeStartLineThrowsOnUnsupportedHttpVersion() public async Task TakeStartLineThrowsOnUnsupportedHttpVersion()
{ {
var requestLineBytes = Encoding.ASCII.GetBytes("GET / HTTP/1.2\r\n"); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.2\r\n"));
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(_socketInput));
Assert.Equal("Unrecognized HTTP version: HTTP/1.2", exception.Message); Assert.Equal("Unrecognized HTTP version: HTTP/1.2", exception.Message);
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode); Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
} }
[Fact] [Fact]
public void TakeStartLineThrowsOnUnsupportedHttpVersionLongerThanEightCharacters() public async Task TakeStartLineThrowsOnUnsupportedHttpVersionLongerThanEightCharacters()
{ {
var requestLineBytes = Encoding.ASCII.GetBytes("GET / HTTP/1.1ab\r\n"); var requestLineBytes = Encoding.ASCII.GetBytes("GET / HTTP/1.1ab\r\n");
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length); await _socketInput.Writer.WriteAsync(requestLineBytes);
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(readableBuffer, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
var exception = Assert.Throws<BadHttpRequestException>(() => _frame.TakeStartLine(_socketInput));
Assert.Equal("Unrecognized HTTP version: HTTP/1.1a...", exception.Message); Assert.Equal("Unrecognized HTTP version: HTTP/1.1a...", exception.Message);
Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode); Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
} }
[Fact] [Fact]
public void TakeMessageHeadersCallsConsumingCompleteWithFurthestExamined() public async Task TakeMessageHeadersCallsConsumingCompleteWithFurthestExamined()
{ {
var headersBytes = Encoding.ASCII.GetBytes("Header: "); foreach (var rawHeader in new [] { "Header: " , "value\r\n" , "\r\n"})
_socketInput.IncomingData(headersBytes, 0, headersBytes.Length); {
_frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders); await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeader));
Assert.False(_socketInput.IsCompleted);
headersBytes = Encoding.ASCII.GetBytes("value\r\n"); var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
_socketInput.IncomingData(headersBytes, 0, headersBytes.Length); _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
_frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders); _socketInput.Reader.Advance(consumed, examined);
Assert.False(_socketInput.IsCompleted); Assert.Equal(readableBuffer.End, examined);
}
headersBytes = Encoding.ASCII.GetBytes("\r\n");
_socketInput.IncomingData(headersBytes, 0, headersBytes.Length);
_frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders);
Assert.False(_socketInput.IsCompleted);
} }
[Theory] [Theory]
@ -619,12 +647,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("Header: value\r")] [InlineData("Header: value\r")]
[InlineData("Header: value\r\n")] [InlineData("Header: value\r\n")]
[InlineData("Header: value\r\n\r")] [InlineData("Header: value\r\n\r")]
public void TakeMessageHeadersReturnsWhenGivenIncompleteHeaders(string headers) public async Task TakeMessageHeadersReturnsWhenGivenIncompleteHeaders(string headers)
{ {
var headerBytes = Encoding.ASCII.GetBytes(headers); var headerBytes = Encoding.ASCII.GetBytes(headers);
_socketInput.IncomingData(headerBytes, 0, headerBytes.Length); await _socketInput.Writer.WriteAsync(headerBytes);
Assert.Equal(false, _frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders)); ReadCursor consumed;
ReadCursor examined;
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
Assert.Equal(false, _frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined));
_socketInput.Reader.Advance(consumed, examined);
} }
[Fact] [Fact]
@ -639,7 +672,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection)); connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection));
_frame.StopAsync(); _frame.StopAsync();
_socketInput.IncomingFin(); _socketInput.Writer.Complete();
requestProcessingTask.Wait(); requestProcessingTask.Wait();
} }
@ -721,13 +754,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
_frame.Start(); _frame.Start();
var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n"); var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n");
_socketInput.IncomingData(data, 0, data.Length); await _socketInput.Writer.WriteAsync(data);
var requestProcessingTask = _frame.StopAsync(); var requestProcessingTask = _frame.StopAsync();
Assert.IsNotType(typeof(Task<Task>), requestProcessingTask); Assert.IsNotType(typeof(Task<Task>), requestProcessingTask);
await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10)); await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10));
_socketInput.IncomingFin(); _socketInput.Writer.Complete();
} }
[Fact] [Fact]

Просмотреть файл

@ -182,7 +182,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
} }
}, },
null); null);
}, null); }, (object)null);
await connectTcs.Task; await connectTcs.Task;
@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address)); Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address)); Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));
await kestrelThreadPrimary.PostAsync(_ => pipe.Dispose(), null); await kestrelThreadPrimary.PostAsync(_ => pipe.Dispose(), (object)null);
// Wait up to 10 seconds for error to be logged // Wait up to 10 seconds for error to be logged
for (var i = 0; i < 10 && primaryTrace.Logger.TotalErrorsLogged == 0; i++) for (var i = 0; i < 10 && primaryTrace.Logger.TotalErrorsLogged == 0; i++)

Просмотреть файл

@ -3,11 +3,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO.Pipelines;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Text; using System.Text;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Xunit; using Xunit;
using MemoryPool = Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure.MemoryPool;
using MemoryPoolBlock = Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure.MemoryPoolBlock;
namespace Microsoft.AspNetCore.Server.KestrelTests namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
@ -68,7 +71,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
public void MemorySeek(string raw, string search, char expectResult, int expectIndex) public void MemorySeek(string raw, string search, char expectResult, int expectIndex)
{ {
var block = _pool.Lease(); var block = _pool.Lease();
var chars = raw.ToCharArray().Select(c => (byte) c).ToArray(); var chars = raw.ToCharArray().Select(c => (byte)c).ToArray();
Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length); Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
block.End += chars.Length; block.End += chars.Length;
@ -150,7 +153,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
head = blocks[0].GetIterator(); head = blocks[0].GetIterator();
for (var i = 0; i < 64; ++i) for (var i = 0; i < 64; ++i)
{ {
Assert.True(head.Put((byte) i), $"Fail to put data at {i}."); Assert.True(head.Put((byte)i), $"Fail to put data at {i}.");
} }
// Can't put anything by the end // Can't put anything by the end
@ -167,7 +170,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
// Arrange // Arrange
var block = _pool.Lease(); var block = _pool.Lease();
var bytes = new byte[] {0, 1, 2, 3, 4, 5, 6, 7}; var bytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
Buffer.BlockCopy(bytes, 0, block.Array, block.Start, bytes.Length); Buffer.BlockCopy(bytes, 0, block.Array, block.Start, bytes.Length);
block.End += bytes.Length; block.End += bytes.Length;
var scan = block.GetIterator(); var scan = block.GetIterator();
@ -177,7 +180,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var result = scan.PeekArraySegment(); var result = scan.PeekArraySegment();
// Assert // Assert
Assert.Equal(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, result); Assert.Equal(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }, result);
Assert.Equal(originalIndex, scan.Index); Assert.Equal(originalIndex, scan.Index);
_pool.Return(block); _pool.Return(block);
@ -195,7 +198,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
// Arrange // Arrange
var block = _pool.Lease(); var block = _pool.Lease();
var bytes = new byte[] {0, 1, 2, 3, 4, 5, 6, 7}; var bytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
Buffer.BlockCopy(bytes, 0, block.Array, block.Start, bytes.Length); Buffer.BlockCopy(bytes, 0, block.Array, block.Start, bytes.Length);
block.End += bytes.Length; block.End += bytes.Length;
block.Start = block.End; block.Start = block.End;
@ -559,50 +562,63 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
public void GetsKnownMethod(string input, bool expectedResult, string expectedKnownString) public void GetsKnownMethod(string input, bool expectedResult, string expectedKnownString)
{ {
// Arrange // Arrange
var block = _pool.Lease(); var block = ReadableBuffer.Create(Encoding.ASCII.GetBytes(input));
var chars = input.ToCharArray().Select(c => (byte)c).ToArray();
Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
block.End += chars.Length;
var begin = block.GetIterator();
string knownString;
// Act // Act
var result = begin.GetKnownMethod(out knownString); string knownString;
var result = block.GetKnownMethod(out knownString);
// Assert // Assert
Assert.Equal(expectedResult, result); Assert.Equal(expectedResult, result);
Assert.Equal(expectedKnownString, knownString); Assert.Equal(expectedKnownString, knownString);
}
[Theory]
[InlineData("CONNECT / HTTP/1.1", true, "CONNECT")]
[InlineData("DELETE / HTTP/1.1", true, "DELETE")]
[InlineData("GET / HTTP/1.1", true, "GET")]
[InlineData("HEAD / HTTP/1.1", true, "HEAD")]
[InlineData("PATCH / HTTP/1.1", true, "PATCH")]
[InlineData("POST / HTTP/1.1", true, "POST")]
[InlineData("PUT / HTTP/1.1", true, "PUT")]
[InlineData("OPTIONS / HTTP/1.1", true, "OPTIONS")]
[InlineData("TRACE / HTTP/1.1", true, "TRACE")]
[InlineData("GET/ HTTP/1.1", false, null)]
[InlineData("get / HTTP/1.1", false, null)]
[InlineData("GOT / HTTP/1.1", false, null)]
[InlineData("ABC / HTTP/1.1", false, null)]
[InlineData("PO / HTTP/1.1", false, null)]
[InlineData("PO ST / HTTP/1.1", false, null)]
[InlineData("short ", false, null)]
public void GetsKnownMethodOnBoundary(string input, bool expectedResult, string expectedKnownString)
{
// Test at boundary // Test at boundary
var maxSplit = Math.Min(input.Length, 8); var maxSplit = Math.Min(input.Length, 8);
var nextBlock = _pool.Lease();
for (var split = 0; split <= maxSplit; split++) for (var split = 0; split <= maxSplit; split++)
{ {
// Arrange using (var pipelineFactory = new PipeFactory())
block.Reset(); {
nextBlock.Reset(); // Arrange
var pipe = pipelineFactory.Create();
var buffer = pipe.Writer.Alloc();
var block1Input = input.Substring(0, split);
var block2Input = input.Substring(split);
buffer.Append(ReadableBuffer.Create(Encoding.ASCII.GetBytes(block1Input)));
buffer.Append(ReadableBuffer.Create(Encoding.ASCII.GetBytes(block2Input)));
buffer.FlushAsync().GetAwaiter().GetResult();
Buffer.BlockCopy(chars, 0, block.Array, block.Start, split); var readResult = pipe.Reader.ReadAsync().GetAwaiter().GetResult();
Buffer.BlockCopy(chars, split, nextBlock.Array, nextBlock.Start, chars.Length - split);
block.End += split; // Act
nextBlock.End += chars.Length - split; string boundaryKnownString;
block.Next = nextBlock; var boundaryResult = readResult.Buffer.GetKnownMethod(out boundaryKnownString);
var boundaryBegin = block.GetIterator(); // Assert
string boundaryKnownString; Assert.Equal(expectedResult, boundaryResult);
Assert.Equal(expectedKnownString, boundaryKnownString);
// Act }
var boundaryResult = boundaryBegin.GetKnownMethod(out boundaryKnownString);
// Assert
Assert.Equal(expectedResult, boundaryResult);
Assert.Equal(expectedKnownString, boundaryKnownString);
} }
_pool.Return(block);
_pool.Return(nextBlock);
} }
[Theory] [Theory]
@ -615,49 +631,52 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
public void GetsKnownVersion(string input, bool expectedResult, string expectedKnownString) public void GetsKnownVersion(string input, bool expectedResult, string expectedKnownString)
{ {
// Arrange // Arrange
var block = _pool.Lease(); var block = ReadableBuffer.Create(Encoding.ASCII.GetBytes(input));
var chars = input.ToCharArray().Select(c => (byte)c).ToArray();
Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
block.End += chars.Length;
var begin = block.GetIterator();
string knownString;
// Act // Act
var result = begin.GetKnownVersion(out knownString); string knownString;
var result = block.GetKnownVersion(out knownString);
// Assert // Assert
Assert.Equal(expectedResult, result); Assert.Equal(expectedResult, result);
Assert.Equal(expectedKnownString, knownString); Assert.Equal(expectedKnownString, knownString);
}
[Theory]
[InlineData("HTTP/1.0\r", true, MemoryPoolIteratorExtensions.Http10Version)]
[InlineData("HTTP/1.1\r", true, MemoryPoolIteratorExtensions.Http11Version)]
[InlineData("HTTP/3.0\r", false, null)]
[InlineData("http/1.0\r", false, null)]
[InlineData("http/1.1\r", false, null)]
[InlineData("short ", false, null)]
public void GetsKnownVersionOnBoundary(string input, bool expectedResult, string expectedKnownString)
{
// Test at boundary // Test at boundary
var maxSplit = Math.Min(input.Length, 9); var maxSplit = Math.Min(input.Length, 9);
var nextBlock = _pool.Lease();
for (var split = 0; split <= maxSplit; split++) for (var split = 0; split <= maxSplit; split++)
{ {
// Arrange using (var pipelineFactory = new PipeFactory())
block.Reset(); {
nextBlock.Reset(); // Arrange
var pipe = pipelineFactory.Create();
var buffer = pipe.Writer.Alloc();
var block1Input = input.Substring(0, split);
var block2Input = input.Substring(split);
buffer.Append(ReadableBuffer.Create(Encoding.ASCII.GetBytes(block1Input)));
buffer.Append(ReadableBuffer.Create(Encoding.ASCII.GetBytes(block2Input)));
buffer.FlushAsync().GetAwaiter().GetResult();
Buffer.BlockCopy(chars, 0, block.Array, block.Start, split); var readResult = pipe.Reader.ReadAsync().GetAwaiter().GetResult();
Buffer.BlockCopy(chars, split, nextBlock.Array, nextBlock.Start, chars.Length - split);
block.End += split; // Act
nextBlock.End += chars.Length - split; string boundaryKnownString;
block.Next = nextBlock; var boundaryResult = readResult.Buffer.GetKnownVersion(out boundaryKnownString);
var boundaryBegin = block.GetIterator(); // Assert
string boundaryKnownString; Assert.Equal(expectedResult, boundaryResult);
Assert.Equal(expectedKnownString, boundaryKnownString);
// Act }
var boundaryResult = boundaryBegin.GetKnownVersion(out boundaryKnownString);
// Assert
Assert.Equal(expectedResult, boundaryResult);
Assert.Equal(expectedKnownString, boundaryKnownString);
} }
_pool.Return(block);
_pool.Return(nextBlock);
} }
[Theory] [Theory]
@ -681,37 +700,24 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData("HTTP/1.1\r", "")] [InlineData("HTTP/1.1\r", "")]
public void KnownVersionCanBeReadAtAnyBlockBoundary(string block1Input, string block2Input) public void KnownVersionCanBeReadAtAnyBlockBoundary(string block1Input, string block2Input)
{ {
MemoryPoolBlock block1 = null; using (var pipelineFactory = new PipeFactory())
MemoryPoolBlock block2 = null;
try
{ {
// Arrange // Arrange
var chars1 = block1Input.ToCharArray().Select(c => (byte)c).ToArray(); var pipe = pipelineFactory.Create();
var chars2 = block2Input.ToCharArray().Select(c => (byte)c).ToArray(); var buffer = pipe.Writer.Alloc();
block1 = _pool.Lease(); buffer.Append(ReadableBuffer.Create(Encoding.ASCII.GetBytes(block1Input)));
block2 = _pool.Lease(); buffer.Append(ReadableBuffer.Create(Encoding.ASCII.GetBytes(block2Input)));
Buffer.BlockCopy(chars1, 0, block1.Array, block1.Start, chars1.Length); buffer.FlushAsync().GetAwaiter().GetResult();
Buffer.BlockCopy(chars2, 0, block2.Array, block2.Start, chars2.Length);
block1.End += chars1.Length;
block2.End += chars2.Length;
block1.Next = block2;
var iterator = block1.GetIterator();
var readResult = pipe.Reader.ReadAsync().GetAwaiter().GetResult();
// Act // Act
string knownVersion; string knownVersion;
var result = iterator.GetKnownVersion(out knownVersion); var result = readResult.Buffer.GetKnownVersion(out knownVersion);
// Assert // Assert
Assert.True(result); Assert.True(result);
Assert.Equal("HTTP/1.1", knownVersion); Assert.Equal("HTTP/1.1", knownVersion);
} }
finally
{
// Cleanup
if (block1 != null) _pool.Return(block1);
if (block2 != null) _pool.Return(block2);
}
} }
[Theory] [Theory]
@ -740,7 +746,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
// Arrange // Arrange
block = _pool.Lease(); block = _pool.Lease();
var chars = input.ToString().ToCharArray().Select(c => (byte)c).ToArray(); var chars = input.ToString().ToCharArray().Select(c => (byte) c).ToArray();
Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length); Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
block.End += chars.Length; block.End += chars.Length;
var scan = block.GetIterator(); var scan = block.GetIterator();
@ -974,7 +980,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[Fact] [Fact]
public void EmptyIteratorBehaviourIsValid() public void EmptyIteratorBehaviourIsValid()
{ {
const byte byteCr = (byte) '\n'; const byte byteCr = (byte)'\n';
ulong longValue; ulong longValue;
var end = default(MemoryPoolIterator); var end = default(MemoryPoolIterator);
@ -1194,16 +1200,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
try try
{ {
// Arrange // Arrange
block = _pool.Lease(); var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(input));
var chars = input.ToCharArray().Select(c => (byte)c).ToArray();
Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
block.End += chars.Length;
var start = block.GetIterator();
var end = start;
end.Skip(input.Length);
// Act // Act
var result = start.GetAsciiStringEscaped(end, maxChars); var result = buffer.Start.GetAsciiStringEscaped(buffer.End, maxChars);
// Assert // Assert
Assert.Equal(expected, result); Assert.Equal(expected, result);
@ -1294,28 +1294,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
} }
} }
private delegate bool GetKnownString(MemoryPoolIterator iter, out string result); private delegate bool GetKnownString(ReadableBuffer iter, out string result);
private void TestKnownStringsInterning(string input, string expected, GetKnownString action) private void TestKnownStringsInterning(string input, string expected, GetKnownString action)
{ {
// Arrange
var chars = input.ToCharArray().Select(c => (byte)c).ToArray();
var block1 = _pool.Lease();
var block2 = _pool.Lease();
Buffer.BlockCopy(chars, 0, block1.Array, block1.Start, chars.Length);
Buffer.BlockCopy(chars, 0, block2.Array, block2.Start, chars.Length);
block1.End += chars.Length;
block2.End += chars.Length;
var begin1 = block1.GetIterator();
var begin2 = block2.GetIterator();
// Act // Act
string knownString1, knownString2; string knownString1, knownString2;
var result1 = action(begin1, out knownString1); var result1 = action(ReadableBuffer.Create(Encoding.ASCII.GetBytes(input)), out knownString1);
var result2 = action(begin2, out knownString2); var result2 = action(ReadableBuffer.Create(Encoding.ASCII.GetBytes(input)), out knownString2);
_pool.Return(block1);
_pool.Return(block2);
// Assert // Assert
Assert.True(result1); Assert.True(result1);

Просмотреть файл

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Pipelines;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -282,24 +283,29 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
// so no need to bounds check in this test. // so no need to bounds check in this test.
var socketInput = input.FrameContext.Input; var socketInput = input.FrameContext.Input;
var bytes = Encoding.ASCII.GetBytes(data[0]); var bytes = Encoding.ASCII.GetBytes(data[0]);
var block = socketInput.IncomingStart(); var buffer = socketInput.Writer.Alloc(2048);
Buffer.BlockCopy(bytes, 0, block.Array, block.End, bytes.Length); ArraySegment<byte> block;
socketInput.IncomingComplete(bytes.Length, null); Assert.True(buffer.Memory.TryGetArray(out block));
Buffer.BlockCopy(bytes, 0, block.Array, block.Offset, bytes.Length);
buffer.Advance(bytes.Length);
await buffer.FlushAsync();
// Verify the block passed to WriteAsync is the same one incoming data was written into. // Verify the block passed to WriteAsync is the same one incoming data was written into.
Assert.Same(block.Array, await writeTcs.Task); Assert.Same(block.Array, await writeTcs.Task);
writeTcs = new TaskCompletionSource<byte[]>(); writeTcs = new TaskCompletionSource<byte[]>();
bytes = Encoding.ASCII.GetBytes(data[1]); bytes = Encoding.ASCII.GetBytes(data[1]);
block = socketInput.IncomingStart(); buffer = socketInput.Writer.Alloc(2048);
Buffer.BlockCopy(bytes, 0, block.Array, block.End, bytes.Length); Assert.True(buffer.Memory.TryGetArray(out block));
socketInput.IncomingComplete(bytes.Length, null); Buffer.BlockCopy(bytes, 0, block.Array, block.Offset, bytes.Length);
buffer.Advance(bytes.Length);
await buffer.FlushAsync();
Assert.Same(block.Array, await writeTcs.Task); Assert.Same(block.Array, await writeTcs.Task);
if (headers.HeaderConnection == "close") if (headers.HeaderConnection == "close")
{ {
socketInput.IncomingFin(); socketInput.Writer.Complete();
} }
await copyToAsyncTask; await copyToAsyncTask;

Просмотреть файл

@ -3,7 +3,7 @@
<Import Project="..\..\build\common.props" /> <Import Project="..\..\build\common.props" />
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp1.1;net452</TargetFrameworks> <TargetFrameworks>netcoreapp1.1</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp1.1</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp1.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- TODO remove rid when https://github.com/dotnet/sdk/issues/396 is resolved --> <!-- TODO remove rid when https://github.com/dotnet/sdk/issues/396 is resolved -->

Просмотреть файл

@ -1,243 +0,0 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
using Microsoft.AspNetCore.Testing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.KestrelTests
{
public class SocketInputTests
{
public static readonly TheoryData<Mock<IBufferSizeControl>> MockBufferSizeControlData =
new TheoryData<Mock<IBufferSizeControl>>() { new Mock<IBufferSizeControl>(), null };
[Theory]
[MemberData(nameof(MockBufferSizeControlData))]
public void IncomingDataCallsBufferSizeControlAdd(Mock<IBufferSizeControl> mockBufferSizeControl)
{
using (var memory = new MemoryPool())
using (var socketInput = new SocketInput(memory, null, mockBufferSizeControl?.Object))
{
socketInput.IncomingData(new byte[5], 0, 5);
mockBufferSizeControl?.Verify(b => b.Add(5));
}
}
[Theory]
[MemberData(nameof(MockBufferSizeControlData))]
public void IncomingCompleteCallsBufferSizeControlAdd(Mock<IBufferSizeControl> mockBufferSizeControl)
{
using (var memory = new MemoryPool())
using (var socketInput = new SocketInput(memory, null, mockBufferSizeControl?.Object))
{
socketInput.IncomingComplete(5, null);
mockBufferSizeControl?.Verify(b => b.Add(5));
}
}
[Theory]
[MemberData(nameof(MockBufferSizeControlData))]
public void ConsumingCompleteCallsBufferSizeControlSubtract(Mock<IBufferSizeControl> mockBufferSizeControl)
{
using (var kestrelEngine = new KestrelEngine(new MockLibuv(), new TestServiceContext()))
{
kestrelEngine.Start(1);
using (var memory = new MemoryPool())
using (var socketInput = new SocketInput(memory, null, mockBufferSizeControl?.Object))
{
socketInput.IncomingData(new byte[20], 0, 20);
var iterator = socketInput.ConsumingStart();
iterator.Skip(5);
socketInput.ConsumingComplete(iterator, iterator);
mockBufferSizeControl?.Verify(b => b.Subtract(5));
}
}
}
[Fact]
public async Task ConcurrentReadsFailGracefully()
{
// Arrange
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
using (var memory2 = new MemoryPool())
using (var socketInput = new SocketInput(memory2, ltp))
{
var task0Threw = false;
var task1Threw = false;
var task2Threw = false;
var task0 = AwaitAsTaskAsync(socketInput);
Assert.False(task0.IsFaulted);
var task = task0.ContinueWith(
(t) =>
{
TestConcurrentFaultedTask(t);
task0Threw = true;
},
TaskContinuationOptions.OnlyOnFaulted);
Assert.False(task0.IsFaulted);
// Awaiting/continuing two tasks faults both
var task1 = AwaitAsTaskAsync(socketInput);
await task1.ContinueWith(
(t) =>
{
TestConcurrentFaultedTask(t);
task1Threw = true;
},
TaskContinuationOptions.OnlyOnFaulted);
await task;
Assert.True(task0.IsFaulted);
Assert.True(task1.IsFaulted);
Assert.True(task0Threw);
Assert.True(task1Threw);
// socket stays faulted
var task2 = AwaitAsTaskAsync(socketInput);
await task2.ContinueWith(
(t) =>
{
TestConcurrentFaultedTask(t);
task2Threw = true;
},
TaskContinuationOptions.OnlyOnFaulted);
Assert.True(task2.IsFaulted);
Assert.True(task2Threw);
}
}
[Fact]
public void ConsumingOutOfOrderFailsGracefully()
{
var defultIter = new MemoryPoolIterator();
// Calling ConsumingComplete without a preceding calling to ConsumingStart fails
using (var socketInput = new SocketInput(null, null))
{
Assert.Throws<InvalidOperationException>(() => socketInput.ConsumingComplete(defultIter, defultIter));
}
// Calling ConsumingComplete twice in a row fails
using (var socketInput = new SocketInput(null, null))
{
socketInput.ConsumingStart();
socketInput.ConsumingComplete(defultIter, defultIter);
Assert.Throws<InvalidOperationException>(() => socketInput.ConsumingComplete(defultIter, defultIter));
}
// Calling ConsumingStart twice in a row fails
using (var socketInput = new SocketInput(null, null))
{
socketInput.ConsumingStart();
Assert.Throws<InvalidOperationException>(() => socketInput.ConsumingStart());
}
}
[Fact]
public async Task PeekAsyncRereturnsTheSameData()
{
using (var memory = new MemoryPool())
using (var socketInput = new SocketInput(memory, new SynchronousThreadPool()))
{
socketInput.IncomingData(new byte[5], 0, 5);
Assert.True(socketInput.IsCompleted);
Assert.Equal(5, (await socketInput.PeekAsync()).Count);
// The same 5 bytes will be returned again since it hasn't been consumed.
Assert.True(socketInput.IsCompleted);
Assert.Equal(5, (await socketInput.PeekAsync()).Count);
var scan = socketInput.ConsumingStart();
scan.Skip(3);
socketInput.ConsumingComplete(scan, scan);
// The remaining 2 unconsumed bytes will be returned.
Assert.True(socketInput.IsCompleted);
Assert.Equal(2, (await socketInput.PeekAsync()).Count);
scan = socketInput.ConsumingStart();
scan.Skip(2);
socketInput.ConsumingComplete(scan, scan);
// Everything has been consume so socketInput is no longer in the completed state
Assert.False(socketInput.IsCompleted);
}
}
[Fact]
public async Task CompleteAwaitingDoesNotCauseZeroLengthRead()
{
using (var memory = new MemoryPool())
using (var socketInput = new SocketInput(memory, new SynchronousThreadPool()))
{
var readBuffer = new byte[20];
socketInput.IncomingData(new byte[5], 0, 5);
Assert.Equal(5, await socketInput.ReadAsync(readBuffer, 0, 20));
var readTask = socketInput.ReadAsync(readBuffer, 0, 20);
socketInput.CompleteAwaiting();
Assert.False(readTask.IsCompleted);
socketInput.IncomingData(new byte[5], 0, 5);
Assert.Equal(5, await readTask);
}
}
[Fact]
public async Task CompleteAwaitingDoesNotCauseZeroLengthPeek()
{
using (var memory = new MemoryPool())
using (var socketInput = new SocketInput(memory, new SynchronousThreadPool()))
{
socketInput.IncomingData(new byte[5], 0, 5);
Assert.Equal(5, (await socketInput.PeekAsync()).Count);
var scan = socketInput.ConsumingStart();
scan.Skip(5);
socketInput.ConsumingComplete(scan, scan);
var peekTask = socketInput.PeekAsync();
socketInput.CompleteAwaiting();
Assert.False(peekTask.IsCompleted);
socketInput.IncomingData(new byte[5], 0, 5);
Assert.Equal(5, (await socketInput.PeekAsync()).Count);
}
}
private static void TestConcurrentFaultedTask(Task t)
{
Assert.True(t.IsFaulted);
Assert.IsType(typeof(System.InvalidOperationException), t.Exception.InnerException);
Assert.Equal(t.Exception.InnerException.Message, "Concurrent reads are not supported.");
}
private async Task AwaitAsTaskAsync(SocketInput socketInput)
{
await socketInput;
}
}
}

Просмотреть файл

@ -2,6 +2,8 @@
// 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.IO.Pipelines;
using System.Text;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -11,12 +13,14 @@ using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using MemoryPool = Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure.MemoryPool;
namespace Microsoft.AspNetCore.Server.KestrelTests namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
class TestInput : IConnectionControl, IFrameControl, IDisposable class TestInput : IConnectionControl, IFrameControl, IDisposable
{ {
private MemoryPool _memoryPool; private MemoryPool _memoryPool;
private PipeFactory _pipelineFactory;
public TestInput() public TestInput()
{ {
@ -41,18 +45,19 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
FrameContext.ConnectionContext.ListenerContext.ServiceContext.Log = trace; FrameContext.ConnectionContext.ListenerContext.ServiceContext.Log = trace;
_memoryPool = new MemoryPool(); _memoryPool = new MemoryPool();
FrameContext.Input = new SocketInput(_memoryPool, ltp); _pipelineFactory = new PipeFactory();
FrameContext.Input = _pipelineFactory.Create();;
} }
public Frame FrameContext { get; set; } public Frame FrameContext { get; set; }
public void Add(string text, bool fin = false) public void Add(string text, bool fin = false)
{ {
var data = System.Text.Encoding.ASCII.GetBytes(text); var data = Encoding.ASCII.GetBytes(text);
FrameContext.Input.IncomingData(data, 0, data.Length); FrameContext.Input.Writer.WriteAsync(data).Wait();
if (fin) if (fin)
{ {
FrameContext.Input.IncomingFin(); FrameContext.Input.Writer.Complete();
} }
} }
@ -116,7 +121,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
public void Dispose() public void Dispose()
{ {
FrameContext.Input.Dispose(); _pipelineFactory.Dispose();
_memoryPool.Dispose(); _memoryPool.Dispose();
} }
} }

Просмотреть файл

@ -1,37 +0,0 @@
// 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 Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
namespace Microsoft.AspNetCore.Testing
{
public static class SocketInputExtensions
{
public static void IncomingData(this SocketInput input, byte[] buffer, int offset, int count)
{
var bufferIndex = offset;
var remaining = count;
while (remaining > 0)
{
var block = input.IncomingStart();
var bytesLeftInBlock = block.Data.Offset + block.Data.Count - block.End;
var bytesToCopy = remaining < bytesLeftInBlock ? remaining : bytesLeftInBlock;
Buffer.BlockCopy(buffer, bufferIndex, block.Array, block.End, bytesToCopy);
bufferIndex += bytesToCopy;
remaining -= bytesToCopy;
input.IncomingComplete(bytesToCopy, null);
}
}
public static void IncomingFin(this SocketInput input)
{
input.IncomingComplete(0, null);
}
}
}