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.Memory<byte> GetMemory(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> 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; }
|
||||
|
|
@ -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.Memory<byte> GetMemory(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> WriteChunkAsync(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; }
|
||||
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; }
|
||||
public void Reset() { }
|
||||
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 WriteDataAsync(System.ReadOnlySpan<byte> data, System.Threading.CancellationToken cancellationToken) { throw null; }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading;
|
||||
|
|
@ -26,6 +27,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
// "0\r\n\r\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 ConnectionContext _connectionContext;
|
||||
private readonly IKestrelTrace _log;
|
||||
|
|
@ -40,21 +44,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
private bool _completed;
|
||||
private bool _aborted;
|
||||
private long _unflushedBytes;
|
||||
private bool _autoChunk;
|
||||
|
||||
private readonly PipeWriter _pipeWriter;
|
||||
private const int MemorySizeThreshold = 1024;
|
||||
private const int BeginChunkLengthMax = 5;
|
||||
private const int EndChunkLength = 2;
|
||||
private IMemoryOwner<byte> _fakeMemoryOwner;
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// and append the end terminator.
|
||||
|
||||
private bool _autoChunk;
|
||||
private int _advancedBytesForChunk;
|
||||
private Memory<byte> _currentChunkMemory;
|
||||
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(
|
||||
PipeWriter pipeWriter,
|
||||
|
|
@ -158,6 +169,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
return GetFakeMemory(sizeHint);
|
||||
}
|
||||
else if (!_startCalled)
|
||||
{
|
||||
return LeasedMemory(sizeHint);
|
||||
}
|
||||
else if (_autoChunk)
|
||||
{
|
||||
return GetChunkedMemory(sizeHint);
|
||||
|
|
@ -177,6 +192,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
return GetFakeMemory(sizeHint).Span;
|
||||
}
|
||||
else if (!_startCalled)
|
||||
{
|
||||
return LeasedMemory(sizeHint).Span;
|
||||
}
|
||||
else if (_autoChunk)
|
||||
{
|
||||
return GetChunkedMemory(sizeHint).Span;
|
||||
|
|
@ -197,16 +216,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
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;
|
||||
}
|
||||
|
|
@ -238,6 +264,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
var writer = new BufferWriter<PipeWriter>(_pipeWriter);
|
||||
CommitChunkInternal(ref writer, buffer);
|
||||
_unflushedBytes += writer.BytesCommitted;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -260,7 +287,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
|
||||
writer.Commit();
|
||||
_unflushedBytes += writer.BytesCommitted;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
_unflushedBytes += writer.BytesCommitted;
|
||||
_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()
|
||||
|
|
@ -302,10 +372,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_fakeMemoryOwner = null;
|
||||
}
|
||||
|
||||
// Call dispose on any memory that wasn't written.
|
||||
if (_completedSegments != null)
|
||||
{
|
||||
foreach (var segment in _completedSegments)
|
||||
{
|
||||
segment.Return();
|
||||
}
|
||||
}
|
||||
|
||||
DisposeCurrentSegment();
|
||||
|
||||
CompletePipe();
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeCurrentSegment()
|
||||
{
|
||||
_currentSegmentOwner?.Dispose();
|
||||
_currentSegmentOwner = null;
|
||||
_currentSegment = default;
|
||||
}
|
||||
|
||||
private void CompletePipe()
|
||||
{
|
||||
if (!_pipeWriterCompleted)
|
||||
|
|
@ -382,10 +470,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
CommitChunkInternal(ref writer, buffer);
|
||||
|
||||
_unflushedBytes += writer.BytesCommitted;
|
||||
|
||||
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(
|
||||
ReadOnlySpan<byte> buffer,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
|
@ -454,7 +553,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
|
||||
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.
|
||||
var writer = new BufferWriter<PipeWriter>(_pipeWriter);
|
||||
|
|
@ -506,5 +605,91 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
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
|
||||
// volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
|
||||
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;
|
||||
protected Exception _applicationException;
|
||||
private BadHttpRequestException _requestRejectedException;
|
||||
|
|
@ -351,6 +355,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
RequestHeaders = HttpRequestHeaders;
|
||||
ResponseHeaders = HttpResponseHeaders;
|
||||
|
||||
_isLeasedMemoryInvalid = true;
|
||||
_hasAdvanced = false;
|
||||
_canWriteResponseBody = true;
|
||||
|
||||
if (_scheme == null)
|
||||
{
|
||||
var tlsFeature = ConnectionFeatures?[typeof(ITlsConnectionFeature)];
|
||||
|
|
@ -380,6 +388,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
Output?.Reset();
|
||||
|
||||
_requestHeadersParsed = 0;
|
||||
|
||||
_responseBytesWritten = 0;
|
||||
|
|
@ -921,6 +931,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
return;
|
||||
}
|
||||
|
||||
_isLeasedMemoryInvalid = true;
|
||||
|
||||
_requestProcessingStatus = RequestProcessingStatus.HeadersCommitted;
|
||||
|
||||
var responseHeaders = CreateResponseHeaders(appCompleted);
|
||||
|
|
@ -1066,7 +1078,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
_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.
|
||||
if (CanAutoSetContentLengthZeroResponseHeader())
|
||||
|
|
@ -1268,6 +1280,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
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)
|
||||
{
|
||||
VerifyAndUpdateWrite(bytes);
|
||||
|
|
@ -1276,7 +1303,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
else
|
||||
{
|
||||
HandleNonBodyResponseWrite();
|
||||
|
||||
// For HEAD requests, we still use the number of bytes written for logging
|
||||
// how many bytes were written.
|
||||
VerifyAndUpdateWrite(bytes);
|
||||
|
|
@ -1285,27 +1311,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
public Memory<byte> GetMemory(int sizeHint = 0)
|
||||
{
|
||||
ThrowIfResponseNotStarted();
|
||||
|
||||
_isLeasedMemoryInvalid = false;
|
||||
return Output.GetMemory(sizeHint);
|
||||
}
|
||||
|
||||
public Span<byte> GetSpan(int sizeHint = 0)
|
||||
{
|
||||
ThrowIfResponseNotStarted();
|
||||
|
||||
_isLeasedMemoryInvalid = false;
|
||||
return Output.GetSpan(sizeHint);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
private void ThrowIfResponseNotStarted()
|
||||
{
|
||||
if (!HasResponseStarted)
|
||||
{
|
||||
throw new InvalidOperationException(CoreStrings.StartAsyncBeforeGetMemory);
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<FlushResult> FlushPipeAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!HasResponseStarted)
|
||||
|
|
@ -1338,6 +1353,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
ApplicationAbort();
|
||||
}
|
||||
}
|
||||
|
||||
Output.Complete();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,5 +25,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
void Complete();
|
||||
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);
|
||||
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
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
}
|
||||
|
||||
private async ValueTask<FlushResult> ProcessDataWrites()
|
||||
{
|
||||
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]
|
||||
public async Task ChunksWithGetMemoryBeforeFirstFlushStillFlushes()
|
||||
{
|
||||
|
|
@ -429,7 +475,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
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);
|
||||
|
|
@ -476,7 +522,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
await response.StartAsync();
|
||||
|
||||
var memory = response.BodyWriter.GetMemory();
|
||||
length.Value = memory.Length;
|
||||
|
|
@ -524,7 +569,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChunksWithGetMemoryWithInitialFlushWorks()
|
||||
public async Task ChunksWithGetMemoryAndStartAsyncWithInitialFlushWorks()
|
||||
{
|
||||
var length = new IntAsRef();
|
||||
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]
|
||||
public async Task ChunkGetMemoryMultipleAdvance()
|
||||
{
|
||||
|
|
@ -633,6 +737,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
await response.StartAsync();
|
||||
|
||||
// To avoid using span in an async method
|
||||
void NonAsyncMethod()
|
||||
|
|
@ -647,8 +752,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
response.BodyWriter.Advance(6);
|
||||
}
|
||||
|
||||
await response.StartAsync();
|
||||
|
||||
NonAsyncMethod();
|
||||
}, testContext))
|
||||
{
|
||||
|
|
@ -687,6 +790,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
await response.StartAsync();
|
||||
|
||||
var memory = response.BodyWriter.GetMemory(4096);
|
||||
|
||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
||||
fisrtPartOfResponse.CopyTo(memory);
|
||||
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]
|
||||
public async Task GetMemoryWithSizeHint()
|
||||
{
|
||||
|
|
@ -758,10 +904,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(15)]
|
||||
[InlineData(255)]
|
||||
public async Task ChunkGetMemoryWithSmallerSizesWork(int writeSize)
|
||||
[Fact]
|
||||
public async Task GetMemoryWithSizeHintWithoutStartAsync()
|
||||
{
|
||||
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);
|
||||
|
||||
|
|
@ -769,12 +952,53 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
{
|
||||
var response = httpContext.Response;
|
||||
|
||||
await response.StartAsync();
|
||||
|
||||
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))
|
||||
{
|
||||
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))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
|
|
@ -806,7 +1030,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
await response.StartAsync();
|
||||
|
||||
var memory = response.BodyWriter.GetMemory(4096);
|
||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
|
||||
fisrtPartOfResponse.CopyTo(memory);
|
||||
|
|
|
|||
|
|
@ -2625,6 +2625,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
[Fact]
|
||||
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[]
|
||||
{
|
||||
|
|
@ -2682,7 +2728,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
var response = httpContext.Response;
|
||||
await response.StartAsync();
|
||||
|
||||
var memory = response.BodyWriter.GetMemory();
|
||||
Assert.Equal(4096, 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;
|
||||
|
||||
await response.StartAsync();
|
||||
|
||||
var memory = response.BodyWriter.GetMemory(4096);
|
||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
|
||||
|
|
@ -2960,6 +3004,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
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]
|
||||
public async Task WriteAsync_BothPipeAndStreamWorks()
|
||||
{
|
||||
|
|
@ -3037,7 +3124,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
var response = httpContext.Response;
|
||||
response.ContentLength = 12;
|
||||
await response.StartAsync();
|
||||
await Task.CompletedTask;
|
||||
|
||||
void NonAsyncMethod()
|
||||
{
|
||||
|
|
@ -3084,11 +3171,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
};
|
||||
await InitializeConnectionAsync(async httpContext =>
|
||||
await InitializeConnectionAsync(httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
response.ContentLength = 12;
|
||||
await response.StartAsync();
|
||||
|
||||
var memory = response.BodyWriter.GetMemory(4096);
|
||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
||||
|
|
@ -3098,6 +3184,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var secondPartOfResponse = Encoding.ASCII.GetBytes("World!");
|
||||
secondPartOfResponse.CopyTo(memory.Slice(6));
|
||||
response.BodyWriter.Advance(6);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
|
|
@ -3174,8 +3261,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
var response = httpContext.Response;
|
||||
response.ContentLength = 54;
|
||||
await response.StartAsync();
|
||||
var memory = response.BodyWriter.GetMemory(4096);
|
||||
|
||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
|
||||
fisrtPartOfResponse.CopyTo(memory);
|
||||
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]
|
||||
public async Task AdvanceWithTooLargeOfAValueThrowInvalidOperationException()
|
||||
{
|
||||
var testContext = new TestServiceContext(LoggerFactory);
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
using (var server = new TestServer(httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
|
||||
await response.StartAsync();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => response.BodyWriter.Advance(1));
|
||||
return Task.CompletedTask;
|
||||
}, testContext))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
|
|
@ -3275,60 +3306,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetMemoryBeforeStartAsyncThrows()
|
||||
public async Task ContentLengthWithoutStartAsyncWithGetSpanWorks()
|
||||
{
|
||||
var testContext = new TestServiceContext(LoggerFactory);
|
||||
|
||||
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;
|
||||
response.ContentLength = 12;
|
||||
await response.StartAsync();
|
||||
|
||||
void NonAsyncMethod()
|
||||
{
|
||||
var span = response.BodyWriter.GetSpan(4096);
|
||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
||||
fisrtPartOfResponse.CopyTo(span);
|
||||
response.BodyWriter.Advance(6);
|
||||
|
||||
var secondPartOfResponse = Encoding.ASCII.GetBytes("World!");
|
||||
secondPartOfResponse.CopyTo(span.Slice(6));
|
||||
response.BodyWriter.Advance(6);
|
||||
}
|
||||
|
||||
NonAsyncMethod();
|
||||
var span = response.BodyWriter.GetSpan(4096);
|
||||
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
|
||||
fisrtPartOfResponse.CopyTo(span);
|
||||
response.BodyWriter.Advance(6);
|
||||
|
||||
var secondPartOfResponse = Encoding.ASCII.GetBytes("World!");
|
||||
secondPartOfResponse.CopyTo(span.Slice(6));
|
||||
response.BodyWriter.Advance(6);
|
||||
return Task.CompletedTask;
|
||||
}, testContext))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
|
|
@ -3359,6 +3354,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
{
|
||||
var response = httpContext.Response;
|
||||
response.ContentLength = 12;
|
||||
|
||||
await response.StartAsync();
|
||||
|
||||
var memory = response.BodyWriter.GetMemory(4096);
|
||||
|
|
@ -3461,7 +3457,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResponseBodyPipeCompleteWithoutExceptionDoesNotThrow()
|
||||
public async Task ResponseBodyWriterCompleteWithoutExceptionDoesNotThrow()
|
||||
{
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
|
|
@ -3488,7 +3484,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResponseBodyPipeCompleteWithoutExceptionWritesDoNotThrow()
|
||||
public async Task ResponseBodyWriterCompleteWithoutExceptionWritesDoNotThrow()
|
||||
{
|
||||
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]
|
||||
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]
|
||||
public async Task ResponseCompleteGetMemoryAdvanceInLoopDoesNotThrow()
|
||||
{
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
await httpContext.Response.StartAsync();
|
||||
|
||||
httpContext.Response.BodyWriter.Complete();
|
||||
for (var i = 0; i < 5; i++)
|
||||
|
|
@ -3653,12 +3959,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
{
|
||||
var memoryStream = new MemoryStream();
|
||||
httpContext.Response.Body = memoryStream;
|
||||
var bodyPipe1 = httpContext.Response.BodyWriter;
|
||||
var BodyWriter1 = httpContext.Response.BodyWriter;
|
||||
|
||||
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;
|
||||
}, new TestServiceContext(LoggerFactory)))
|
||||
{
|
||||
|
|
@ -3715,7 +4021,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResponseSetPipeAndBodyPipeIsWrapped()
|
||||
public async Task ResponseSetPipeAndBodyWriterIsWrapped()
|
||||
{
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
|
|
@ -3746,7 +4052,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResponseWriteToBodyPipeAndStreamAllBlocksDisposed()
|
||||
public async Task ResponseWriteToBodyWriterAndStreamAllBlocksDisposed()
|
||||
{
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue