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 System.Threading.Tasks.Task FlushAsync() { throw null; }
|
||||||
public override void Write(char value) { }
|
public override void Write(char value) { }
|
||||||
public override void Write(char[] values, int index, int count) { }
|
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 void Write(string value) { }
|
||||||
public override System.Threading.Tasks.Task WriteAsync(char value) { throw null; }
|
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(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 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)]
|
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||||
public partial struct KeyValueAccumulator
|
public partial struct KeyValueAccumulator
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.WebUtilities
|
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)
|
public override void Write(string value)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
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)
|
public override Task WriteAsync(char value)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
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
|
// We want to flush the stream when Flush/FlushAsync is explicitly
|
||||||
// called by the user (example: from a Razor view).
|
// called by the user (example: from a Razor view).
|
||||||
|
|
||||||
|
|
@ -423,6 +548,19 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
count -= remaining;
|
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)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private static Task GetObjectDisposedTask()
|
private static Task GetObjectDisposedTask()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -253,6 +253,62 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
Assert.Equal(byteLength, stream.Length);
|
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]
|
[Theory]
|
||||||
[InlineData(1023)]
|
[InlineData(1023)]
|
||||||
[InlineData(1024)]
|
[InlineData(1024)]
|
||||||
|
|
@ -298,6 +354,102 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
Assert.Equal(byteLength, stream.Length);
|
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]
|
[Theory]
|
||||||
[InlineData("你好世界", "utf-16")]
|
[InlineData("你好世界", "utf-16")]
|
||||||
[InlineData("హలో ప్రపంచ", "iso-8859-1")]
|
[InlineData("హలో ప్రపంచ", "iso-8859-1")]
|
||||||
|
|
@ -539,11 +691,15 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
{
|
{
|
||||||
httpResponseStreamWriter.Write(new char[] { 'a', 'b' }, 0, 1);
|
httpResponseStreamWriter.Write(new char[] { 'a', 'b' }, 0, 1);
|
||||||
})};
|
})};
|
||||||
|
|
||||||
yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
|
yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
|
||||||
{
|
{
|
||||||
httpResponseStreamWriter.Write("hello");
|
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) =>
|
yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
|
||||||
{
|
{
|
||||||
httpResponseStreamWriter.Flush();
|
httpResponseStreamWriter.Flush();
|
||||||
|
|
@ -560,11 +716,19 @@ namespace Microsoft.AspNetCore.WebUtilities
|
||||||
{
|
{
|
||||||
await httpResponseStreamWriter.WriteAsync(new char[] { 'a', 'b' }, 0, 1);
|
await httpResponseStreamWriter.WriteAsync(new char[] { 'a', 'b' }, 0, 1);
|
||||||
})};
|
})};
|
||||||
|
|
||||||
yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
|
yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
|
||||||
{
|
{
|
||||||
await httpResponseStreamWriter.WriteAsync("hello");
|
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) =>
|
yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
|
||||||
{
|
{
|
||||||
await httpResponseStreamWriter.FlushAsync();
|
await httpResponseStreamWriter.FlushAsync();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue