Input Pipeline migration (#1277)
This commit is contained in:
Родитель
bfe1f06938
Коммит
824ef2c937
|
@ -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>
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Загрузка…
Ссылка в новой задаче