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="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="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" />
|
||||
</packageSources>
|
||||
</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.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using MemoryPool = Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure.MemoryPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
|
||||
{
|
||||
public class AdaptedPipeline : IDisposable
|
||||
{
|
||||
private const int MinAllocBufferSize = 2048;
|
||||
|
||||
private readonly Stream _filteredStream;
|
||||
|
||||
public AdaptedPipeline(
|
||||
string connectionId,
|
||||
Stream filteredStream,
|
||||
IPipe pipe,
|
||||
MemoryPool memory,
|
||||
IKestrelTrace logger,
|
||||
IThreadPool threadPool,
|
||||
IBufferSizeControl bufferSizeControl)
|
||||
IKestrelTrace logger)
|
||||
{
|
||||
SocketInput = new SocketInput(memory, threadPool, bufferSizeControl);
|
||||
SocketOutput = new StreamSocketOutput(connectionId, filteredStream, memory, logger);
|
||||
Input = pipe;
|
||||
Output = new StreamSocketOutput(connectionId, filteredStream, memory, logger);
|
||||
|
||||
_filteredStream = filteredStream;
|
||||
}
|
||||
|
||||
public SocketInput SocketInput { get; }
|
||||
public IPipe Input { get; }
|
||||
|
||||
public ISocketOutput SocketOutput { get; }
|
||||
public ISocketOutput Output { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SocketInput.Dispose();
|
||||
Input.Writer.Complete();
|
||||
}
|
||||
|
||||
public async Task ReadInputAsync()
|
||||
|
@ -42,21 +45,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
|
|||
|
||||
do
|
||||
{
|
||||
var block = SocketInput.IncomingStart();
|
||||
var block = Input.Writer.Alloc(MinAllocBufferSize);
|
||||
|
||||
try
|
||||
{
|
||||
var count = block.Data.Offset + block.Data.Count - block.End;
|
||||
bytesRead = await _filteredStream.ReadAsync(block.Array, block.End, count);
|
||||
var array = block.Memory.GetArray();
|
||||
try
|
||||
{
|
||||
bytesRead = await _filteredStream.ReadAsync(array.Array, array.Offset, array.Count);
|
||||
block.Advance(bytesRead);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await block.FlushAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SocketInput.IncomingComplete(0, ex);
|
||||
Input.Writer.Complete(ex);
|
||||
throw;
|
||||
}
|
||||
|
||||
SocketInput.IncomingComplete(bytesRead, error: null);
|
||||
} while (bytesRead != 0);
|
||||
|
||||
Input.Writer.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
|
@ -12,12 +13,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
|
|||
{
|
||||
public class RawStream : Stream
|
||||
{
|
||||
private readonly SocketInput _input;
|
||||
private readonly IPipeReader _input;
|
||||
private readonly ISocketOutput _output;
|
||||
|
||||
private Task<int> _cachedTask = TaskCache<int>.DefaultCompletedTask;
|
||||
|
||||
public RawStream(SocketInput input, ISocketOutput output)
|
||||
public RawStream(IPipeReader input, ISocketOutput output)
|
||||
{
|
||||
_input = input;
|
||||
_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)
|
||||
{
|
||||
var task = 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;
|
||||
return ReadAsync(new ArraySegment<byte>(buffer, offset, 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);
|
||||
}
|
||||
|
||||
|
||||
private ValueTask<int> ReadAsync(ArraySegment<byte> buffer)
|
||||
private async Task<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
|
||||
|
|
|
@ -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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter;
|
||||
|
@ -18,6 +19,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
public class Connection : ConnectionContext, IConnectionControl
|
||||
{
|
||||
private const int MinAllocBufferSize = 2048;
|
||||
|
||||
// Base32 encoding - in ascii sort order for easy text based sorting
|
||||
private static readonly string _encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
|
||||
|
||||
|
@ -40,11 +43,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
private Task _readInputTask;
|
||||
|
||||
private TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>();
|
||||
private BufferSizeControl _bufferSizeControl;
|
||||
|
||||
private long _lastTimestamp;
|
||||
private long _timeoutTimestamp = long.MaxValue;
|
||||
private TimeoutAction _timeoutAction;
|
||||
private WritableBuffer? _currentWritableBuffer;
|
||||
|
||||
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));
|
||||
|
||||
if (ServerOptions.Limits.MaxRequestBufferSize.HasValue)
|
||||
{
|
||||
_bufferSizeControl = new BufferSizeControl(ServerOptions.Limits.MaxRequestBufferSize.Value, this);
|
||||
}
|
||||
|
||||
Input = new SocketInput(Thread.Memory, ThreadPool, _bufferSizeControl);
|
||||
Input = Thread.PipelineFactory.Create(ListenerContext.LibuvPipeOptions);
|
||||
Output = new SocketOutput(Thread, _socket, this, ConnectionId, Log, ThreadPool);
|
||||
|
||||
var tcpHandle = _socket as UvTcpHandle;
|
||||
|
@ -92,6 +90,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
// Start socket prior to applying the ConnectionAdapter
|
||||
_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)
|
||||
{
|
||||
_frame.Start();
|
||||
|
@ -107,7 +120,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
public Task StopAsync()
|
||||
{
|
||||
_frame.StopAsync();
|
||||
_frame.Input.CompleteAwaiting();
|
||||
_frame.Input.Reader.CancelPendingRead();
|
||||
|
||||
return _socketClosedTcs.Task;
|
||||
}
|
||||
|
@ -138,11 +151,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
var connection2 = (Connection)state2;
|
||||
connection2._filteredStream.Dispose();
|
||||
connection2._adaptedPipeline.Dispose();
|
||||
Input.Reader.Complete();
|
||||
}, connection);
|
||||
}
|
||||
}, this);
|
||||
|
||||
Input.Dispose();
|
||||
Input.Writer.Complete(new TaskCanceledException("The request was aborted"));
|
||||
_socketClosedTcs.TrySetResult(null);
|
||||
}
|
||||
|
||||
|
@ -168,7 +182,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
try
|
||||
{
|
||||
var rawStream = new RawStream(Input, Output);
|
||||
var rawStream = new RawStream(Input.Reader, Output);
|
||||
var adapterContext = new ConnectionAdapterContext(rawStream);
|
||||
var adaptedConnections = new IAdaptedConnection[_connectionAdapters.Count];
|
||||
|
||||
|
@ -182,11 +196,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
if (adapterContext.ConnectionStream != rawStream)
|
||||
{
|
||||
_filteredStream = adapterContext.ConnectionStream;
|
||||
_adaptedPipeline = new AdaptedPipeline(ConnectionId, adapterContext.ConnectionStream,
|
||||
Thread.Memory, Log, ThreadPool, _bufferSizeControl);
|
||||
_adaptedPipeline = new AdaptedPipeline(
|
||||
ConnectionId,
|
||||
adapterContext.ConnectionStream,
|
||||
Thread.PipelineFactory.Create(ListenerContext.AdaptedPipeOptions),
|
||||
Thread.Memory,
|
||||
Log);
|
||||
|
||||
_frame.Input = _adaptedPipeline.SocketInput;
|
||||
_frame.Output = _adaptedPipeline.SocketOutput;
|
||||
_frame.Input = _adaptedPipeline.Input;
|
||||
_frame.Output = _adaptedPipeline.Output;
|
||||
|
||||
// Don't attempt to read input if connection has already closed.
|
||||
// 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)
|
||||
{
|
||||
Log.LogError(0, ex, $"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}.");
|
||||
Input.Reader.Complete();
|
||||
ConnectionControl.End(ProduceEndType.SocketDisconnect);
|
||||
}
|
||||
}
|
||||
|
@ -210,13 +229,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
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(
|
||||
result.DataArrayPtr + result.End,
|
||||
result.Data.Offset + result.Data.Count - result.End);
|
||||
(IntPtr)dataPtr,
|
||||
currentWritableBuffer.Memory.Length);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void OnRead(UvStreamHandle handle, int status)
|
||||
private async void OnRead(UvStreamHandle handle, int status)
|
||||
{
|
||||
if (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 normalRead = status >= 0;
|
||||
var normalDone = status == Constants.EOF;
|
||||
var errorDone = !(normalDone || normalRead);
|
||||
var readCount = normalRead ? status : 0;
|
||||
|
@ -256,6 +270,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
|
||||
IOException error = null;
|
||||
WritableBufferAwaitable? flushTask = null;
|
||||
if (errorDone)
|
||||
{
|
||||
Exception uvError;
|
||||
|
@ -272,13 +287,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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,
|
||||
// post anyway so the ReadStop() call doesn't get reordered
|
||||
// relative to the ReadStart() call made in Resume().
|
||||
Thread.Post(state => ((Connection)state).OnPausePosted(), this);
|
||||
Thread.Post(state => state.OnPausePosted(), this);
|
||||
}
|
||||
|
||||
void IConnectionControl.Resume()
|
||||
|
@ -297,7 +330,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
Log.ConnectionResume(ConnectionId);
|
||||
|
||||
// This is called from the consuming thread.
|
||||
Thread.Post(state => ((Connection)state).OnResumePosted(), this);
|
||||
Thread.Post(state => state.OnResumePosted(), this);
|
||||
}
|
||||
|
||||
private void OnPausePosted()
|
||||
|
@ -316,14 +349,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
try
|
||||
{
|
||||
_socket.ReadStart(_allocCallback, _readCallback, this);
|
||||
_socket.ReadStart(_allocCallback, _readCallback, this);
|
||||
}
|
||||
catch (UvException)
|
||||
{
|
||||
// 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.
|
||||
Log.ConnectionReadFin(ConnectionId);
|
||||
Input.IncomingComplete(0, null);
|
||||
Input.Writer.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.IO.Pipelines;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
|
@ -20,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
public ListenerContext ListenerContext { get; set; }
|
||||
|
||||
public SocketInput Input { get; set; }
|
||||
public IPipe Input { get; set; }
|
||||
|
||||
public ISocketOutput Output { get; set; }
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,14 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.IO.Pipelines.Text.Primitives;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web.Utf8;
|
||||
using System.Text.Utf8;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -95,7 +99,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
|
||||
public ConnectionContext ConnectionContext { get; }
|
||||
public SocketInput Input { get; set; }
|
||||
public IPipe Input { get; set; }
|
||||
public ISocketOutput Output { get; set; }
|
||||
public IEnumerable<IAdaptedConnection> AdaptedConnections { get; set; }
|
||||
|
||||
|
@ -386,13 +390,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
public void Start()
|
||||
{
|
||||
Reset();
|
||||
_requestProcessingTask =
|
||||
Task.Factory.StartNew(
|
||||
(o) => ((Frame)o).RequestProcessingAsync(),
|
||||
this,
|
||||
default(CancellationToken),
|
||||
TaskCreationOptions.DenyChildAttach,
|
||||
TaskScheduler.Default).Unwrap();
|
||||
_requestProcessingTask = RequestProcessingAsync();
|
||||
_frameStartedTcs.SetResult(null);
|
||||
}
|
||||
|
||||
|
@ -986,216 +984,204 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
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();
|
||||
var start = scan;
|
||||
var consumed = scan;
|
||||
var end = scan;
|
||||
examined = buffer.End;
|
||||
consumed = buffer.Start;
|
||||
|
||||
try
|
||||
if (_requestProcessingStatus == RequestProcessingStatus.RequestPending)
|
||||
{
|
||||
// We may hit this when the client has stopped sending data but
|
||||
// the connection hasn't closed yet, and therefore Frame.Stop()
|
||||
// hasn't been called yet.
|
||||
if (scan.Peek() == -1)
|
||||
ConnectionControl.ResetTimeout(_requestHeadersTimeoutMilliseconds, TimeoutAction.SendTimeoutResponse);
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
RejectRequest(RequestRejectionReason.RequestLineTooLong);
|
||||
}
|
||||
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();
|
||||
begin = scan;
|
||||
var needDecode = false;
|
||||
var chFound = scan.Seek(ByteSpace, ByteQuestionMark, BytePercentage, ref end);
|
||||
method = buffer.Slice(buffer.Start, methodEnd).GetAsciiString();
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
RejectRequest(RequestRejectionReason.InvalidRequestLine,
|
||||
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);
|
||||
}
|
||||
RejectRequestLine(start, end);
|
||||
}
|
||||
};
|
||||
|
||||
var pathBegin = begin;
|
||||
var pathEnd = scan;
|
||||
|
||||
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
|
||||
var queryString = "";
|
||||
ReadCursor queryEnd = pathEnd;
|
||||
if (chFound == ByteQuestionMark)
|
||||
{
|
||||
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)
|
||||
|
@ -1255,34 +1241,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
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();
|
||||
var consumed = scan;
|
||||
var end = scan;
|
||||
try
|
||||
consumed = buffer.Start;
|
||||
examined = buffer.End;
|
||||
|
||||
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();
|
||||
if (ch == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (ch == ByteCR)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var ch = headersEndSpan[0];
|
||||
if (ch == ByteCR)
|
||||
{
|
||||
// Check for final CRLF.
|
||||
end.Take();
|
||||
ch = end.Take();
|
||||
|
||||
if (ch == -1)
|
||||
if (headersEndSpan.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (ch == ByteLF)
|
||||
else if (headersEndSpan[1] == ByteLF)
|
||||
{
|
||||
consumed = headersEnd.End;
|
||||
examined = consumed;
|
||||
ConnectionControl.CancelTimeout();
|
||||
consumed = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1293,129 +1280,113 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
RejectRequest(RequestRejectionReason.HeaderLineMustNotStartWithWhitespace);
|
||||
}
|
||||
}
|
||||
|
||||
// If we've parsed the max allowed numbers of headers and we're starting a new
|
||||
// one, we've gone over the limit.
|
||||
if (_requestHeadersParsed == ServerOptions.Limits.MaxRequestHeaderCount)
|
||||
// If we've parsed the max allowed numbers of headers and we're starting a new
|
||||
// one, we've gone over the limit.
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
finally
|
||||
{
|
||||
input.ConsumingComplete(consumed, end);
|
||||
var beginName = buffer.Start;
|
||||
ReadCursor endName;
|
||||
if (ReadCursorOperations.Seek(buffer.Start, lineEnd, out endName, ByteColon) == -1)
|
||||
{
|
||||
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.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
|
@ -31,66 +32,95 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
/// </summary>
|
||||
public override async Task RequestProcessingAsync()
|
||||
{
|
||||
var requestLineStatus = RequestLineStatus.Empty;
|
||||
var requestLineStatus = default(RequestLineStatus);
|
||||
|
||||
try
|
||||
{
|
||||
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);
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestLineStatus != RequestLineStatus.Done)
|
||||
{
|
||||
RejectRequest(RequestRejectionReason.InvalidRequestLine, requestLineStatus.ToString());
|
||||
}
|
||||
|
||||
break;
|
||||
RejectRequest(RequestRejectionReason.InvalidRequestLine, requestLineStatus.ToString());
|
||||
}
|
||||
|
||||
await Input;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
await Input;
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
RejectRequest(RequestRejectionReason.MalformedRequestInvalidHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_requestProcessingStopping)
|
||||
|
@ -216,6 +246,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
try
|
||||
{
|
||||
Input.Reader.Complete();
|
||||
// If _requestAborted is set, the connection has already been closed.
|
||||
if (Volatile.Read(ref _requestAborted) == 0)
|
||||
{
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
Thread.Post(state =>
|
||||
{
|
||||
var tcs2 = (TaskCompletionSource<int>) state;
|
||||
var tcs2 = state;
|
||||
try
|
||||
{
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.IO.Pipelines;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
|
@ -40,5 +41,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
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);
|
||||
|
||||
var tcs = new TaskCompletionSource<int>(this);
|
||||
Thread.Post(state => StartCallback((TaskCompletionSource<int>)state), tcs);
|
||||
Thread.Post(StartCallback, tcs);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
|
@ -215,9 +215,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
private void ConsumedBytes(int count)
|
||||
{
|
||||
var scan = _context.Input.ConsumingStart();
|
||||
scan.Skip(count);
|
||||
_context.Input.ConsumingComplete(scan, scan);
|
||||
var scan = _context.Input.Reader.ReadAsync().GetResult().Buffer;
|
||||
var consumed = scan.Move(scan.Start, count);
|
||||
_context.Input.Reader.Advance(consumed, consumed);
|
||||
|
||||
OnConsumedBytes(count);
|
||||
}
|
||||
|
@ -304,7 +304,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
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>>();
|
||||
}
|
||||
|
||||
var task = _context.Input.PeekAsync();
|
||||
var task = _context.Input.Reader.PeekAsync();
|
||||
|
||||
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
|
||||
private const byte ByteCR = (byte)'\r';
|
||||
|
||||
private readonly SocketInput _input;
|
||||
private readonly IPipeReader _input;
|
||||
private readonly FrameRequestHeaders _requestHeaders;
|
||||
private int _inputLength;
|
||||
|
||||
|
@ -423,7 +423,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
: base(context)
|
||||
{
|
||||
RequestKeepAlive = keepAlive;
|
||||
_input = _context.Input;
|
||||
_input = _context.Input.Reader;
|
||||
_requestHeaders = headers;
|
||||
}
|
||||
|
||||
|
@ -443,45 +443,71 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
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)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (fin)
|
||||
else if (result.IsCompleted)
|
||||
{
|
||||
_context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
|
||||
}
|
||||
|
||||
await _input;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (fin)
|
||||
else if (result.IsCompleted)
|
||||
{
|
||||
_context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
|
||||
}
|
||||
|
||||
await _input;
|
||||
}
|
||||
|
||||
while (_mode == Mode.Data)
|
||||
{
|
||||
var fin = _input.CheckFinOrThrow();
|
||||
|
||||
var segment = PeekChunkedData();
|
||||
var result = await _input.ReadAsync();
|
||||
var buffer = result.Buffer;
|
||||
ArraySegment<byte> segment;
|
||||
try
|
||||
{
|
||||
segment = PeekChunkedData(buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_input.Advance(buffer.Start, buffer.Start);
|
||||
}
|
||||
|
||||
if (segment.Count != 0)
|
||||
{
|
||||
|
@ -491,195 +517,214 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
break;
|
||||
}
|
||||
else if (fin)
|
||||
else if (result.IsCompleted)
|
||||
{
|
||||
_context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
|
||||
}
|
||||
|
||||
await _input;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (fin)
|
||||
else if (result.IsCompleted)
|
||||
{
|
||||
_context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
|
||||
}
|
||||
|
||||
await _input;
|
||||
}
|
||||
}
|
||||
|
||||
// Chunks finished, parse trailers
|
||||
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)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (fin)
|
||||
else if (result.IsCompleted)
|
||||
{
|
||||
_context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
|
||||
}
|
||||
|
||||
await _input;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.RejectRequest(RequestRejectionReason.ChunkedRequestIncomplete);
|
||||
}
|
||||
}
|
||||
|
||||
await _input;
|
||||
finally
|
||||
{
|
||||
_input.Advance(consumed, examined);
|
||||
}
|
||||
}
|
||||
|
||||
_mode = Mode.Complete;
|
||||
}
|
||||
|
||||
return default(ArraySegment<byte>);
|
||||
}
|
||||
|
||||
private void ParseChunkedPrefix()
|
||||
private void ParseChunkedPrefix(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
|
||||
{
|
||||
var scan = _input.ConsumingStart();
|
||||
var consumed = scan;
|
||||
try
|
||||
consumed = buffer.Start;
|
||||
examined = buffer.Start;
|
||||
var reader = new ReadableBufferReader(buffer);
|
||||
var ch1 = reader.Take();
|
||||
var ch2 = reader.Take();
|
||||
|
||||
if (ch1 == -1 || ch2 == -1)
|
||||
{
|
||||
var ch1 = scan.Take();
|
||||
var ch2 = scan.Take();
|
||||
if (ch1 == -1 || ch2 == -1)
|
||||
examined = reader.Cursor;
|
||||
return;
|
||||
}
|
||||
|
||||
var chunkSize = CalculateChunkSize(ch1, 0);
|
||||
ch1 = ch2;
|
||||
|
||||
do
|
||||
{
|
||||
if (ch1 == ';')
|
||||
{
|
||||
consumed = reader.Cursor;
|
||||
examined = reader.Cursor;
|
||||
|
||||
_inputLength = chunkSize;
|
||||
_mode = Mode.Extension;
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
} while (ch1 != -1);
|
||||
}
|
||||
|
||||
private void ParseExtension()
|
||||
private void ParseExtension(ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
|
||||
{
|
||||
var scan = _input.ConsumingStart();
|
||||
var consumed = scan;
|
||||
try
|
||||
// Chunk-extensions not currently parsed
|
||||
// Just drain the data
|
||||
consumed = buffer.Start;
|
||||
examined = buffer.Start;
|
||||
do
|
||||
{
|
||||
// Chunk-extensions not currently parsed
|
||||
// Just drain the data
|
||||
do
|
||||
ReadCursor extensionCursor;
|
||||
if (ReadCursorOperations.Seek(buffer.Start, buffer.End, out extensionCursor, ByteCR) == -1)
|
||||
{
|
||||
if (scan.Seek(ByteCR) == -1)
|
||||
{
|
||||
// End marker not found yet
|
||||
consumed = scan;
|
||||
return;
|
||||
};
|
||||
// End marker not found yet
|
||||
examined = buffer.End;
|
||||
return;
|
||||
};
|
||||
|
||||
var ch1 = scan.Take();
|
||||
var ch2 = scan.Take();
|
||||
var sufixBuffer = buffer.Slice(extensionCursor);
|
||||
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;
|
||||
if (_inputLength > 0)
|
||||
{
|
||||
_mode = Mode.Data;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mode = Mode.Trailer;
|
||||
}
|
||||
_mode = Mode.Data;
|
||||
}
|
||||
else if (ch2 == -1)
|
||||
else
|
||||
{
|
||||
return;
|
||||
_mode = Mode.Trailer;
|
||||
}
|
||||
} while (_mode == Mode.Extension);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_input.ConsumingComplete(consumed, scan);
|
||||
}
|
||||
}
|
||||
} while (_mode == Mode.Extension);
|
||||
}
|
||||
|
||||
private ArraySegment<byte> PeekChunkedData()
|
||||
private ArraySegment<byte> PeekChunkedData(ReadableBuffer buffer)
|
||||
{
|
||||
if (_inputLength == 0)
|
||||
{
|
||||
_mode = Mode.Suffix;
|
||||
return default(ArraySegment<byte>);
|
||||
}
|
||||
var segment = buffer.First.GetArray();
|
||||
|
||||
var scan = _input.ConsumingStart();
|
||||
var segment = scan.PeekArraySegment();
|
||||
int actual = Math.Min(segment.Count, _inputLength);
|
||||
// Nothing is consumed yet. ConsumedBytes(int) will move the iterator.
|
||||
_input.ConsumingComplete(scan, scan);
|
||||
|
||||
if (actual == segment.Count)
|
||||
{
|
||||
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();
|
||||
var consumed = scan;
|
||||
try
|
||||
consumed = buffer.Start;
|
||||
examined = buffer.Start;
|
||||
|
||||
if (buffer.Length < 2)
|
||||
{
|
||||
var ch1 = scan.Take();
|
||||
var ch2 = scan.Take();
|
||||
if (ch1 == -1 || ch2 == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (ch1 == '\r' && ch2 == '\n')
|
||||
{
|
||||
consumed = scan;
|
||||
_mode = Mode.Prefix;
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.RejectRequest(RequestRejectionReason.BadChunkSuffix);
|
||||
}
|
||||
examined = buffer.End;
|
||||
return;
|
||||
}
|
||||
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();
|
||||
var consumed = scan;
|
||||
try
|
||||
{
|
||||
var ch1 = scan.Take();
|
||||
var ch2 = scan.Take();
|
||||
consumed = buffer.Start;
|
||||
examined = buffer.Start;
|
||||
|
||||
if (ch1 == -1 || ch2 == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (ch1 == '\r' && ch2 == '\n')
|
||||
{
|
||||
consumed = scan;
|
||||
_mode = Mode.Complete;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mode = Mode.TrailerHeaders;
|
||||
}
|
||||
}
|
||||
finally
|
||||
if (buffer.Length < 2)
|
||||
{
|
||||
_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,
|
||||
FinalTransferCodingNotChunked,
|
||||
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()
|
||||
{
|
||||
_thread.Post(state => ((SocketOutput)state).WriteAllPending(), this);
|
||||
_thread.Post(state => state.WriteAllPending(), this);
|
||||
}
|
||||
|
||||
// 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.Collections.Generic;
|
||||
using System.IO.Pipelines;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.InteropServices;
|
||||
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.Networking;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MemoryPool = Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure.MemoryPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for KestrelThread
|
||||
/// </summary>
|
||||
public class KestrelThread
|
||||
public class KestrelThread: IScheduler
|
||||
{
|
||||
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) =>
|
||||
{
|
||||
var streamHandle = UvMemory.FromIntPtr<UvHandle>(ptr) as UvStreamHandle;
|
||||
|
@ -78,10 +78,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
QueueCloseHandle = PostCloseHandle;
|
||||
QueueCloseAsyncHandle = EnqueueCloseHandle;
|
||||
Memory = new MemoryPool();
|
||||
PipelineFactory = new PipeFactory();
|
||||
WriteReqPool = new WriteReqPool(this, _log);
|
||||
ConnectionManager = new ConnectionManager(this, _threadPool);
|
||||
}
|
||||
|
||||
// For testing
|
||||
internal KestrelThread(KestrelEngine engine, int maxLoops)
|
||||
: this(engine)
|
||||
|
@ -93,6 +93,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
|
||||
public MemoryPool Memory { get; }
|
||||
|
||||
public PipeFactory PipelineFactory { get; }
|
||||
|
||||
public ConnectionManager ConnectionManager { get; }
|
||||
|
||||
public WriteReqPool WriteReqPool { get; }
|
||||
|
@ -180,7 +182,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
|
||||
var result = await WaitAsync(PostAsync(state =>
|
||||
{
|
||||
var listener = (KestrelThread)state;
|
||||
var listener = state;
|
||||
listener.WriteReqPool.Dispose();
|
||||
},
|
||||
this), _shutdownTimeout).ConfigureAwait(false);
|
||||
|
@ -193,6 +195,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
finally
|
||||
{
|
||||
Memory.Dispose();
|
||||
PipelineFactory.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,13 +227,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
_loop.Stop();
|
||||
}
|
||||
|
||||
public void Post(Action<object> callback, object state)
|
||||
public void Post<T>(Action<T> callback, T state)
|
||||
{
|
||||
lock (_workSync)
|
||||
{
|
||||
_workAdding.Enqueue(new Work
|
||||
{
|
||||
CallbackAdapter = _postCallbackAdapter,
|
||||
CallbackAdapter = CallbackAdapter<T>.PostCallbackAdapter,
|
||||
Callback = callback,
|
||||
State = state
|
||||
});
|
||||
|
@ -240,17 +243,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
|
||||
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>();
|
||||
lock (_workSync)
|
||||
{
|
||||
_workAdding.Enqueue(new Work
|
||||
{
|
||||
CallbackAdapter = _postAsyncCallbackAdapter,
|
||||
CallbackAdapter = CallbackAdapter<T>.PostAsyncCallbackAdapter,
|
||||
Callback = callback,
|
||||
State = state,
|
||||
Completion = tcs
|
||||
|
@ -439,6 +442,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
return await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task;
|
||||
}
|
||||
|
||||
public void Schedule(Action action)
|
||||
{
|
||||
Post(state => state(), action);
|
||||
}
|
||||
|
||||
private struct Work
|
||||
{
|
||||
public Action<object, object> CallbackAdapter;
|
||||
|
@ -452,5 +460,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
public Action<IntPtr> Callback;
|
||||
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.Diagnostics;
|
||||
using System.IO.Pipelines;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
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 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();
|
||||
sb.Append(ch < 0x20 || ch >= 0x7F ? $"<0x{ch.ToString("X2")}>" : ((char)ch).ToString());
|
||||
var ch = reader.Take();
|
||||
sb.Append(ch < 0x20 || ch >= 0x7F ? $"<0x{ch:X2}>" : ((char)ch).ToString());
|
||||
maxChars--;
|
||||
}
|
||||
|
||||
if (scan.Block != end.Block || scan.Index != end.Index)
|
||||
if (!reader.End)
|
||||
{
|
||||
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>
|
||||
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
|
||||
[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;
|
||||
|
||||
ulong value;
|
||||
if (!begin.TryPeekLong(out value))
|
||||
if (begin.Length < sizeof(ulong))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong value = begin.ReadLittleEndian<ulong>();
|
||||
if ((value & _mask4Chars) == _httpGetMethodLong)
|
||||
{
|
||||
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>
|
||||
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
|
||||
[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;
|
||||
|
||||
ulong value;
|
||||
if (!begin.TryPeekLong(out value))
|
||||
if (begin.Length < sizeof(ulong))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var value = begin.ReadLittleEndian<ulong>();
|
||||
if (value == _http11VersionLong)
|
||||
{
|
||||
knownVersion = Http11Version;
|
||||
|
@ -192,9 +192,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
|||
|
||||
if (knownVersion != null)
|
||||
{
|
||||
begin.Skip(knownVersion.Length);
|
||||
|
||||
if (begin.Peek() != '\r')
|
||||
if (begin.Slice(sizeof(ulong)).Peek() != '\r')
|
||||
{
|
||||
knownVersion = null;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
<PackageReference Include="Microsoft.Extensions.TaskCache.Sources" Version="1.2.0-*" PrivateAssets="All" />
|
||||
<PackageReference Include="System.Numerics.Vectors" 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 Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("LargeUploadData")]
|
||||
[MemberData(nameof(LargeUploadData))]
|
||||
public async Task LargeUpload(long? maxRequestBufferSize, bool ssl, bool expectPause)
|
||||
{
|
||||
// Parameters
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<Import Project="..\..\build\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp1.1;net452</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp1.1</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp1.1</TargetFrameworks>
|
||||
<!-- TODO remove rid when https://github.com/dotnet/sdk/issues/396 is resolved -->
|
||||
<RuntimeIdentifier Condition="'$(TargetFramework)' != 'netcoreapp1.1'">win7-x64</RuntimeIdentifier>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// 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.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
@ -87,7 +88,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
|
||||
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();
|
||||
|
||||
var responseText = await response.Content.ReadAsStringAsync();
|
||||
|
|
|
@ -60,7 +60,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
{
|
||||
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.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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
|
||||
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();
|
||||
var responseBody = await response.Content.ReadAsStreamAsync();
|
||||
|
||||
|
@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
|
||||
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();
|
||||
|
||||
var headers = response.Headers;
|
||||
|
@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
|
||||
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.False(onStartingCalled);
|
||||
|
@ -178,7 +178,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
|
||||
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
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
var requestTasks = new List<Task<string>>();
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
<Compile Include="..\shared\TestKestrelTrace.cs" />
|
||||
<Compile Include="..\shared\MockConnection.cs" />
|
||||
<Compile Include="..\shared\MockSocketOutput.cs" />
|
||||
<Compile Include="..\shared\SocketInputExtensions.cs" />
|
||||
</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>();
|
||||
}
|
||||
if (type.HasFlag(BenchmarkType.Throughput))
|
||||
{
|
||||
BenchmarkRunner.Run<PipeThroughput>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,6 +48,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
{
|
||||
RequestParsing = 1,
|
||||
Writing = 2,
|
||||
Throughput = 4,
|
||||
// add new ones in powers of two - e.g. 2,4,8,16...
|
||||
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
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.Infrastructure;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using MemoryPool = Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure.MemoryPool;
|
||||
using RequestLineStatus = Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.RequestLineStatus;
|
||||
|
||||
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 liveaspnetRequest = "GET https://live.asp.net/ HTTP/1.1\r\n" +
|
||||
"Host: live.asp.net\r\n" +
|
||||
"Connection: keep-alive\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" +
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n" +
|
||||
"DNT: 1\r\n" +
|
||||
"Accept-Encoding: gzip, deflate, sdch, br\r\n" +
|
||||
private const string liveaspnetRequest = "GET https://live.asp.net/ HTTP/1.1\r\n" +
|
||||
"Host: live.asp.net\r\n" +
|
||||
"Connection: keep-alive\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" +
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n" +
|
||||
"DNT: 1\r\n" +
|
||||
"Accept-Encoding: gzip, deflate, sdch, br\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";
|
||||
|
||||
|
@ -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";
|
||||
|
||||
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[] _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[] _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)]
|
||||
public void ParsePlaintext()
|
||||
{
|
||||
for (var i = 0; i < InnerLoopCount; i++)
|
||||
{
|
||||
InsertData(_plaintextRequest);
|
||||
|
||||
ParseData();
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +74,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
for (var i = 0; i < InnerLoopCount; i++)
|
||||
{
|
||||
InsertData(_plaintextPipelinedRequests);
|
||||
|
||||
ParseData();
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +84,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
for (var i = 0; i < InnerLoopCount; i++)
|
||||
{
|
||||
InsertData(_liveaspnentRequest);
|
||||
|
||||
ParseData();
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +94,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
for (var i = 0; i < InnerLoopCount; i++)
|
||||
{
|
||||
InsertData(_liveaspnentPipelinedRequests);
|
||||
|
||||
ParseData();
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +104,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
for (var i = 0; i < InnerLoopCount; i++)
|
||||
{
|
||||
InsertData(_unicodeRequest);
|
||||
|
||||
ParseData();
|
||||
}
|
||||
}
|
||||
|
@ -123,34 +114,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
for (var i = 0; i < InnerLoopCount; i++)
|
||||
{
|
||||
InsertData(_unicodePipelinedRequests);
|
||||
|
||||
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()
|
||||
{
|
||||
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();
|
||||
|
||||
if (Frame.TakeStartLine(SocketInput) != RequestLineStatus.Done)
|
||||
ReadCursor consumed;
|
||||
ReadCursor examined;
|
||||
if (!Frame.TakeStartLine(readableBuffer, out consumed, out examined))
|
||||
{
|
||||
ThrowInvalidStartLine();
|
||||
}
|
||||
Pipe.Reader.Advance(consumed, examined);
|
||||
|
||||
result = Pipe.Reader.ReadAsync().GetAwaiter().GetResult();
|
||||
readableBuffer = result.Buffer;
|
||||
|
||||
Frame.InitializeHeaders();
|
||||
|
||||
if (!Frame.TakeMessageHeaders(SocketInput, (FrameRequestHeaders) Frame.RequestHeaders))
|
||||
if (!Frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)Frame.RequestHeaders, out consumed, out examined))
|
||||
{
|
||||
ThrowInvalidMessageHeaders();
|
||||
}
|
||||
Pipe.Reader.Advance(consumed, examined);
|
||||
}
|
||||
while(true);
|
||||
}
|
||||
|
||||
private void ThrowInvalidStartLine()
|
||||
|
@ -166,23 +175,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
[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());
|
||||
connectionContext.Input = SocketInput;
|
||||
|
||||
Frame = new Frame<object>(application: null, context: connectionContext);
|
||||
PipelineFactory = new PipeFactory();
|
||||
Pipe = PipelineFactory.Create();
|
||||
}
|
||||
|
||||
[Cleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
SocketInput.IncomingFin();
|
||||
SocketInput.Dispose();
|
||||
MemoryPool.Dispose();
|
||||
}
|
||||
public IPipe Pipe { get; set; }
|
||||
|
||||
public Frame<object> Frame { get; set; }
|
||||
|
||||
public PipeFactory PipelineFactory { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// 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.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -88,9 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
|
||||
private TestFrame<object> MakeFrame()
|
||||
{
|
||||
var ltp = new LoggingThreadPool(Mock.Of<IKestrelTrace>());
|
||||
var pool = new MemoryPool();
|
||||
var socketInput = new SocketInput(pool, ltp);
|
||||
var socketInput = new PipeFactory().Create();
|
||||
|
||||
var serviceContext = new ServiceContext
|
||||
{
|
||||
|
|
|
@ -49,11 +49,15 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
Libuv.uv_buf_t ignored;
|
||||
mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
@ -13,7 +14,6 @@ using Microsoft.AspNetCore.Http.Features;
|
|||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Moq;
|
||||
|
@ -23,11 +23,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
public class FrameTests : IDisposable
|
||||
{
|
||||
private readonly SocketInput _socketInput;
|
||||
private readonly MemoryPool _pool;
|
||||
private readonly IPipe _socketInput;
|
||||
private readonly TestFrame<object> _frame;
|
||||
private readonly ServiceContext _serviceContext;
|
||||
private readonly ConnectionContext _connectionContext;
|
||||
private PipeFactory _pipelineFactory;
|
||||
|
||||
ReadCursor consumed;
|
||||
ReadCursor examined;
|
||||
|
||||
private class TestFrame<TContext> : Frame<TContext>
|
||||
{
|
||||
|
@ -45,9 +48,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
public FrameTests()
|
||||
{
|
||||
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||
var ltp = new LoggingThreadPool(trace);
|
||||
_pool = new MemoryPool();
|
||||
_socketInput = new SocketInput(_pool, ltp);
|
||||
_pipelineFactory = new PipeFactory();
|
||||
_socketInput = _pipelineFactory.Create();
|
||||
|
||||
_serviceContext = new ServiceContext
|
||||
{
|
||||
|
@ -73,27 +75,26 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
_pool.Dispose();
|
||||
_socketInput.Dispose();
|
||||
_socketInput.Reader.Complete();
|
||||
_socketInput.Writer.Complete();
|
||||
_pipelineFactory.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanReadHeaderValueWithoutLeadingWhitespace()
|
||||
public async Task CanReadHeaderValueWithoutLeadingWhitespace()
|
||||
{
|
||||
_frame.InitializeHeaders();
|
||||
|
||||
var headerArray = Encoding.ASCII.GetBytes("Header:value\r\n\r\n");
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header:value\r\n\r\n"));
|
||||
|
||||
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.Equal(1, _frame.RequestHeaders.Count);
|
||||
Assert.Equal("value", _frame.RequestHeaders["Header"]);
|
||||
|
||||
// Assert TakeMessageHeaders consumed all the input
|
||||
var scan = _socketInput.ConsumingStart();
|
||||
Assert.True(scan.IsEnd);
|
||||
Assert.Equal(readableBuffer.End, consumed);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -107,20 +108,18 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[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")]
|
||||
public void LeadingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders)
|
||||
public async Task LeadingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders)
|
||||
{
|
||||
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
|
||||
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.Equal(1, _frame.RequestHeaders.Count);
|
||||
Assert.Equal("value", _frame.RequestHeaders["Header"]);
|
||||
|
||||
// Assert TakeMessageHeaders consumed all the input
|
||||
var scan = _socketInput.ConsumingStart();
|
||||
Assert.True(scan.IsEnd);
|
||||
Assert.Equal(readableBuffer.End, consumed);
|
||||
}
|
||||
|
||||
[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")]
|
||||
public void TrailingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders)
|
||||
public async Task TrailingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders)
|
||||
{
|
||||
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
|
||||
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.Equal(1, _frame.RequestHeaders.Count);
|
||||
Assert.Equal("value", _frame.RequestHeaders["Header"]);
|
||||
|
||||
// Assert TakeMessageHeaders consumed all the input
|
||||
var scan = _socketInput.ConsumingStart();
|
||||
Assert.True(scan.IsEnd);
|
||||
Assert.Equal(readableBuffer.End, consumed);
|
||||
}
|
||||
|
||||
[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\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")]
|
||||
public void WhitespaceWithinHeaderValueIsPreserved(string rawHeaders, string expectedValue)
|
||||
public async Task WhitespaceWithinHeaderValueIsPreserved(string rawHeaders, string expectedValue)
|
||||
{
|
||||
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
|
||||
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.Equal(1, _frame.RequestHeaders.Count);
|
||||
Assert.Equal(expectedValue, _frame.RequestHeaders["Header"]);
|
||||
|
||||
// Assert TakeMessageHeaders consumed all the input
|
||||
var scan = _socketInput.ConsumingStart();
|
||||
Assert.True(scan.IsEnd);
|
||||
Assert.Equal(readableBuffer.End, consumed);
|
||||
}
|
||||
|
||||
[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\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);
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
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(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TakeMessageHeadersThrowsOnHeaderValueWithLineFolding_CharacterNotAvailableOnFirstAttempt()
|
||||
public async Task TakeMessageHeadersThrowsOnHeaderValueWithLineFolding_CharacterNotAvailableOnFirstAttempt()
|
||||
{
|
||||
var headerArray = Encoding.ASCII.GetBytes("Header-1: value1\r\n");
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("Header-1: value1\r\n"));
|
||||
|
||||
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(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\r\nHeader-2: value2\r\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);
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
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 must not contain CR characters.", exception.Message);
|
||||
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\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);
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
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(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||
}
|
||||
|
@ -244,12 +247,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[InlineData("\tHeader: value\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")]
|
||||
public void TakeMessageHeadersThrowsOnHeaderLineStartingWithWhitespace(string rawHeaders)
|
||||
public async Task TakeMessageHeadersThrowsOnHeaderLineStartingWithWhitespace(string rawHeaders)
|
||||
{
|
||||
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
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(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\t: value2\r\n\r\n")]
|
||||
public void TakeMessageHeadersThrowsOnWhitespaceInHeaderName(string rawHeaders)
|
||||
public async Task TakeMessageHeadersThrowsOnWhitespaceInHeaderName(string rawHeaders)
|
||||
{
|
||||
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
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(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 ")]
|
||||
[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);
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
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(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TakeMessageHeadersThrowsWhenHeadersExceedTotalSizeLimit()
|
||||
public async Task TakeMessageHeadersThrowsWhenHeadersExceedTotalSizeLimit()
|
||||
{
|
||||
const string headerLine = "Header: value\r\n";
|
||||
_serviceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize = headerLine.Length - 1;
|
||||
_frame.Reset();
|
||||
|
||||
var headerArray = Encoding.ASCII.GetBytes($"{headerLine}\r\n");
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
|
||||
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(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TakeMessageHeadersThrowsWhenHeadersExceedCountLimit()
|
||||
public async Task TakeMessageHeadersThrowsWhenHeadersExceedCountLimit()
|
||||
{
|
||||
const string headerLines = "Header-1: value1\r\nHeader-2: value2\r\n";
|
||||
_serviceContext.ServerOptions.Limits.MaxRequestHeaderCount = 1;
|
||||
|
||||
var headerArray = Encoding.ASCII.GetBytes($"{headerLines}\r\n");
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
|
||||
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(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
|
||||
}
|
||||
|
@ -323,19 +336,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[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)]
|
||||
public void EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders)
|
||||
public async Task EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders)
|
||||
{
|
||||
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||
_socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeaders));
|
||||
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.Equal(numHeaders, _frame.RequestHeaders.Count);
|
||||
|
||||
// Assert TakeMessageHeaders consumed all the input
|
||||
var scan = _socketInput.ConsumingStart();
|
||||
Assert.True(scan.IsEnd);
|
||||
Assert.Equal(readableBuffer.End, consumed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -351,7 +362,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetResetsHeaderLimits()
|
||||
public async Task ResetResetsHeaderLimits()
|
||||
{
|
||||
const string headerLine1 = "Header-1: value1\r\n";
|
||||
const string headerLine2 = "Header-2: value2\r\n";
|
||||
|
@ -361,19 +372,25 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
options.Limits.MaxRequestHeaderCount = 1;
|
||||
_serviceContext.ServerOptions = options;
|
||||
|
||||
var headerArray1 = Encoding.ASCII.GetBytes($"{headerLine1}\r\n");
|
||||
_socketInput.IncomingData(headerArray1, 0, headerArray1.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
|
||||
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("value1", _frame.RequestHeaders["Header-1"]);
|
||||
|
||||
_frame.Reset();
|
||||
|
||||
var headerArray2 = Encoding.ASCII.GetBytes($"{headerLine2}\r\n");
|
||||
_socketInput.IncomingData(headerArray2, 0, headerArray1.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
|
||||
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("value2", _frame.RequestHeaders["Header-2"]);
|
||||
}
|
||||
|
@ -462,78 +479,84 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void TakeStartLineCallsConsumingCompleteWithFurthestExamined()
|
||||
public async Task TakeStartLineCallsConsumingCompleteWithFurthestExamined()
|
||||
{
|
||||
var requestLineBytes = Encoding.ASCII.GetBytes("GET / ");
|
||||
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
|
||||
_frame.TakeStartLine(_socketInput);
|
||||
Assert.False(_socketInput.IsCompleted);
|
||||
await _socketInput.Writer.WriteAsync(requestLineBytes);
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
|
||||
_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");
|
||||
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
|
||||
_frame.TakeStartLine(_socketInput);
|
||||
Assert.False(_socketInput.IsCompleted);
|
||||
await _socketInput.Writer.WriteAsync(requestLineBytes);
|
||||
readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
|
||||
_frame.TakeStartLine(readableBuffer, out consumed, out examined);
|
||||
_socketInput.Reader.Advance(consumed, examined);
|
||||
|
||||
Assert.Equal(readableBuffer.End, consumed);
|
||||
Assert.Equal(readableBuffer.End, examined);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", Frame.RequestLineStatus.Empty)]
|
||||
[InlineData("G", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GE", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET ", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET /", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET / ", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET / H", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET / HT", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET / HTT", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET / HTTP", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET / HTTP/", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET / HTTP/1", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET / HTTP/1.", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET / HTTP/1.1", Frame.RequestLineStatus.Incomplete)]
|
||||
[InlineData("GET / HTTP/1.1\r", Frame.RequestLineStatus.Incomplete)]
|
||||
public void TakeStartLineReturnsWhenGivenIncompleteRequestLines(string requestLine, Frame.RequestLineStatus expectedReturnValue)
|
||||
[InlineData("G")]
|
||||
[InlineData("GE")]
|
||||
[InlineData("GET")]
|
||||
[InlineData("GET ")]
|
||||
[InlineData("GET /")]
|
||||
[InlineData("GET / ")]
|
||||
[InlineData("GET / H")]
|
||||
[InlineData("GET / HT")]
|
||||
[InlineData("GET / HTT")]
|
||||
[InlineData("GET / HTTP")]
|
||||
[InlineData("GET / HTTP/")]
|
||||
[InlineData("GET / HTTP/1")]
|
||||
[InlineData("GET / HTTP/1.")]
|
||||
[InlineData("GET / HTTP/1.1")]
|
||||
[InlineData("GET / HTTP/1.1\r")]
|
||||
public async Task TakeStartLineReturnsWhenGivenIncompleteRequestLines(string requestLine)
|
||||
{
|
||||
var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
|
||||
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
|
||||
await _socketInput.Writer.WriteAsync(requestLineBytes);
|
||||
|
||||
var returnValue = _frame.TakeStartLine(_socketInput);
|
||||
Assert.Equal(expectedReturnValue, returnValue);
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
var returnValue = _frame.TakeStartLine(readableBuffer, out consumed, out examined);
|
||||
_socketInput.Reader.Advance(consumed, examined);
|
||||
|
||||
Assert.False(returnValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TakeStartLineStartsRequestHeadersTimeoutOnFirstByteAvailable()
|
||||
public async Task TakeStartLineStartsRequestHeadersTimeoutOnFirstByteAvailable()
|
||||
{
|
||||
var connectionControl = new Mock<IConnectionControl>();
|
||||
_connectionContext.ConnectionControl = connectionControl.Object;
|
||||
|
||||
var requestLineBytes = Encoding.ASCII.GetBytes("G");
|
||||
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("G"));
|
||||
|
||||
_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;
|
||||
connectionControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutAction.SendTimeoutResponse));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TakeStartLineDoesNotStartRequestHeadersTimeoutIfNoDataAvailable()
|
||||
{
|
||||
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()
|
||||
public async Task TakeStartLineThrowsWhenTooLong()
|
||||
{
|
||||
_serviceContext.ServerOptions.Limits.MaxRequestLineSize = "GET / HTTP/1.1\r\n".Length;
|
||||
|
||||
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(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 / \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>")]
|
||||
public void TakeStartLineThrowsWhenInvalid(string requestLine, string expectedExceptionMessage)
|
||||
public async Task TakeStartLineThrowsWhenInvalid(string requestLine, string expectedExceptionMessage)
|
||||
{
|
||||
var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
|
||||
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
|
||||
|
||||
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(StatusCodes.Status400BadRequest, exception.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TakeStartLineThrowsOnUnsupportedHttpVersion()
|
||||
public async Task TakeStartLineThrowsOnUnsupportedHttpVersion()
|
||||
{
|
||||
var requestLineBytes = Encoding.ASCII.GetBytes("GET / HTTP/1.2\r\n");
|
||||
_socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.2\r\n"));
|
||||
|
||||
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(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TakeStartLineThrowsOnUnsupportedHttpVersionLongerThanEightCharacters()
|
||||
public async Task TakeStartLineThrowsOnUnsupportedHttpVersionLongerThanEightCharacters()
|
||||
{
|
||||
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(StatusCodes.Status505HttpVersionNotsupported, exception.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TakeMessageHeadersCallsConsumingCompleteWithFurthestExamined()
|
||||
public async Task TakeMessageHeadersCallsConsumingCompleteWithFurthestExamined()
|
||||
{
|
||||
var headersBytes = Encoding.ASCII.GetBytes("Header: ");
|
||||
_socketInput.IncomingData(headersBytes, 0, headersBytes.Length);
|
||||
_frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders);
|
||||
Assert.False(_socketInput.IsCompleted);
|
||||
foreach (var rawHeader in new [] { "Header: " , "value\r\n" , "\r\n"})
|
||||
{
|
||||
await _socketInput.Writer.WriteAsync(Encoding.ASCII.GetBytes(rawHeader));
|
||||
|
||||
headersBytes = Encoding.ASCII.GetBytes("value\r\n");
|
||||
_socketInput.IncomingData(headersBytes, 0, headersBytes.Length);
|
||||
_frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders);
|
||||
Assert.False(_socketInput.IsCompleted);
|
||||
|
||||
headersBytes = Encoding.ASCII.GetBytes("\r\n");
|
||||
_socketInput.IncomingData(headersBytes, 0, headersBytes.Length);
|
||||
_frame.TakeMessageHeaders(_socketInput, (FrameRequestHeaders)_frame.RequestHeaders);
|
||||
Assert.False(_socketInput.IsCompleted);
|
||||
var readableBuffer = (await _socketInput.Reader.ReadAsync()).Buffer;
|
||||
_frame.TakeMessageHeaders(readableBuffer, (FrameRequestHeaders)_frame.RequestHeaders, out consumed, out examined);
|
||||
_socketInput.Reader.Advance(consumed, examined);
|
||||
Assert.Equal(readableBuffer.End, examined);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -619,12 +647,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[InlineData("Header: value\r")]
|
||||
[InlineData("Header: value\r\n")]
|
||||
[InlineData("Header: value\r\n\r")]
|
||||
public void TakeMessageHeadersReturnsWhenGivenIncompleteHeaders(string headers)
|
||||
public async Task TakeMessageHeadersReturnsWhenGivenIncompleteHeaders(string 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]
|
||||
|
@ -639,7 +672,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection));
|
||||
|
||||
_frame.StopAsync();
|
||||
_socketInput.IncomingFin();
|
||||
_socketInput.Writer.Complete();
|
||||
|
||||
requestProcessingTask.Wait();
|
||||
}
|
||||
|
@ -721,13 +754,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
_frame.Start();
|
||||
|
||||
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();
|
||||
Assert.IsNotType(typeof(Task<Task>), requestProcessingTask);
|
||||
|
||||
await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10));
|
||||
_socketInput.IncomingFin();
|
||||
_socketInput.Writer.Complete();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -182,7 +182,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
},
|
||||
null);
|
||||
}, null);
|
||||
}, (object)null);
|
||||
|
||||
await connectTcs.Task;
|
||||
|
||||
|
@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
Assert.Equal("Primary", 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
|
||||
for (var i = 0; i < 10 && primaryTrace.Logger.TotalErrorsLogged == 0; i++)
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
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
|
||||
{
|
||||
|
@ -68,7 +71,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
public void MemorySeek(string raw, string search, char expectResult, int expectIndex)
|
||||
{
|
||||
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);
|
||||
block.End += chars.Length;
|
||||
|
||||
|
@ -150,7 +153,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
head = blocks[0].GetIterator();
|
||||
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
|
||||
|
@ -167,7 +170,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
// Arrange
|
||||
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);
|
||||
block.End += bytes.Length;
|
||||
var scan = block.GetIterator();
|
||||
|
@ -177,7 +180,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
var result = scan.PeekArraySegment();
|
||||
|
||||
// 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);
|
||||
|
||||
_pool.Return(block);
|
||||
|
@ -195,7 +198,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
// Arrange
|
||||
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);
|
||||
block.End += bytes.Length;
|
||||
block.Start = block.End;
|
||||
|
@ -559,50 +562,63 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
public void GetsKnownMethod(string input, bool expectedResult, string expectedKnownString)
|
||||
{
|
||||
// Arrange
|
||||
var block = _pool.Lease();
|
||||
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;
|
||||
var block = ReadableBuffer.Create(Encoding.ASCII.GetBytes(input));
|
||||
|
||||
// Act
|
||||
var result = begin.GetKnownMethod(out knownString);
|
||||
|
||||
string knownString;
|
||||
var result = block.GetKnownMethod(out knownString);
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, result);
|
||||
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
|
||||
var maxSplit = Math.Min(input.Length, 8);
|
||||
var nextBlock = _pool.Lease();
|
||||
|
||||
for (var split = 0; split <= maxSplit; split++)
|
||||
{
|
||||
// Arrange
|
||||
block.Reset();
|
||||
nextBlock.Reset();
|
||||
using (var pipelineFactory = new PipeFactory())
|
||||
{
|
||||
// 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);
|
||||
Buffer.BlockCopy(chars, split, nextBlock.Array, nextBlock.Start, chars.Length - split);
|
||||
var readResult = pipe.Reader.ReadAsync().GetAwaiter().GetResult();
|
||||
|
||||
block.End += split;
|
||||
nextBlock.End += chars.Length - split;
|
||||
block.Next = nextBlock;
|
||||
// Act
|
||||
string boundaryKnownString;
|
||||
var boundaryResult = readResult.Buffer.GetKnownMethod(out boundaryKnownString);
|
||||
|
||||
var boundaryBegin = block.GetIterator();
|
||||
string boundaryKnownString;
|
||||
|
||||
// Act
|
||||
var boundaryResult = boundaryBegin.GetKnownMethod(out boundaryKnownString);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, boundaryResult);
|
||||
Assert.Equal(expectedKnownString, boundaryKnownString);
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, boundaryResult);
|
||||
Assert.Equal(expectedKnownString, boundaryKnownString);
|
||||
}
|
||||
}
|
||||
|
||||
_pool.Return(block);
|
||||
_pool.Return(nextBlock);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -615,49 +631,52 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
public void GetsKnownVersion(string input, bool expectedResult, string expectedKnownString)
|
||||
{
|
||||
// Arrange
|
||||
var block = _pool.Lease();
|
||||
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;
|
||||
var block = ReadableBuffer.Create(Encoding.ASCII.GetBytes(input));
|
||||
|
||||
// Act
|
||||
var result = begin.GetKnownVersion(out knownString);
|
||||
string knownString;
|
||||
var result = block.GetKnownVersion(out knownString);
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, result);
|
||||
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
|
||||
var maxSplit = Math.Min(input.Length, 9);
|
||||
var nextBlock = _pool.Lease();
|
||||
|
||||
for (var split = 0; split <= maxSplit; split++)
|
||||
{
|
||||
// Arrange
|
||||
block.Reset();
|
||||
nextBlock.Reset();
|
||||
using (var pipelineFactory = new PipeFactory())
|
||||
{
|
||||
// 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);
|
||||
Buffer.BlockCopy(chars, split, nextBlock.Array, nextBlock.Start, chars.Length - split);
|
||||
var readResult = pipe.Reader.ReadAsync().GetAwaiter().GetResult();
|
||||
|
||||
block.End += split;
|
||||
nextBlock.End += chars.Length - split;
|
||||
block.Next = nextBlock;
|
||||
// Act
|
||||
string boundaryKnownString;
|
||||
var boundaryResult = readResult.Buffer.GetKnownVersion(out boundaryKnownString);
|
||||
|
||||
var boundaryBegin = block.GetIterator();
|
||||
string boundaryKnownString;
|
||||
|
||||
// Act
|
||||
var boundaryResult = boundaryBegin.GetKnownVersion(out boundaryKnownString);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, boundaryResult);
|
||||
Assert.Equal(expectedKnownString, boundaryKnownString);
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, boundaryResult);
|
||||
Assert.Equal(expectedKnownString, boundaryKnownString);
|
||||
}
|
||||
}
|
||||
|
||||
_pool.Return(block);
|
||||
_pool.Return(nextBlock);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -681,37 +700,24 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[InlineData("HTTP/1.1\r", "")]
|
||||
public void KnownVersionCanBeReadAtAnyBlockBoundary(string block1Input, string block2Input)
|
||||
{
|
||||
MemoryPoolBlock block1 = null;
|
||||
MemoryPoolBlock block2 = null;
|
||||
|
||||
try
|
||||
using (var pipelineFactory = new PipeFactory())
|
||||
{
|
||||
// Arrange
|
||||
var chars1 = block1Input.ToCharArray().Select(c => (byte)c).ToArray();
|
||||
var chars2 = block2Input.ToCharArray().Select(c => (byte)c).ToArray();
|
||||
block1 = _pool.Lease();
|
||||
block2 = _pool.Lease();
|
||||
Buffer.BlockCopy(chars1, 0, block1.Array, block1.Start, chars1.Length);
|
||||
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 pipe = pipelineFactory.Create();
|
||||
var buffer = pipe.Writer.Alloc();
|
||||
buffer.Append(ReadableBuffer.Create(Encoding.ASCII.GetBytes(block1Input)));
|
||||
buffer.Append(ReadableBuffer.Create(Encoding.ASCII.GetBytes(block2Input)));
|
||||
buffer.FlushAsync().GetAwaiter().GetResult();
|
||||
|
||||
var readResult = pipe.Reader.ReadAsync().GetAwaiter().GetResult();
|
||||
// Act
|
||||
string knownVersion;
|
||||
var result = iterator.GetKnownVersion(out knownVersion);
|
||||
var result = readResult.Buffer.GetKnownVersion(out knownVersion);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Equal("HTTP/1.1", knownVersion);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
if (block1 != null) _pool.Return(block1);
|
||||
if (block2 != null) _pool.Return(block2);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -740,7 +746,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
// Arrange
|
||||
|
||||
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);
|
||||
block.End += chars.Length;
|
||||
var scan = block.GetIterator();
|
||||
|
@ -974,7 +980,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[Fact]
|
||||
public void EmptyIteratorBehaviourIsValid()
|
||||
{
|
||||
const byte byteCr = (byte) '\n';
|
||||
const byte byteCr = (byte)'\n';
|
||||
ulong longValue;
|
||||
var end = default(MemoryPoolIterator);
|
||||
|
||||
|
@ -1194,16 +1200,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
try
|
||||
{
|
||||
// Arrange
|
||||
block = _pool.Lease();
|
||||
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);
|
||||
var buffer = ReadableBuffer.Create(Encoding.ASCII.GetBytes(input));
|
||||
|
||||
// Act
|
||||
var result = start.GetAsciiStringEscaped(end, maxChars);
|
||||
var result = buffer.Start.GetAsciiStringEscaped(buffer.End, maxChars);
|
||||
|
||||
// Assert
|
||||
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)
|
||||
{
|
||||
// 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
|
||||
string knownString1, knownString2;
|
||||
var result1 = action(begin1, out knownString1);
|
||||
var result2 = action(begin2, out knownString2);
|
||||
|
||||
_pool.Return(block1);
|
||||
_pool.Return(block2);
|
||||
var result1 = action(ReadableBuffer.Create(Encoding.ASCII.GetBytes(input)), out knownString1);
|
||||
var result2 = action(ReadableBuffer.Create(Encoding.ASCII.GetBytes(input)), out knownString2);
|
||||
|
||||
// Assert
|
||||
Assert.True(result1);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
@ -282,24 +283,29 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
// so no need to bounds check in this test.
|
||||
var socketInput = input.FrameContext.Input;
|
||||
var bytes = Encoding.ASCII.GetBytes(data[0]);
|
||||
var block = socketInput.IncomingStart();
|
||||
Buffer.BlockCopy(bytes, 0, block.Array, block.End, bytes.Length);
|
||||
socketInput.IncomingComplete(bytes.Length, null);
|
||||
var buffer = socketInput.Writer.Alloc(2048);
|
||||
ArraySegment<byte> block;
|
||||
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.
|
||||
Assert.Same(block.Array, await writeTcs.Task);
|
||||
|
||||
writeTcs = new TaskCompletionSource<byte[]>();
|
||||
bytes = Encoding.ASCII.GetBytes(data[1]);
|
||||
block = socketInput.IncomingStart();
|
||||
Buffer.BlockCopy(bytes, 0, block.Array, block.End, bytes.Length);
|
||||
socketInput.IncomingComplete(bytes.Length, null);
|
||||
buffer = socketInput.Writer.Alloc(2048);
|
||||
Assert.True(buffer.Memory.TryGetArray(out block));
|
||||
Buffer.BlockCopy(bytes, 0, block.Array, block.Offset, bytes.Length);
|
||||
buffer.Advance(bytes.Length);
|
||||
await buffer.FlushAsync();
|
||||
|
||||
Assert.Same(block.Array, await writeTcs.Task);
|
||||
|
||||
if (headers.HeaderConnection == "close")
|
||||
{
|
||||
socketInput.IncomingFin();
|
||||
socketInput.Writer.Complete();
|
||||
}
|
||||
|
||||
await copyToAsyncTask;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<Import Project="..\..\build\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp1.1;net452</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp1.1</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp1.1</TargetFrameworks>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<!-- 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.
|
||||
|
||||
using System;
|
||||
using System.IO.Pipelines;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
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.Testing;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using MemoryPool = Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure.MemoryPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||
{
|
||||
class TestInput : IConnectionControl, IFrameControl, IDisposable
|
||||
{
|
||||
private MemoryPool _memoryPool;
|
||||
private PipeFactory _pipelineFactory;
|
||||
|
||||
public TestInput()
|
||||
{
|
||||
|
@ -41,18 +45,19 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
FrameContext.ConnectionContext.ListenerContext.ServiceContext.Log = trace;
|
||||
|
||||
_memoryPool = new MemoryPool();
|
||||
FrameContext.Input = new SocketInput(_memoryPool, ltp);
|
||||
_pipelineFactory = new PipeFactory();
|
||||
FrameContext.Input = _pipelineFactory.Create();;
|
||||
}
|
||||
|
||||
public Frame FrameContext { get; set; }
|
||||
|
||||
public void Add(string text, bool fin = false)
|
||||
{
|
||||
var data = System.Text.Encoding.ASCII.GetBytes(text);
|
||||
FrameContext.Input.IncomingData(data, 0, data.Length);
|
||||
var data = Encoding.ASCII.GetBytes(text);
|
||||
FrameContext.Input.Writer.WriteAsync(data).Wait();
|
||||
if (fin)
|
||||
{
|
||||
FrameContext.Input.IncomingFin();
|
||||
FrameContext.Input.Writer.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,7 +121,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
FrameContext.Input.Dispose();
|
||||
_pipelineFactory.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);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче