Added Write/WriteLine ReadOnlySpan/ReadOnlyMemory overrides to HttpResponseStreamWriter (#18451)
This commit is contained in:
parent
bc6fb44840
commit
6e0a4fcfdd
|
|
@ -161,10 +161,14 @@ namespace Microsoft.AspNetCore.WebUtilities
|
|||
public override System.Threading.Tasks.Task FlushAsync() { throw null; }
|
||||
public override void Write(char value) { }
|
||||
public override void Write(char[] values, int index, int count) { }
|
||||
public override void Write(System.ReadOnlySpan<char> value) { }
|
||||
public override void Write(string value) { }
|
||||
public override System.Threading.Tasks.Task WriteAsync(char value) { throw null; }
|
||||
public override System.Threading.Tasks.Task WriteAsync(char[] values, int index, int count) { throw null; }
|
||||
public override System.Threading.Tasks.Task WriteAsync(System.ReadOnlyMemory<char> value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
||||
public override System.Threading.Tasks.Task WriteAsync(string value) { throw null; }
|
||||
public override void WriteLine(System.ReadOnlySpan<char> value) { }
|
||||
public override System.Threading.Tasks.Task WriteLineAsync(System.ReadOnlyMemory<char> value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public partial struct KeyValueAccumulator
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebUtilities
|
||||
|
|
@ -127,6 +128,30 @@ namespace Microsoft.AspNetCore.WebUtilities
|
|||
}
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<char> value)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
|
||||
}
|
||||
|
||||
var written = 0;
|
||||
while (written < value.Length)
|
||||
{
|
||||
if (_charBufferCount == _charBufferSize)
|
||||
{
|
||||
FlushInternal(flushEncoder: false);
|
||||
}
|
||||
|
||||
written = CopyToCharBuffer(value);
|
||||
|
||||
if (written < value.Length)
|
||||
{
|
||||
value = value.Slice(written);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override void Write(string value)
|
||||
{
|
||||
if (_disposed)
|
||||
|
|
@ -152,6 +177,17 @@ namespace Microsoft.AspNetCore.WebUtilities
|
|||
}
|
||||
}
|
||||
|
||||
public override void WriteLine(ReadOnlySpan<char> value)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
|
||||
}
|
||||
|
||||
Write(value);
|
||||
Write(NewLine);
|
||||
}
|
||||
|
||||
public override Task WriteAsync(char value)
|
||||
{
|
||||
if (_disposed)
|
||||
|
|
@ -268,6 +304,95 @@ namespace Microsoft.AspNetCore.WebUtilities
|
|||
}
|
||||
}
|
||||
|
||||
public override Task WriteAsync(ReadOnlyMemory<char> value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return GetObjectDisposedTask();
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
if (value.IsEmpty)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var remaining = _charBufferSize - _charBufferCount;
|
||||
if (remaining >= value.Length)
|
||||
{
|
||||
// Enough room in buffer, no need to go async
|
||||
CopyToCharBuffer(value.Span);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
return WriteAsyncAwaited(value);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteAsyncAwaited(ReadOnlyMemory<char> value)
|
||||
{
|
||||
Debug.Assert(value.Length > 0);
|
||||
Debug.Assert(_charBufferSize - _charBufferCount < value.Length);
|
||||
|
||||
int written = 0;
|
||||
while (written < value.Length)
|
||||
{
|
||||
if (_charBufferCount == _charBufferSize)
|
||||
{
|
||||
await FlushInternalAsync(flushEncoder: false);
|
||||
}
|
||||
|
||||
written = CopyToCharBuffer(value.Span);
|
||||
|
||||
if (written < value.Length)
|
||||
{
|
||||
value = value.Slice(written);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override Task WriteLineAsync(ReadOnlyMemory<char> value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return GetObjectDisposedTask();
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
if (value.IsEmpty && NewLine.Length == 0)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var remaining = _charBufferSize - _charBufferCount;
|
||||
if (remaining >= value.Length + NewLine.Length)
|
||||
{
|
||||
// Enough room in buffer, no need to go async
|
||||
CopyToCharBuffer(value.Span);
|
||||
CopyToCharBuffer(NewLine);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
return WriteLineAsyncAwaited(value);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteLineAsyncAwaited(ReadOnlyMemory<char> value)
|
||||
{
|
||||
await WriteAsyncAwaited(value);
|
||||
await WriteAsyncAwaited(NewLine);
|
||||
}
|
||||
|
||||
// We want to flush the stream when Flush/FlushAsync is explicitly
|
||||
// called by the user (example: from a Razor view).
|
||||
|
||||
|
|
@ -423,6 +548,19 @@ namespace Microsoft.AspNetCore.WebUtilities
|
|||
count -= remaining;
|
||||
}
|
||||
|
||||
private int CopyToCharBuffer(ReadOnlySpan<char> value)
|
||||
{
|
||||
var remaining = Math.Min(_charBufferSize - _charBufferCount, value.Length);
|
||||
|
||||
var source = value.Slice(0, remaining);
|
||||
var destination = new Span<char>(_charBuffer, _charBufferCount, remaining);
|
||||
source.CopyTo(destination);
|
||||
|
||||
_charBufferCount += remaining;
|
||||
|
||||
return remaining;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static Task GetObjectDisposedTask()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -253,6 +253,62 @@ namespace Microsoft.AspNetCore.WebUtilities
|
|||
Assert.Equal(byteLength, stream.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1023)]
|
||||
[InlineData(1024)]
|
||||
[InlineData(1050)]
|
||||
[InlineData(2048)]
|
||||
public void WriteReadOnlySpanChar_WritesToStream(int byteLength)
|
||||
{
|
||||
// Arrange
|
||||
var stream = new TestMemoryStream();
|
||||
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||
|
||||
// Act
|
||||
using (writer)
|
||||
{
|
||||
var array = new string('a', byteLength).ToCharArray();
|
||||
var span = new ReadOnlySpan<char>(array);
|
||||
writer.Write(span);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal(byteLength, stream.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1022, "\n")]
|
||||
[InlineData(1023, "\n")]
|
||||
[InlineData(1024, "\n")]
|
||||
[InlineData(1050, "\n")]
|
||||
[InlineData(2047, "\n")]
|
||||
[InlineData(2048, "\n")]
|
||||
[InlineData(1021, "\r\n")]
|
||||
[InlineData(1022, "\r\n")]
|
||||
[InlineData(1023, "\r\n")]
|
||||
[InlineData(1024, "\r\n")]
|
||||
[InlineData(1050, "\r\n")]
|
||||
[InlineData(2046, "\r\n")]
|
||||
[InlineData(2048, "\r\n")]
|
||||
public void WriteLineReadOnlySpanChar_WritesToStream(int byteLength, string newLine)
|
||||
{
|
||||
// Arrange
|
||||
var stream = new TestMemoryStream();
|
||||
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||
|
||||
writer.NewLine = newLine;
|
||||
// Act
|
||||
using (writer)
|
||||
{
|
||||
var array = new string('a', byteLength).ToCharArray();
|
||||
var span = new ReadOnlySpan<char>(array);
|
||||
writer.WriteLine(span);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal(byteLength + newLine.Length, stream.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1023)]
|
||||
[InlineData(1024)]
|
||||
|
|
@ -298,6 +354,102 @@ namespace Microsoft.AspNetCore.WebUtilities
|
|||
Assert.Equal(byteLength, stream.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(1023)]
|
||||
[InlineData(1024)]
|
||||
[InlineData(1050)]
|
||||
[InlineData(2048)]
|
||||
public async Task WriteReadOnlyMemoryAsync_WritesToStream(int byteLength)
|
||||
{
|
||||
// Arrange
|
||||
var stream = new TestMemoryStream();
|
||||
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||
|
||||
// Act
|
||||
using (writer)
|
||||
{
|
||||
var array = new string('a', byteLength).ToCharArray();
|
||||
var memory = new ReadOnlyMemory<char>(array);
|
||||
await writer.WriteAsync(memory);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal(byteLength, stream.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteReadOnlyMemoryAsync_TokenCanceled_ReturnsCanceledTask()
|
||||
{
|
||||
// Arrange
|
||||
var stream = new TestMemoryStream();
|
||||
using var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||
var memory = new ReadOnlyMemory<char>(new char[] { 'a' });
|
||||
var cancellationToken = new CancellationToken(true);
|
||||
|
||||
// Act
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await writer.WriteAsync(memory, cancellationToken));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, stream.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 1)]
|
||||
[InlineData(1022, 1)]
|
||||
[InlineData(1023, 1)]
|
||||
[InlineData(1024, 1)]
|
||||
[InlineData(1050, 1)]
|
||||
[InlineData(2047, 1)]
|
||||
[InlineData(2048, 1)]
|
||||
[InlineData(1021, 2)]
|
||||
[InlineData(1022, 2)]
|
||||
[InlineData(1023, 2)]
|
||||
[InlineData(1024, 2)]
|
||||
[InlineData(1024, 1023)]
|
||||
[InlineData(1024, 1024)]
|
||||
[InlineData(1024, 1050)]
|
||||
[InlineData(1050, 2)]
|
||||
[InlineData(2046, 2)]
|
||||
[InlineData(2048, 2)]
|
||||
public async Task WriteLineReadOnlyMemoryAsync_WritesToStream(int byteLength, int newLineLength)
|
||||
{
|
||||
// Arrange
|
||||
var stream = new TestMemoryStream();
|
||||
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||
writer.NewLine = new string('\n', newLineLength);
|
||||
|
||||
// Act
|
||||
using (writer)
|
||||
{
|
||||
var array = new string('a', byteLength).ToCharArray();
|
||||
var memory = new ReadOnlyMemory<char>(array);
|
||||
await writer.WriteLineAsync(memory);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal(byteLength + newLineLength, stream.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteLineReadOnlyMemoryAsync_TokenCanceled_ReturnsCanceledTask()
|
||||
{
|
||||
// Arrange
|
||||
var stream = new TestMemoryStream();
|
||||
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||
var memory = new ReadOnlyMemory<char>(new char[] { 'a' });
|
||||
var cancellationToken = new CancellationToken(true);
|
||||
|
||||
// Act
|
||||
using (writer)
|
||||
{
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await writer.WriteLineAsync(memory, cancellationToken));
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, stream.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("你好世界", "utf-16")]
|
||||
[InlineData("హలో ప్రపంచ", "iso-8859-1")]
|
||||
|
|
@ -539,11 +691,15 @@ namespace Microsoft.AspNetCore.WebUtilities
|
|||
{
|
||||
httpResponseStreamWriter.Write(new char[] { 'a', 'b' }, 0, 1);
|
||||
})};
|
||||
|
||||
yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
|
||||
{
|
||||
httpResponseStreamWriter.Write("hello");
|
||||
})};
|
||||
yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
|
||||
{
|
||||
httpResponseStreamWriter.Write(new ReadOnlySpan<char>(new char[] { 'a', 'b' }));
|
||||
})};
|
||||
|
||||
yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
|
||||
{
|
||||
httpResponseStreamWriter.Flush();
|
||||
|
|
@ -560,11 +716,19 @@ namespace Microsoft.AspNetCore.WebUtilities
|
|||
{
|
||||
await httpResponseStreamWriter.WriteAsync(new char[] { 'a', 'b' }, 0, 1);
|
||||
})};
|
||||
|
||||
yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
|
||||
{
|
||||
await httpResponseStreamWriter.WriteAsync("hello");
|
||||
})};
|
||||
yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
|
||||
{
|
||||
await httpResponseStreamWriter.WriteAsync(new ReadOnlyMemory<char>(new char[] { 'a', 'b' }));
|
||||
})};
|
||||
yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
|
||||
{
|
||||
await httpResponseStreamWriter.WriteLineAsync(new ReadOnlyMemory<char>(new char[] { 'a', 'b' }));
|
||||
})};
|
||||
|
||||
yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
|
||||
{
|
||||
await httpResponseStreamWriter.FlushAsync();
|
||||
|
|
|
|||
Loading…
Reference in New Issue