Make StartAsync not throw if we haven't started the response (#8199)
This commit is contained in:
parent
c0c2bb3049
commit
bae2f2280a
|
|
@ -484,6 +484,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
public System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
public System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
||||||
public System.Memory<byte> GetMemory(int sizeHint = 0) { throw null; }
|
public System.Memory<byte> GetMemory(int sizeHint = 0) { throw null; }
|
||||||
public System.Span<byte> GetSpan(int sizeHint = 0) { throw null; }
|
public System.Span<byte> GetSpan(int sizeHint = 0) { throw null; }
|
||||||
|
public void Reset() { }
|
||||||
public System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> Write100ContinueAsync() { throw null; }
|
public System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> Write100ContinueAsync() { throw null; }
|
||||||
public System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> WriteChunkAsync(System.ReadOnlySpan<byte> buffer, System.Threading.CancellationToken cancellationToken) { throw null; }
|
public System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> WriteChunkAsync(System.ReadOnlySpan<byte> buffer, System.Threading.CancellationToken cancellationToken) { throw null; }
|
||||||
public System.Threading.Tasks.Task WriteDataAsync(System.ReadOnlySpan<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
public System.Threading.Tasks.Task WriteDataAsync(System.ReadOnlySpan<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
||||||
|
|
@ -954,6 +955,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> FlushAsync(System.Threading.CancellationToken cancellationToken);
|
System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> FlushAsync(System.Threading.CancellationToken cancellationToken);
|
||||||
System.Memory<byte> GetMemory(int sizeHint = 0);
|
System.Memory<byte> GetMemory(int sizeHint = 0);
|
||||||
System.Span<byte> GetSpan(int sizeHint = 0);
|
System.Span<byte> GetSpan(int sizeHint = 0);
|
||||||
|
void Reset();
|
||||||
System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> Write100ContinueAsync();
|
System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> Write100ContinueAsync();
|
||||||
System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> WriteChunkAsync(System.ReadOnlySpan<byte> data, System.Threading.CancellationToken cancellationToken);
|
System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> WriteChunkAsync(System.ReadOnlySpan<byte> data, System.Threading.CancellationToken cancellationToken);
|
||||||
System.Threading.Tasks.Task WriteDataAsync(System.ReadOnlySpan<byte> data, System.Threading.CancellationToken cancellationToken);
|
System.Threading.Tasks.Task WriteDataAsync(System.ReadOnlySpan<byte> data, System.Threading.CancellationToken cancellationToken);
|
||||||
|
|
@ -1297,6 +1299,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
public System.Span<byte> GetSpan(int sizeHint = 0) { throw null; }
|
public System.Span<byte> GetSpan(int sizeHint = 0) { throw null; }
|
||||||
void Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpOutputAborter.Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
void Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpOutputAborter.Abort(Microsoft.AspNetCore.Connections.ConnectionAbortedException abortReason) { }
|
||||||
System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpOutputProducer.WriteChunkAsync(System.ReadOnlySpan<byte> data, System.Threading.CancellationToken cancellationToken) { throw null; }
|
System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpOutputProducer.WriteChunkAsync(System.ReadOnlySpan<byte> data, System.Threading.CancellationToken cancellationToken) { throw null; }
|
||||||
|
public void Reset() { }
|
||||||
public System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> Write100ContinueAsync() { throw null; }
|
public System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> Write100ContinueAsync() { throw null; }
|
||||||
public System.Threading.Tasks.Task WriteChunkAsync(System.ReadOnlySpan<byte> span, System.Threading.CancellationToken cancellationToken) { throw null; }
|
public System.Threading.Tasks.Task WriteChunkAsync(System.ReadOnlySpan<byte> span, System.Threading.CancellationToken cancellationToken) { throw null; }
|
||||||
public System.Threading.Tasks.Task WriteDataAsync(System.ReadOnlySpan<byte> data, System.Threading.CancellationToken cancellationToken) { throw null; }
|
public System.Threading.Tasks.Task WriteDataAsync(System.ReadOnlySpan<byte> data, System.Threading.CancellationToken cancellationToken) { throw null; }
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -26,6 +27,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
// "0\r\n\r\n"
|
// "0\r\n\r\n"
|
||||||
private static ReadOnlySpan<byte> EndChunkedResponseBytes => new byte[] { (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
|
private static ReadOnlySpan<byte> EndChunkedResponseBytes => new byte[] { (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
|
||||||
|
|
||||||
|
private const int BeginChunkLengthMax = 5;
|
||||||
|
private const int EndChunkLength = 2;
|
||||||
|
|
||||||
private readonly string _connectionId;
|
private readonly string _connectionId;
|
||||||
private readonly ConnectionContext _connectionContext;
|
private readonly ConnectionContext _connectionContext;
|
||||||
private readonly IKestrelTrace _log;
|
private readonly IKestrelTrace _log;
|
||||||
|
|
@ -40,21 +44,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private bool _completed;
|
private bool _completed;
|
||||||
private bool _aborted;
|
private bool _aborted;
|
||||||
private long _unflushedBytes;
|
private long _unflushedBytes;
|
||||||
private bool _autoChunk;
|
|
||||||
private readonly PipeWriter _pipeWriter;
|
private readonly PipeWriter _pipeWriter;
|
||||||
private const int MemorySizeThreshold = 1024;
|
private IMemoryOwner<byte> _fakeMemoryOwner;
|
||||||
private const int BeginChunkLengthMax = 5;
|
|
||||||
private const int EndChunkLength = 2;
|
|
||||||
|
|
||||||
// Chunked responses need to be treated uniquely when using GetMemory + Advance.
|
// Chunked responses need to be treated uniquely when using GetMemory + Advance.
|
||||||
// We need to know the size of the data written to the chunk before calling Advance on the
|
// We need to know the size of the data written to the chunk before calling Advance on the
|
||||||
// PipeWriter, meaning we internally track how far we have advanced through a current chunk (_advancedBytesForChunk).
|
// PipeWriter, meaning we internally track how far we have advanced through a current chunk (_advancedBytesForChunk).
|
||||||
// Once write or flush is called, we modify the _currentChunkMemory to prepend the size of data written
|
// Once write or flush is called, we modify the _currentChunkMemory to prepend the size of data written
|
||||||
// and append the end terminator.
|
// and append the end terminator.
|
||||||
|
|
||||||
|
private bool _autoChunk;
|
||||||
private int _advancedBytesForChunk;
|
private int _advancedBytesForChunk;
|
||||||
private Memory<byte> _currentChunkMemory;
|
private Memory<byte> _currentChunkMemory;
|
||||||
private bool _currentChunkMemoryUpdated;
|
private bool _currentChunkMemoryUpdated;
|
||||||
private IMemoryOwner<byte> _fakeMemoryOwner;
|
|
||||||
|
// Fields needed to store writes before calling either startAsync or Write/FlushAsync
|
||||||
|
// These should be cleared by the end of the request
|
||||||
|
private List<CompletedBuffer> _completedSegments;
|
||||||
|
private Memory<byte> _currentSegment;
|
||||||
|
private IMemoryOwner<byte> _currentSegmentOwner;
|
||||||
|
private int _position;
|
||||||
|
private bool _startCalled;
|
||||||
|
|
||||||
public Http1OutputProducer(
|
public Http1OutputProducer(
|
||||||
PipeWriter pipeWriter,
|
PipeWriter pipeWriter,
|
||||||
|
|
@ -158,6 +169,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
return GetFakeMemory(sizeHint);
|
return GetFakeMemory(sizeHint);
|
||||||
}
|
}
|
||||||
|
else if (!_startCalled)
|
||||||
|
{
|
||||||
|
return LeasedMemory(sizeHint);
|
||||||
|
}
|
||||||
else if (_autoChunk)
|
else if (_autoChunk)
|
||||||
{
|
{
|
||||||
return GetChunkedMemory(sizeHint);
|
return GetChunkedMemory(sizeHint);
|
||||||
|
|
@ -177,6 +192,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
return GetFakeMemory(sizeHint).Span;
|
return GetFakeMemory(sizeHint).Span;
|
||||||
}
|
}
|
||||||
|
else if (!_startCalled)
|
||||||
|
{
|
||||||
|
return LeasedMemory(sizeHint).Span;
|
||||||
|
}
|
||||||
else if (_autoChunk)
|
else if (_autoChunk)
|
||||||
{
|
{
|
||||||
return GetChunkedMemory(sizeHint).Span;
|
return GetChunkedMemory(sizeHint).Span;
|
||||||
|
|
@ -197,16 +216,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_autoChunk)
|
if (!_startCalled)
|
||||||
{
|
{
|
||||||
if (bytes < 0)
|
if (bytes >= 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(bytes));
|
if (_currentSegment.Length - bytes < _position)
|
||||||
}
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("Can't advance past buffer size.");
|
||||||
|
}
|
||||||
|
|
||||||
if (bytes + _advancedBytesForChunk > _currentChunkMemory.Length - BeginChunkLengthMax - EndChunkLength)
|
_position += bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_autoChunk)
|
||||||
|
{
|
||||||
|
if (_advancedBytesForChunk > _currentChunkMemory.Length - BeginChunkLengthMax - EndChunkLength - bytes)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Can't advance past buffer size.");
|
throw new ArgumentOutOfRangeException("Can't advance past buffer size.");
|
||||||
}
|
}
|
||||||
_advancedBytesForChunk += bytes;
|
_advancedBytesForChunk += bytes;
|
||||||
}
|
}
|
||||||
|
|
@ -238,6 +264,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
var writer = new BufferWriter<PipeWriter>(_pipeWriter);
|
var writer = new BufferWriter<PipeWriter>(_pipeWriter);
|
||||||
CommitChunkInternal(ref writer, buffer);
|
CommitChunkInternal(ref writer, buffer);
|
||||||
|
_unflushedBytes += writer.BytesCommitted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,7 +287,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.Commit();
|
writer.Commit();
|
||||||
_unflushedBytes += writer.BytesCommitted;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteResponseHeaders(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk)
|
public void WriteResponseHeaders(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk)
|
||||||
|
|
@ -288,8 +314,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
writer.Commit();
|
writer.Commit();
|
||||||
|
|
||||||
_unflushedBytes += writer.BytesCommitted;
|
|
||||||
_autoChunk = autoChunk;
|
_autoChunk = autoChunk;
|
||||||
|
WriteDataWrittenBeforeHeaders(ref writer);
|
||||||
|
_unflushedBytes += writer.BytesCommitted;
|
||||||
|
|
||||||
|
_startCalled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteDataWrittenBeforeHeaders(ref BufferWriter<PipeWriter> writer)
|
||||||
|
{
|
||||||
|
if (_completedSegments != null)
|
||||||
|
{
|
||||||
|
foreach (var segment in _completedSegments)
|
||||||
|
{
|
||||||
|
if (_autoChunk)
|
||||||
|
{
|
||||||
|
CommitChunkInternal(ref writer, segment.Span);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.Write(segment.Span);
|
||||||
|
writer.Commit();
|
||||||
|
}
|
||||||
|
segment.Return();
|
||||||
|
}
|
||||||
|
|
||||||
|
_completedSegments.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_currentSegment.IsEmpty)
|
||||||
|
{
|
||||||
|
var segment = _currentSegment.Slice(0, _position);
|
||||||
|
|
||||||
|
if (_autoChunk)
|
||||||
|
{
|
||||||
|
CommitChunkInternal(ref writer, segment.Span);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.Write(segment.Span);
|
||||||
|
writer.Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
_position = 0;
|
||||||
|
|
||||||
|
DisposeCurrentSegment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -302,10 +372,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
_fakeMemoryOwner = null;
|
_fakeMemoryOwner = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call dispose on any memory that wasn't written.
|
||||||
|
if (_completedSegments != null)
|
||||||
|
{
|
||||||
|
foreach (var segment in _completedSegments)
|
||||||
|
{
|
||||||
|
segment.Return();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposeCurrentSegment();
|
||||||
|
|
||||||
CompletePipe();
|
CompletePipe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DisposeCurrentSegment()
|
||||||
|
{
|
||||||
|
_currentSegmentOwner?.Dispose();
|
||||||
|
_currentSegmentOwner = null;
|
||||||
|
_currentSegment = default;
|
||||||
|
}
|
||||||
|
|
||||||
private void CompletePipe()
|
private void CompletePipe()
|
||||||
{
|
{
|
||||||
if (!_pipeWriterCompleted)
|
if (!_pipeWriterCompleted)
|
||||||
|
|
@ -382,10 +470,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
CommitChunkInternal(ref writer, buffer);
|
CommitChunkInternal(ref writer, buffer);
|
||||||
|
|
||||||
|
_unflushedBytes += writer.BytesCommitted;
|
||||||
|
|
||||||
return FlushAsync(cancellationToken);
|
return FlushAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
Debug.Assert(_currentSegmentOwner == null);
|
||||||
|
Debug.Assert(_completedSegments == null || _completedSegments.Count == 0);
|
||||||
|
_autoChunk = false;
|
||||||
|
_startCalled = false;
|
||||||
|
_currentChunkMemoryUpdated = false;
|
||||||
|
}
|
||||||
|
|
||||||
private ValueTask<FlushResult> WriteAsync(
|
private ValueTask<FlushResult> WriteAsync(
|
||||||
ReadOnlySpan<byte> buffer,
|
ReadOnlySpan<byte> buffer,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
|
|
@ -454,7 +553,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
var memoryMaxLength = _currentChunkMemory.Length - BeginChunkLengthMax - EndChunkLength;
|
var memoryMaxLength = _currentChunkMemory.Length - BeginChunkLengthMax - EndChunkLength;
|
||||||
if (_advancedBytesForChunk >= memoryMaxLength - Math.Min(MemorySizeThreshold, sizeHint))
|
if (_advancedBytesForChunk >= memoryMaxLength - sizeHint && _advancedBytesForChunk > 0)
|
||||||
{
|
{
|
||||||
// Chunk is completely written, commit it to the pipe so GetMemory will return a new chunk of memory.
|
// Chunk is completely written, commit it to the pipe so GetMemory will return a new chunk of memory.
|
||||||
var writer = new BufferWriter<PipeWriter>(_pipeWriter);
|
var writer = new BufferWriter<PipeWriter>(_pipeWriter);
|
||||||
|
|
@ -506,5 +605,91 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
return _fakeMemoryOwner.Memory;
|
return _fakeMemoryOwner.Memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Memory<byte> LeasedMemory(int sizeHint)
|
||||||
|
{
|
||||||
|
EnsureCapacity(sizeHint);
|
||||||
|
return _currentSegment.Slice(_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int sizeHint)
|
||||||
|
{
|
||||||
|
// Only subtracts _position from the current segment length if it's non-null.
|
||||||
|
// If _currentSegment is null, it returns 0.
|
||||||
|
var remainingSize = _currentSegment.Length - _position;
|
||||||
|
|
||||||
|
// If the sizeHint is 0, any capacity will do
|
||||||
|
// Otherwise, the buffer must have enough space for the entire size hint, or we need to add a segment.
|
||||||
|
if ((sizeHint == 0 && remainingSize > 0) || (sizeHint > 0 && remainingSize >= sizeHint))
|
||||||
|
{
|
||||||
|
// We have capacity in the current segment
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddSegment(sizeHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddSegment(int sizeHint = 0)
|
||||||
|
{
|
||||||
|
if (_currentSegment.Length != 0)
|
||||||
|
{
|
||||||
|
// We're adding a segment to the list
|
||||||
|
if (_completedSegments == null)
|
||||||
|
{
|
||||||
|
_completedSegments = new List<CompletedBuffer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position might be less than the segment length if there wasn't enough space to satisfy the sizeHint when
|
||||||
|
// GetMemory was called. In that case we'll take the current segment and call it "completed", but need to
|
||||||
|
// ignore any empty space in it.
|
||||||
|
_completedSegments.Add(new CompletedBuffer(_currentSegmentOwner, _currentSegment, _position));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizeHint <= _memoryPool.MaxBufferSize)
|
||||||
|
{
|
||||||
|
// Get a new buffer using the minimum segment size, unless the size hint is larger than a single segment.
|
||||||
|
// Also, the size cannot be larger than the MaxBufferSize of the MemoryPool
|
||||||
|
var owner = _memoryPool.Rent(Math.Min(sizeHint, _memoryPool.MaxBufferSize));
|
||||||
|
_currentSegment = owner.Memory;
|
||||||
|
_currentSegmentOwner = owner;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_currentSegment = new byte[sizeHint];
|
||||||
|
_currentSegmentOwner = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Holds a byte[] from the pool and a size value. Basically a Memory but guaranteed to be backed by an ArrayPool byte[], so that we know we can return it.
|
||||||
|
/// </summary>
|
||||||
|
private readonly struct CompletedBuffer
|
||||||
|
{
|
||||||
|
private readonly IMemoryOwner<byte> _memoryOwner;
|
||||||
|
|
||||||
|
public Memory<byte> Buffer { get; }
|
||||||
|
public int Length { get; }
|
||||||
|
|
||||||
|
public ReadOnlySpan<byte> Span => Buffer.Span.Slice(0, Length);
|
||||||
|
|
||||||
|
public CompletedBuffer(IMemoryOwner<byte> owner, Memory<byte> buffer, int length)
|
||||||
|
{
|
||||||
|
_memoryOwner = owner;
|
||||||
|
|
||||||
|
Buffer = buffer;
|
||||||
|
Length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Return()
|
||||||
|
{
|
||||||
|
if (_memoryOwner != null)
|
||||||
|
{
|
||||||
|
_memoryOwner.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
// Keep-alive is default for HTTP/1.1 and HTTP/2; parsing and errors will change its value
|
// Keep-alive is default for HTTP/1.1 and HTTP/2; parsing and errors will change its value
|
||||||
// volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
|
// volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
|
||||||
protected volatile bool _keepAlive = true;
|
protected volatile bool _keepAlive = true;
|
||||||
private bool _canWriteResponseBody;
|
// _canWriteResponseBody is set in CreateResponseHeaders.
|
||||||
|
// If we are writing with GetMemory/Advance before calling StartAsync, assume we can write and throw away contents if we can't.
|
||||||
|
private bool _canWriteResponseBody = true;
|
||||||
|
private bool _hasAdvanced;
|
||||||
|
private bool _isLeasedMemoryInvalid = true;
|
||||||
private bool _autoChunk;
|
private bool _autoChunk;
|
||||||
protected Exception _applicationException;
|
protected Exception _applicationException;
|
||||||
private BadHttpRequestException _requestRejectedException;
|
private BadHttpRequestException _requestRejectedException;
|
||||||
|
|
@ -351,6 +355,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
RequestHeaders = HttpRequestHeaders;
|
RequestHeaders = HttpRequestHeaders;
|
||||||
ResponseHeaders = HttpResponseHeaders;
|
ResponseHeaders = HttpResponseHeaders;
|
||||||
|
|
||||||
|
_isLeasedMemoryInvalid = true;
|
||||||
|
_hasAdvanced = false;
|
||||||
|
_canWriteResponseBody = true;
|
||||||
|
|
||||||
if (_scheme == null)
|
if (_scheme == null)
|
||||||
{
|
{
|
||||||
var tlsFeature = ConnectionFeatures?[typeof(ITlsConnectionFeature)];
|
var tlsFeature = ConnectionFeatures?[typeof(ITlsConnectionFeature)];
|
||||||
|
|
@ -380,6 +388,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Output?.Reset();
|
||||||
|
|
||||||
_requestHeadersParsed = 0;
|
_requestHeadersParsed = 0;
|
||||||
|
|
||||||
_responseBytesWritten = 0;
|
_responseBytesWritten = 0;
|
||||||
|
|
@ -921,6 +931,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_isLeasedMemoryInvalid = true;
|
||||||
|
|
||||||
_requestProcessingStatus = RequestProcessingStatus.HeadersCommitted;
|
_requestProcessingStatus = RequestProcessingStatus.HeadersCommitted;
|
||||||
|
|
||||||
var responseHeaders = CreateResponseHeaders(appCompleted);
|
var responseHeaders = CreateResponseHeaders(appCompleted);
|
||||||
|
|
@ -1066,7 +1078,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
_keepAlive = false;
|
_keepAlive = false;
|
||||||
}
|
}
|
||||||
else if (appCompleted || !_canWriteResponseBody)
|
else if ((appCompleted || !_canWriteResponseBody) && !_hasAdvanced) // Avoid setting contentLength of 0 if we wrote data before calling CreateResponseHeaders
|
||||||
{
|
{
|
||||||
// Don't set the Content-Length header automatically for HEAD requests, 204 responses, or 304 responses.
|
// Don't set the Content-Length header automatically for HEAD requests, 204 responses, or 304 responses.
|
||||||
if (CanAutoSetContentLengthZeroResponseHeader())
|
if (CanAutoSetContentLengthZeroResponseHeader())
|
||||||
|
|
@ -1268,6 +1280,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
public void Advance(int bytes)
|
public void Advance(int bytes)
|
||||||
{
|
{
|
||||||
|
if (bytes < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(bytes));
|
||||||
|
}
|
||||||
|
else if (bytes > 0)
|
||||||
|
{
|
||||||
|
_hasAdvanced = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isLeasedMemoryInvalid)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Invalid ordering of calling StartAsync and Advance. " +
|
||||||
|
"Call StartAsync before calling GetMemory/GetSpan and Advance.");
|
||||||
|
}
|
||||||
|
|
||||||
if (_canWriteResponseBody)
|
if (_canWriteResponseBody)
|
||||||
{
|
{
|
||||||
VerifyAndUpdateWrite(bytes);
|
VerifyAndUpdateWrite(bytes);
|
||||||
|
|
@ -1276,7 +1303,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HandleNonBodyResponseWrite();
|
HandleNonBodyResponseWrite();
|
||||||
|
|
||||||
// For HEAD requests, we still use the number of bytes written for logging
|
// For HEAD requests, we still use the number of bytes written for logging
|
||||||
// how many bytes were written.
|
// how many bytes were written.
|
||||||
VerifyAndUpdateWrite(bytes);
|
VerifyAndUpdateWrite(bytes);
|
||||||
|
|
@ -1285,27 +1311,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
public Memory<byte> GetMemory(int sizeHint = 0)
|
public Memory<byte> GetMemory(int sizeHint = 0)
|
||||||
{
|
{
|
||||||
ThrowIfResponseNotStarted();
|
_isLeasedMemoryInvalid = false;
|
||||||
|
|
||||||
return Output.GetMemory(sizeHint);
|
return Output.GetMemory(sizeHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Span<byte> GetSpan(int sizeHint = 0)
|
public Span<byte> GetSpan(int sizeHint = 0)
|
||||||
{
|
{
|
||||||
ThrowIfResponseNotStarted();
|
_isLeasedMemoryInvalid = false;
|
||||||
|
|
||||||
return Output.GetSpan(sizeHint);
|
return Output.GetSpan(sizeHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
[StackTraceHidden]
|
|
||||||
private void ThrowIfResponseNotStarted()
|
|
||||||
{
|
|
||||||
if (!HasResponseStarted)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(CoreStrings.StartAsyncBeforeGetMemory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask<FlushResult> FlushPipeAsync(CancellationToken cancellationToken)
|
public ValueTask<FlushResult> FlushPipeAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!HasResponseStarted)
|
if (!HasResponseStarted)
|
||||||
|
|
@ -1338,6 +1353,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
ApplicationAbort();
|
ApplicationAbort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Output.Complete();
|
Output.Complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,5 +25,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
void Complete();
|
void Complete();
|
||||||
ValueTask<FlushResult> FirstWriteAsync(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, ReadOnlySpan<byte> data, CancellationToken cancellationToken);
|
ValueTask<FlushResult> FirstWriteAsync(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, ReadOnlySpan<byte> data, CancellationToken cancellationToken);
|
||||||
ValueTask<FlushResult> FirstWriteChunkedAsync(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, ReadOnlySpan<byte> data, CancellationToken cancellationToken);
|
ValueTask<FlushResult> FirstWriteChunkedAsync(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, ReadOnlySpan<byte> data, CancellationToken cancellationToken);
|
||||||
|
void Reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
// This will noop for now. See: https://github.com/aspnet/AspNetCore/issues/7370
|
// This will noop for now. See: https://github.com/aspnet/AspNetCore/issues/7370
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private async ValueTask<FlushResult> ProcessDataWrites()
|
private async ValueTask<FlushResult> ProcessDataWrites()
|
||||||
{
|
{
|
||||||
FlushResult flushResult = default;
|
FlushResult flushResult = default;
|
||||||
|
|
|
||||||
|
|
@ -421,6 +421,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ChunksWithGetMemoryAfterStartAsyncBeforeFirstFlushStillFlushes()
|
||||||
|
{
|
||||||
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
|
||||||
|
await response.StartAsync();
|
||||||
|
var memory = response.BodyWriter.GetMemory();
|
||||||
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
||||||
|
fisrtPartOfResponse.CopyTo(memory);
|
||||||
|
response.BodyWriter.Advance(6);
|
||||||
|
|
||||||
|
memory = response.BodyWriter.GetMemory();
|
||||||
|
var secondPartOfResponse = Encoding.ASCII.GetBytes("World!");
|
||||||
|
secondPartOfResponse.CopyTo(memory);
|
||||||
|
response.BodyWriter.Advance(6);
|
||||||
|
|
||||||
|
await response.BodyWriter.FlushAsync();
|
||||||
|
}, testContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host: ",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {testContext.DateHeaderValue}",
|
||||||
|
"Transfer-Encoding: chunked",
|
||||||
|
"",
|
||||||
|
"c",
|
||||||
|
"Hello World!",
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ChunksWithGetMemoryBeforeFirstFlushStillFlushes()
|
public async Task ChunksWithGetMemoryBeforeFirstFlushStillFlushes()
|
||||||
{
|
{
|
||||||
|
|
@ -429,7 +475,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
using (var server = new TestServer(async httpContext =>
|
using (var server = new TestServer(async httpContext =>
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
await response.StartAsync();
|
|
||||||
var memory = response.BodyWriter.GetMemory();
|
var memory = response.BodyWriter.GetMemory();
|
||||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
||||||
fisrtPartOfResponse.CopyTo(memory);
|
fisrtPartOfResponse.CopyTo(memory);
|
||||||
|
|
@ -476,7 +522,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
using (var server = new TestServer(async httpContext =>
|
using (var server = new TestServer(async httpContext =>
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
await response.StartAsync();
|
|
||||||
|
|
||||||
var memory = response.BodyWriter.GetMemory();
|
var memory = response.BodyWriter.GetMemory();
|
||||||
length.Value = memory.Length;
|
length.Value = memory.Length;
|
||||||
|
|
@ -524,7 +569,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ChunksWithGetMemoryWithInitialFlushWorks()
|
public async Task ChunksWithGetMemoryAndStartAsyncWithInitialFlushWorks()
|
||||||
{
|
{
|
||||||
var length = new IntAsRef();
|
var length = new IntAsRef();
|
||||||
var semaphore = new SemaphoreSlim(initialCount: 0);
|
var semaphore = new SemaphoreSlim(initialCount: 0);
|
||||||
|
|
@ -581,6 +626,65 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ChunksWithGetMemoryBeforeFlushEdgeCase()
|
||||||
|
{
|
||||||
|
var length = 0;
|
||||||
|
var semaphore = new SemaphoreSlim(initialCount: 0);
|
||||||
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
|
||||||
|
await response.StartAsync();
|
||||||
|
|
||||||
|
var memory = response.BodyWriter.GetMemory();
|
||||||
|
length = memory.Length - 1;
|
||||||
|
semaphore.Release();
|
||||||
|
|
||||||
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes(new string('a', length));
|
||||||
|
fisrtPartOfResponse.CopyTo(memory);
|
||||||
|
response.BodyWriter.Advance(length);
|
||||||
|
|
||||||
|
var secondMemory = response.BodyWriter.GetMemory(6);
|
||||||
|
|
||||||
|
var secondPartOfResponse = Encoding.ASCII.GetBytes("World!");
|
||||||
|
secondPartOfResponse.CopyTo(secondMemory);
|
||||||
|
response.BodyWriter.Advance(6);
|
||||||
|
|
||||||
|
await response.BodyWriter.FlushAsync();
|
||||||
|
}, testContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host: ",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
// Wait for length to be set
|
||||||
|
await semaphore.WaitAsync();
|
||||||
|
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {testContext.DateHeaderValue}",
|
||||||
|
"Transfer-Encoding: chunked",
|
||||||
|
"",
|
||||||
|
length.ToString("x"),
|
||||||
|
new string('a', length),
|
||||||
|
"6",
|
||||||
|
"World!",
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ChunkGetMemoryMultipleAdvance()
|
public async Task ChunkGetMemoryMultipleAdvance()
|
||||||
{
|
{
|
||||||
|
|
@ -633,6 +737,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
using (var server = new TestServer(async httpContext =>
|
using (var server = new TestServer(async httpContext =>
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
|
await response.StartAsync();
|
||||||
|
|
||||||
// To avoid using span in an async method
|
// To avoid using span in an async method
|
||||||
void NonAsyncMethod()
|
void NonAsyncMethod()
|
||||||
|
|
@ -647,8 +752,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
response.BodyWriter.Advance(6);
|
response.BodyWriter.Advance(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
await response.StartAsync();
|
|
||||||
|
|
||||||
NonAsyncMethod();
|
NonAsyncMethod();
|
||||||
}, testContext))
|
}, testContext))
|
||||||
{
|
{
|
||||||
|
|
@ -687,6 +790,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
await response.StartAsync();
|
await response.StartAsync();
|
||||||
|
|
||||||
var memory = response.BodyWriter.GetMemory(4096);
|
var memory = response.BodyWriter.GetMemory(4096);
|
||||||
|
|
||||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
||||||
fisrtPartOfResponse.CopyTo(memory);
|
fisrtPartOfResponse.CopyTo(memory);
|
||||||
response.BodyWriter.Advance(6);
|
response.BodyWriter.Advance(6);
|
||||||
|
|
@ -717,6 +821,48 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ChunkGetMemoryAndWriteWithoutStart()
|
||||||
|
{
|
||||||
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
|
||||||
|
var memory = response.BodyWriter.GetMemory(4096);
|
||||||
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
||||||
|
fisrtPartOfResponse.CopyTo(memory);
|
||||||
|
response.BodyWriter.Advance(6);
|
||||||
|
|
||||||
|
await response.WriteAsync("World!");
|
||||||
|
}, testContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host: ",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {testContext.DateHeaderValue}",
|
||||||
|
"Transfer-Encoding: chunked",
|
||||||
|
"",
|
||||||
|
"6",
|
||||||
|
"Hello ",
|
||||||
|
"6",
|
||||||
|
"World!",
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetMemoryWithSizeHint()
|
public async Task GetMemoryWithSizeHint()
|
||||||
{
|
{
|
||||||
|
|
@ -758,10 +904,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[InlineData(15)]
|
public async Task GetMemoryWithSizeHintWithoutStartAsync()
|
||||||
[InlineData(255)]
|
{
|
||||||
public async Task ChunkGetMemoryWithSmallerSizesWork(int writeSize)
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
|
||||||
|
var memory = response.BodyWriter.GetMemory(0);
|
||||||
|
|
||||||
|
Assert.Equal(4096, memory.Length);
|
||||||
|
|
||||||
|
memory = response.BodyWriter.GetMemory(1000000);
|
||||||
|
Assert.Equal(1000000, memory.Length);
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}, testContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host: ",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {testContext.DateHeaderValue}",
|
||||||
|
"Content-Length: 0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(15)]
|
||||||
|
[InlineData(255)]
|
||||||
|
public async Task ChunkGetMemoryWithoutStartWithSmallerSizesWork(int writeSize)
|
||||||
{
|
{
|
||||||
var testContext = new TestServiceContext(LoggerFactory);
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
|
||||||
|
|
@ -769,12 +952,53 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
|
|
||||||
await response.StartAsync();
|
|
||||||
|
|
||||||
var memory = response.BodyWriter.GetMemory(4096);
|
var memory = response.BodyWriter.GetMemory(4096);
|
||||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes(new string('a', writeSize));
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes(new string('a', writeSize));
|
||||||
fisrtPartOfResponse.CopyTo(memory);
|
fisrtPartOfResponse.CopyTo(memory);
|
||||||
response.BodyWriter.Advance(writeSize);
|
response.BodyWriter.Advance(writeSize);
|
||||||
|
await response.BodyWriter.FlushAsync();
|
||||||
|
}, testContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host: ",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {testContext.DateHeaderValue}",
|
||||||
|
"Transfer-Encoding: chunked",
|
||||||
|
"",
|
||||||
|
writeSize.ToString("X").ToLower(),
|
||||||
|
new string('a', writeSize),
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(15)]
|
||||||
|
[InlineData(255)]
|
||||||
|
public async Task ChunkGetMemoryWithStartWithSmallerSizesWork(int writeSize)
|
||||||
|
{
|
||||||
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
|
||||||
|
var memory = response.BodyWriter.GetMemory(4096);
|
||||||
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes(new string('a', writeSize));
|
||||||
|
fisrtPartOfResponse.CopyTo(memory);
|
||||||
|
response.BodyWriter.Advance(writeSize);
|
||||||
|
await response.BodyWriter.FlushAsync();
|
||||||
}, testContext))
|
}, testContext))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
|
|
@ -806,7 +1030,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
using (var server = new TestServer(async httpContext =>
|
using (var server = new TestServer(async httpContext =>
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
await response.StartAsync();
|
|
||||||
var memory = response.BodyWriter.GetMemory(4096);
|
var memory = response.BodyWriter.GetMemory(4096);
|
||||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
|
||||||
fisrtPartOfResponse.CopyTo(memory);
|
fisrtPartOfResponse.CopyTo(memory);
|
||||||
|
|
|
||||||
|
|
@ -2625,6 +2625,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetMemoryAdvance_Works()
|
public async Task GetMemoryAdvance_Works()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
};
|
||||||
|
await InitializeConnectionAsync(httpContext =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
var memory = response.BodyWriter.GetMemory();
|
||||||
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
|
||||||
|
fisrtPartOfResponse.CopyTo(memory);
|
||||||
|
response.BodyWriter.Advance(6);
|
||||||
|
|
||||||
|
memory = response.BodyWriter.GetMemory();
|
||||||
|
var secondPartOfResponse = Encoding.ASCII.GetBytes(" world");
|
||||||
|
secondPartOfResponse.CopyTo(memory);
|
||||||
|
response.BodyWriter.Advance(6);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
await StartStreamAsync(1, headers, endStream: true);
|
||||||
|
|
||||||
|
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 37,
|
||||||
|
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||||
|
withStreamId: 1);
|
||||||
|
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 12,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
|
|
||||||
|
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||||
|
|
||||||
|
Assert.Equal(2, _decodedHeaders.Count);
|
||||||
|
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
||||||
|
|
||||||
|
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetMemoryAdvance_WithStartAsync_Works()
|
||||||
{
|
{
|
||||||
var headers = new[]
|
var headers = new[]
|
||||||
{
|
{
|
||||||
|
|
@ -2682,7 +2728,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
await response.StartAsync();
|
await response.StartAsync();
|
||||||
|
|
||||||
var memory = response.BodyWriter.GetMemory();
|
var memory = response.BodyWriter.GetMemory();
|
||||||
Assert.Equal(4096, memory.Length);
|
Assert.Equal(4096, memory.Length);
|
||||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes(new string('a', memory.Length));
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes(new string('a', memory.Length));
|
||||||
|
|
@ -2884,7 +2929,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
|
|
||||||
await response.StartAsync();
|
|
||||||
|
|
||||||
var memory = response.BodyWriter.GetMemory(4096);
|
var memory = response.BodyWriter.GetMemory(4096);
|
||||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
|
||||||
|
|
@ -2960,6 +3004,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteAsync_GetMemoryWithSizeHintAlwaysReturnsSameSizeStartAsync()
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
};
|
||||||
|
await InitializeConnectionAsync(async httpContext =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
|
||||||
|
var memory = response.BodyWriter.GetMemory(0);
|
||||||
|
Assert.Equal(4096, memory.Length);
|
||||||
|
|
||||||
|
memory = response.BodyWriter.GetMemory(4096);
|
||||||
|
Assert.Equal(4096, memory.Length);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
await StartStreamAsync(1, headers, endStream: true);
|
||||||
|
|
||||||
|
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 55,
|
||||||
|
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||||
|
withStreamId: 1);
|
||||||
|
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
|
|
||||||
|
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||||
|
|
||||||
|
Assert.Equal(3, _decodedHeaders.Count);
|
||||||
|
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task WriteAsync_BothPipeAndStreamWorks()
|
public async Task WriteAsync_BothPipeAndStreamWorks()
|
||||||
{
|
{
|
||||||
|
|
@ -3037,7 +3124,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
response.ContentLength = 12;
|
response.ContentLength = 12;
|
||||||
await response.StartAsync();
|
await Task.CompletedTask;
|
||||||
|
|
||||||
void NonAsyncMethod()
|
void NonAsyncMethod()
|
||||||
{
|
{
|
||||||
|
|
@ -3084,11 +3171,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
};
|
};
|
||||||
await InitializeConnectionAsync(async httpContext =>
|
await InitializeConnectionAsync(httpContext =>
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
response.ContentLength = 12;
|
response.ContentLength = 12;
|
||||||
await response.StartAsync();
|
|
||||||
|
|
||||||
var memory = response.BodyWriter.GetMemory(4096);
|
var memory = response.BodyWriter.GetMemory(4096);
|
||||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
||||||
|
|
@ -3098,6 +3184,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
var secondPartOfResponse = Encoding.ASCII.GetBytes("World!");
|
var secondPartOfResponse = Encoding.ASCII.GetBytes("World!");
|
||||||
secondPartOfResponse.CopyTo(memory.Slice(6));
|
secondPartOfResponse.CopyTo(memory.Slice(6));
|
||||||
response.BodyWriter.Advance(6);
|
response.BodyWriter.Advance(6);
|
||||||
|
return Task.CompletedTask;
|
||||||
});
|
});
|
||||||
|
|
||||||
await StartStreamAsync(1, headers, endStream: true);
|
await StartStreamAsync(1, headers, endStream: true);
|
||||||
|
|
@ -3174,8 +3261,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
response.ContentLength = 54;
|
response.ContentLength = 54;
|
||||||
await response.StartAsync();
|
|
||||||
var memory = response.BodyWriter.GetMemory(4096);
|
var memory = response.BodyWriter.GetMemory(4096);
|
||||||
|
|
||||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
|
||||||
fisrtPartOfResponse.CopyTo(memory);
|
fisrtPartOfResponse.CopyTo(memory);
|
||||||
response.BodyWriter.Advance(6);
|
response.BodyWriter.Advance(6);
|
||||||
|
|
|
||||||
|
|
@ -3239,18 +3239,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AdvanceNegativeValueThrowsArgumentOutOfRangeExceptionWithStart()
|
||||||
|
{
|
||||||
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
|
||||||
|
using (var server = new TestServer(httpContext =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
|
||||||
|
Assert.Throws<ArgumentOutOfRangeException>(() => response.BodyWriter.Advance(-1));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}, testContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host: ",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {testContext.DateHeaderValue}",
|
||||||
|
"Content-Length: 0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task AdvanceWithTooLargeOfAValueThrowInvalidOperationException()
|
public async Task AdvanceWithTooLargeOfAValueThrowInvalidOperationException()
|
||||||
{
|
{
|
||||||
var testContext = new TestServiceContext(LoggerFactory);
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
|
||||||
using (var server = new TestServer(async httpContext =>
|
using (var server = new TestServer(httpContext =>
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
|
|
||||||
await response.StartAsync();
|
|
||||||
|
|
||||||
Assert.Throws<InvalidOperationException>(() => response.BodyWriter.Advance(1));
|
Assert.Throws<InvalidOperationException>(() => response.BodyWriter.Advance(1));
|
||||||
|
return Task.CompletedTask;
|
||||||
}, testContext))
|
}, testContext))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
|
|
@ -3275,60 +3306,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetMemoryBeforeStartAsyncThrows()
|
public async Task ContentLengthWithoutStartAsyncWithGetSpanWorks()
|
||||||
{
|
{
|
||||||
var testContext = new TestServiceContext(LoggerFactory);
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
|
||||||
using (var server = new TestServer(httpContext =>
|
using (var server = new TestServer(httpContext =>
|
||||||
{
|
|
||||||
Assert.Throws<InvalidOperationException>(() => httpContext.Response.BodyWriter.GetMemory());
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}, testContext))
|
|
||||||
{
|
|
||||||
using (var connection = server.CreateConnection())
|
|
||||||
{
|
|
||||||
await connection.Send(
|
|
||||||
"GET / HTTP/1.1",
|
|
||||||
"Host: ",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
await connection.Receive(
|
|
||||||
"HTTP/1.1 200 OK",
|
|
||||||
$"Date: {testContext.DateHeaderValue}",
|
|
||||||
"Content-Length: 0",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
}
|
|
||||||
|
|
||||||
await server.StopAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ContentLengthWithGetSpanWorks()
|
|
||||||
{
|
|
||||||
var testContext = new TestServiceContext(LoggerFactory);
|
|
||||||
|
|
||||||
using (var server = new TestServer(async httpContext =>
|
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
response.ContentLength = 12;
|
response.ContentLength = 12;
|
||||||
await response.StartAsync();
|
|
||||||
|
|
||||||
void NonAsyncMethod()
|
var span = response.BodyWriter.GetSpan(4096);
|
||||||
{
|
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
||||||
var span = response.BodyWriter.GetSpan(4096);
|
fisrtPartOfResponse.CopyTo(span);
|
||||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
response.BodyWriter.Advance(6);
|
||||||
fisrtPartOfResponse.CopyTo(span);
|
|
||||||
response.BodyWriter.Advance(6);
|
|
||||||
|
|
||||||
var secondPartOfResponse = Encoding.ASCII.GetBytes("World!");
|
|
||||||
secondPartOfResponse.CopyTo(span.Slice(6));
|
|
||||||
response.BodyWriter.Advance(6);
|
|
||||||
}
|
|
||||||
|
|
||||||
NonAsyncMethod();
|
|
||||||
|
|
||||||
|
var secondPartOfResponse = Encoding.ASCII.GetBytes("World!");
|
||||||
|
secondPartOfResponse.CopyTo(span.Slice(6));
|
||||||
|
response.BodyWriter.Advance(6);
|
||||||
|
return Task.CompletedTask;
|
||||||
}, testContext))
|
}, testContext))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
|
|
@ -3359,6 +3354,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
response.ContentLength = 12;
|
response.ContentLength = 12;
|
||||||
|
|
||||||
await response.StartAsync();
|
await response.StartAsync();
|
||||||
|
|
||||||
var memory = response.BodyWriter.GetMemory(4096);
|
var memory = response.BodyWriter.GetMemory(4096);
|
||||||
|
|
@ -3461,7 +3457,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ResponseBodyPipeCompleteWithoutExceptionDoesNotThrow()
|
public async Task ResponseBodyWriterCompleteWithoutExceptionDoesNotThrow()
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(async httpContext =>
|
using (var server = new TestServer(async httpContext =>
|
||||||
{
|
{
|
||||||
|
|
@ -3488,7 +3484,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ResponseBodyPipeCompleteWithoutExceptionWritesDoNotThrow()
|
public async Task ResponseBodyWriterCompleteWithoutExceptionWritesDoNotThrow()
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(async httpContext =>
|
using (var server = new TestServer(async httpContext =>
|
||||||
{
|
{
|
||||||
|
|
@ -3516,6 +3512,217 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResponseAdvanceStateIsResetWithMultipleReqeusts()
|
||||||
|
{
|
||||||
|
var secondRequest = false;
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
if (secondRequest)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var memory = httpContext.Response.BodyWriter.GetMemory();
|
||||||
|
Encoding.ASCII.GetBytes("a").CopyTo(memory);
|
||||||
|
httpContext.Response.BodyWriter.Advance(1);
|
||||||
|
await httpContext.Response.BodyWriter.FlushAsync();
|
||||||
|
secondRequest = true;
|
||||||
|
|
||||||
|
}, new TestServiceContext(LoggerFactory)))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Transfer-Encoding: chunked",
|
||||||
|
"",
|
||||||
|
"1",
|
||||||
|
"a",
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Content-Length: 0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResponseStartCalledAndAutoChunkStateIsResetWithMultipleReqeusts()
|
||||||
|
{
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
var memory = httpContext.Response.BodyWriter.GetMemory();
|
||||||
|
Encoding.ASCII.GetBytes("a").CopyTo(memory);
|
||||||
|
httpContext.Response.BodyWriter.Advance(1);
|
||||||
|
await httpContext.Response.BodyWriter.FlushAsync();
|
||||||
|
|
||||||
|
}, new TestServiceContext(LoggerFactory)))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Transfer-Encoding: chunked",
|
||||||
|
"",
|
||||||
|
"1",
|
||||||
|
"a",
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Transfer-Encoding: chunked",
|
||||||
|
"",
|
||||||
|
"1",
|
||||||
|
"a",
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResponseStartCalledStateIsResetWithMultipleReqeusts()
|
||||||
|
{
|
||||||
|
var flip = false;
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
if (flip)
|
||||||
|
{
|
||||||
|
httpContext.Response.ContentLength = 1;
|
||||||
|
var memory = httpContext.Response.BodyWriter.GetMemory();
|
||||||
|
Encoding.ASCII.GetBytes("a").CopyTo(memory);
|
||||||
|
httpContext.Response.BodyWriter.Advance(1);
|
||||||
|
await httpContext.Response.BodyWriter.FlushAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var memory = httpContext.Response.BodyWriter.GetMemory();
|
||||||
|
Encoding.ASCII.GetBytes("a").CopyTo(memory);
|
||||||
|
httpContext.Response.BodyWriter.Advance(1);
|
||||||
|
await httpContext.Response.BodyWriter.FlushAsync();
|
||||||
|
}
|
||||||
|
flip = !flip;
|
||||||
|
|
||||||
|
}, new TestServiceContext(LoggerFactory)))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
for (var i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Transfer-Encoding: chunked",
|
||||||
|
"",
|
||||||
|
"1",
|
||||||
|
"a",
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Content-Length: 1",
|
||||||
|
"",
|
||||||
|
"a");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResponseIsLeasedMemoryInvalidStateIsResetWithMultipleReqeusts()
|
||||||
|
{
|
||||||
|
var secondRequest = false;
|
||||||
|
using (var server = new TestServer(httpContext =>
|
||||||
|
{
|
||||||
|
if (secondRequest)
|
||||||
|
{
|
||||||
|
Assert.Throws<InvalidOperationException>(() => httpContext.Response.BodyWriter.Advance(1));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
var memory = httpContext.Response.BodyWriter.GetMemory();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}, new TestServiceContext(LoggerFactory)))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Content-Length: 0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Content-Length: 0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ResponsePipeWriterCompleteWithException()
|
public async Task ResponsePipeWriterCompleteWithException()
|
||||||
{
|
{
|
||||||
|
|
@ -3579,12 +3786,111 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResponseCompleteGetMemoryReturnsRentedMemoryWithoutStartAsync()
|
||||||
|
{
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
httpContext.Response.BodyWriter.Complete();
|
||||||
|
var memory = httpContext.Response.BodyWriter.GetMemory(); // Shouldn't throw
|
||||||
|
Assert.Equal(4096, memory.Length);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}, new TestServiceContext(LoggerFactory)))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Content-Length: 0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResponseGetMemoryAndStartAsyncMemoryReturnsNewMemory()
|
||||||
|
{
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
var memory = httpContext.Response.BodyWriter.GetMemory();
|
||||||
|
Assert.Equal(4096, memory.Length);
|
||||||
|
|
||||||
|
await httpContext.Response.StartAsync();
|
||||||
|
// Original memory is disposed, don't compare against it.
|
||||||
|
|
||||||
|
memory = httpContext.Response.BodyWriter.GetMemory();
|
||||||
|
Assert.NotEqual(4096, memory.Length);
|
||||||
|
|
||||||
|
}, new TestServiceContext(LoggerFactory)))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Transfer-Encoding: chunked",
|
||||||
|
"",
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResponseGetMemoryAndStartAsyncAdvanceThrows()
|
||||||
|
{
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
var memory = httpContext.Response.BodyWriter.GetMemory();
|
||||||
|
|
||||||
|
await httpContext.Response.StartAsync();
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() => httpContext.Response.BodyWriter.Advance(1));
|
||||||
|
|
||||||
|
}, new TestServiceContext(LoggerFactory)))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
|
"Transfer-Encoding: chunked",
|
||||||
|
"",
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ResponseCompleteGetMemoryAdvanceInLoopDoesNotThrow()
|
public async Task ResponseCompleteGetMemoryAdvanceInLoopDoesNotThrow()
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(async httpContext =>
|
using (var server = new TestServer(async httpContext =>
|
||||||
{
|
{
|
||||||
await httpContext.Response.StartAsync();
|
|
||||||
|
|
||||||
httpContext.Response.BodyWriter.Complete();
|
httpContext.Response.BodyWriter.Complete();
|
||||||
for (var i = 0; i < 5; i++)
|
for (var i = 0; i < 5; i++)
|
||||||
|
|
@ -3653,12 +3959,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
var memoryStream = new MemoryStream();
|
var memoryStream = new MemoryStream();
|
||||||
httpContext.Response.Body = memoryStream;
|
httpContext.Response.Body = memoryStream;
|
||||||
var bodyPipe1 = httpContext.Response.BodyWriter;
|
var BodyWriter1 = httpContext.Response.BodyWriter;
|
||||||
|
|
||||||
httpContext.Response.Body = memoryStream;
|
httpContext.Response.Body = memoryStream;
|
||||||
var bodyPipe2 = httpContext.Response.BodyWriter;
|
var BodyWriter2 = httpContext.Response.BodyWriter;
|
||||||
|
|
||||||
Assert.NotEqual(bodyPipe1, bodyPipe2);
|
Assert.NotEqual(BodyWriter1, BodyWriter2);
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}, new TestServiceContext(LoggerFactory)))
|
}, new TestServiceContext(LoggerFactory)))
|
||||||
{
|
{
|
||||||
|
|
@ -3715,7 +4021,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ResponseSetPipeAndBodyPipeIsWrapped()
|
public async Task ResponseSetPipeAndBodyWriterIsWrapped()
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(async httpContext =>
|
using (var server = new TestServer(async httpContext =>
|
||||||
{
|
{
|
||||||
|
|
@ -3746,7 +4052,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ResponseWriteToBodyPipeAndStreamAllBlocksDisposed()
|
public async Task ResponseWriteToBodyWriterAndStreamAllBlocksDisposed()
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(async httpContext =>
|
using (var server = new TestServer(async httpContext =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue