* Remove HttpRespomseStreamWriter

This commit is contained in:
ryanbrandenburg 2016-01-15 14:17:48 -08:00
parent 3cac63a9cf
commit 78e7179221
12 changed files with 8 additions and 1532 deletions

View File

@ -1,396 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Core;
namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// Writes to the <see cref="Stream"/> using the supplied <see cref="Encoding"/>.
/// It does not write the BOM and also does not close the stream.
/// </summary>
public class HttpResponseStreamWriter : TextWriter
{
private const int MinBufferSize = 128;
/// <summary>
/// Default buffer size.
/// </summary>
public const int DefaultBufferSize = 1024;
private Stream _stream;
private readonly Encoder _encoder;
private readonly ArrayPool<byte> _bytePool;
private readonly ArrayPool<char> _charPool;
private readonly int _charBufferSize;
private byte[] _byteBuffer;
private char[] _charBuffer;
private int _charBufferCount;
public HttpResponseStreamWriter(Stream stream, Encoding encoding)
: this(stream, encoding, DefaultBufferSize)
{
}
public HttpResponseStreamWriter(Stream stream, Encoding encoding, int bufferSize)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
if (!stream.CanWrite)
{
throw new ArgumentException(Resources.HttpResponseStreamWriter_StreamNotWritable, nameof(stream));
}
if (encoding == null)
{
throw new ArgumentNullException(nameof(encoding));
}
_stream = stream;
Encoding = encoding;
_charBufferSize = bufferSize;
if (bufferSize < MinBufferSize)
{
bufferSize = MinBufferSize;
}
_encoder = encoding.GetEncoder();
_byteBuffer = new byte[encoding.GetMaxByteCount(bufferSize)];
_charBuffer = new char[bufferSize];
}
public HttpResponseStreamWriter(
Stream stream,
Encoding encoding,
int bufferSize,
ArrayPool<byte> bytePool,
ArrayPool<char> charPool)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
if (!stream.CanWrite)
{
throw new ArgumentException(Resources.HttpResponseStreamWriter_StreamNotWritable, nameof(stream));
}
if (encoding == null)
{
throw new ArgumentNullException(nameof(encoding));
}
if (bytePool == null)
{
throw new ArgumentNullException(nameof(bytePool));
}
if (charPool == null)
{
throw new ArgumentNullException(nameof(charPool));
}
_stream = stream;
Encoding = encoding;
_charBufferSize = bufferSize;
_encoder = encoding.GetEncoder();
_bytePool = bytePool;
_charPool = charPool;
_charBuffer = charPool.Rent(bufferSize);
try
{
var requiredLength = encoding.GetMaxByteCount(bufferSize);
_byteBuffer = bytePool.Rent(requiredLength);
}
catch
{
charPool.Return(_charBuffer);
_charBuffer = null;
if (_byteBuffer != null)
{
bytePool.Return(_byteBuffer);
_byteBuffer = null;
}
throw;
}
}
public override Encoding Encoding { get; }
public override void Write(char value)
{
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (_charBufferCount == _charBufferSize)
{
FlushInternal(flushEncoder: false);
}
_charBuffer[_charBufferCount] = value;
_charBufferCount++;
}
public override void Write(char[] values, int index, int count)
{
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (values == null)
{
return;
}
while (count > 0)
{
if (_charBufferCount == _charBufferSize)
{
FlushInternal(flushEncoder: false);
}
CopyToCharBuffer(values, ref index, ref count);
}
}
public override void Write(string value)
{
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (value == null)
{
return;
}
var count = value.Length;
var index = 0;
while (count > 0)
{
if (_charBufferCount == _charBufferSize)
{
FlushInternal(flushEncoder: false);
}
CopyToCharBuffer(value, ref index, ref count);
}
}
public override async Task WriteAsync(char value)
{
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (_charBufferCount == _charBufferSize)
{
await FlushInternalAsync(flushEncoder: false);
}
_charBuffer[_charBufferCount] = value;
_charBufferCount++;
}
public override async Task WriteAsync(char[] values, int index, int count)
{
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (values == null)
{
return;
}
while (count > 0)
{
if (_charBufferCount == _charBufferSize)
{
await FlushInternalAsync(flushEncoder: false);
}
CopyToCharBuffer(values, ref index, ref count);
}
}
public override async Task WriteAsync(string value)
{
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (value == null)
{
return;
}
var count = value.Length;
var index = 0;
while (count > 0)
{
if (_charBufferCount == _charBufferSize)
{
await FlushInternalAsync(flushEncoder: false);
}
CopyToCharBuffer(value, ref index, ref count);
}
}
// We want to flush the stream when Flush/FlushAsync is explicitly
// called by the user (example: from a Razor view).
public override void Flush()
{
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
FlushInternal(flushEncoder: true);
}
public override Task FlushAsync()
{
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
return FlushInternalAsync(flushEncoder: true);
}
protected override void Dispose(bool disposing)
{
if (disposing && _stream != null)
{
try
{
FlushInternal(flushEncoder: true);
}
finally
{
_stream = null;
if (_bytePool != null)
{
_bytePool.Return(_byteBuffer);
_byteBuffer = null;
}
if (_charPool != null)
{
_charPool.Return(_charBuffer);
_charBuffer = null;
}
}
}
}
// Note: our FlushInternal method does NOT flush the underlying stream. This would result in
// chunking.
private void FlushInternal(bool flushEncoder)
{
if (_charBufferCount == 0)
{
return;
}
var count = _encoder.GetBytes(
_charBuffer,
0,
_charBufferCount,
_byteBuffer,
0,
flush: flushEncoder);
if (count > 0)
{
_stream.Write(_byteBuffer, 0, count);
}
_charBufferCount = 0;
}
// Note: our FlushInternalAsync method does NOT flush the underlying stream. This would result in
// chunking.
private async Task FlushInternalAsync(bool flushEncoder)
{
if (_charBufferCount == 0)
{
return;
}
var count = _encoder.GetBytes(
_charBuffer,
0,
_charBufferCount,
_byteBuffer,
0,
flush: flushEncoder);
if (count > 0)
{
await _stream.WriteAsync(_byteBuffer, 0, count);
}
_charBufferCount = 0;
}
private void CopyToCharBuffer(string value, ref int index, ref int count)
{
var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
value.CopyTo(
sourceIndex: index,
destination: _charBuffer,
destinationIndex: _charBufferCount,
count: remaining);
_charBufferCount += remaining;
index += remaining;
count -= remaining;
}
private void CopyToCharBuffer(char[] values, ref int index, ref int count)
{
var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
Buffer.BlockCopy(
src: values,
srcOffset: index * sizeof(char),
dst: _charBuffer,
dstOffset: _charBufferCount * sizeof(char),
count: remaining * sizeof(char));
_charBufferCount += remaining;
index += remaining;
count -= remaining;
}
}
}

View File

@ -1,438 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Core;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
public class HttpRequestStreamReader : TextReader
{
private const int DefaultBufferSize = 1024;
private const int MinBufferSize = 128;
private const int MaxSharedBuilderCapacity = 360; // also the max capacity used in StringBuilderCache
private Stream _stream;
private readonly Encoding _encoding;
private readonly Decoder _decoder;
private readonly ArrayPool<byte> _bytePool;
private readonly ArrayPool<char> _charPool;
private readonly int _byteBufferSize;
private byte[] _byteBuffer;
private char[] _charBuffer;
private int _charBufferIndex;
private int _charsRead;
private int _bytesRead;
private bool _isBlocked;
public HttpRequestStreamReader(Stream stream, Encoding encoding)
: this(stream, encoding, DefaultBufferSize)
{
}
public HttpRequestStreamReader(Stream stream, Encoding encoding, int bufferSize)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
if (!stream.CanRead)
{
throw new ArgumentException(Resources.HttpRequestStreamReader_StreamNotReadable, nameof(stream));
}
if (encoding == null)
{
throw new ArgumentNullException(nameof(encoding));
}
_stream = stream;
_encoding = encoding;
_decoder = encoding.GetDecoder();
if (bufferSize < MinBufferSize)
{
bufferSize = MinBufferSize;
}
_byteBufferSize = bufferSize;
_byteBuffer = new byte[bufferSize];
var maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
_charBuffer = new char[maxCharsPerBuffer];
}
public HttpRequestStreamReader(
Stream stream,
Encoding encoding,
int bufferSize,
ArrayPool<byte> bytePool,
ArrayPool<char> charPool)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
if (!stream.CanRead)
{
throw new ArgumentException(Resources.HttpRequestStreamReader_StreamNotReadable, nameof(stream));
}
if (encoding == null)
{
throw new ArgumentNullException(nameof(encoding));
}
if (bytePool == null)
{
throw new ArgumentNullException(nameof(bytePool));
}
if (charPool == null)
{
throw new ArgumentNullException(nameof(charPool));
}
if (bufferSize <= 0)
{
throw new ArgumentOutOfRangeException(nameof(bufferSize));
}
_stream = stream;
_encoding = encoding;
_byteBufferSize = bufferSize;
_bytePool = bytePool;
_charPool = charPool;
_decoder = encoding.GetDecoder();
_byteBuffer = _bytePool.Rent(bufferSize);
try
{
var requiredLength = encoding.GetMaxCharCount(bufferSize);
_charBuffer = _charPool.Rent(requiredLength);
}
catch
{
_bytePool.Return(_byteBuffer);
_byteBuffer = null;
if (_charBuffer != null)
{
_charPool.Return(_charBuffer);
_charBuffer = null;
}
}
}
#if dnx451
public override void Close()
{
Dispose(true);
}
#endif
protected override void Dispose(bool disposing)
{
if (disposing && _stream != null)
{
_stream = null;
if (_bytePool != null)
{
_bytePool.Return(_byteBuffer);
_byteBuffer = null;
}
if (_charPool != null)
{
_charPool.Return(_charBuffer);
_charBuffer = null;
}
}
base.Dispose(disposing);
}
public override int Peek()
{
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (_charBufferIndex == _charsRead)
{
if (_isBlocked || ReadIntoBuffer() == 0)
{
return -1;
}
}
return _charBuffer[_charBufferIndex];
}
public override int Read()
{
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (_charBufferIndex == _charsRead)
{
if (ReadIntoBuffer() == 0)
{
return -1;
}
}
return _charBuffer[_charBufferIndex++];
}
public override int Read(char[] buffer, int index, int count)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (index < 0)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
if (count < 0 || index + count > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
var charsRead = 0;
while (count > 0)
{
var charsRemaining = _charsRead - _charBufferIndex;
if (charsRemaining == 0)
{
charsRemaining = ReadIntoBuffer();
}
if (charsRemaining == 0)
{
break; // We're at EOF
}
if (charsRemaining > count)
{
charsRemaining = count;
}
Buffer.BlockCopy(
_charBuffer,
_charBufferIndex * 2,
buffer,
(index + charsRead) * 2,
charsRemaining * 2);
_charBufferIndex += charsRemaining;
charsRead += charsRemaining;
count -= charsRemaining;
// If we got back fewer chars than we asked for, then it's likely the underlying stream is blocked.
// Send the data back to the caller so they can process it.
if (_isBlocked)
{
break;
}
}
return charsRead;
}
public override async Task<int> ReadAsync(char[] buffer, int index, int count)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (index < 0)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
if (count < 0 || index + count > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (_charBufferIndex == _charsRead && await ReadIntoBufferAsync() == 0)
{
return 0;
}
var charsRead = 0;
while (count > 0)
{
// n is the characters available in _charBuffer
var n = _charsRead - _charBufferIndex;
// charBuffer is empty, let's read from the stream
if (n == 0)
{
_charsRead = 0;
_charBufferIndex = 0;
_bytesRead = 0;
// We loop here so that we read in enough bytes to yield at least 1 char.
// We break out of the loop if the stream is blocked (EOF is reached).
do
{
Debug.Assert(n == 0);
_bytesRead = await _stream.ReadAsync(
_byteBuffer,
0,
_byteBufferSize);
if (_bytesRead == 0) // EOF
{
_isBlocked = true;
break;
}
// _isBlocked == whether we read fewer bytes than we asked for.
_isBlocked = (_bytesRead < _byteBufferSize);
Debug.Assert(n == 0);
_charBufferIndex = 0;
n = _decoder.GetChars(
_byteBuffer,
0,
_bytesRead,
_charBuffer,
0);
Debug.Assert(n > 0);
_charsRead += n; // Number of chars in StreamReader's buffer.
}
while (n == 0);
if (n == 0)
{
break; // We're at EOF
}
}
// Got more chars in charBuffer than the user requested
if (n > count)
{
n = count;
}
Buffer.BlockCopy(
_charBuffer,
_charBufferIndex * 2,
buffer,
(index + charsRead) * 2,
n * 2);
_charBufferIndex += n;
charsRead += n;
count -= n;
// This function shouldn't block for an indefinite amount of time,
// or reading from a network stream won't work right. If we got
// fewer bytes than we requested, then we want to break right here.
if (_isBlocked)
{
break;
}
}
return charsRead;
}
private int ReadIntoBuffer()
{
_charsRead = 0;
_charBufferIndex = 0;
_bytesRead = 0;
do
{
_bytesRead = _stream.Read(_byteBuffer, 0, _byteBufferSize);
if (_bytesRead == 0) // We're at EOF
{
return _charsRead;
}
_isBlocked = (_bytesRead < _byteBufferSize);
_charsRead += _decoder.GetChars(
_byteBuffer,
0,
_bytesRead,
_charBuffer,
_charsRead);
}
while (_charsRead == 0);
return _charsRead;
}
private async Task<int> ReadIntoBufferAsync()
{
_charsRead = 0;
_charBufferIndex = 0;
_bytesRead = 0;
do
{
_bytesRead = await _stream.ReadAsync(
_byteBuffer,
0,
_byteBufferSize).ConfigureAwait(false);
if (_bytesRead == 0)
{
// We're at EOF
return _charsRead;
}
// _isBlocked == whether we read fewer bytes than we asked for.
_isBlocked = (_bytesRead < _byteBufferSize);
_charsRead += _decoder.GetChars(
_byteBuffer,
0,
_bytesRead,
_charBuffer,
_charsRead);
}
while (_charsRead == 0);
return _charsRead;
}
}
}

View File

@ -5,7 +5,7 @@ using System;
using System.Buffers;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.WebUtilities;
namespace Microsoft.AspNetCore.Mvc.Internal
{

View File

@ -5,6 +5,7 @@ using System;
using System.Buffers;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.WebUtilities;
namespace Microsoft.AspNetCore.Mvc.Internal
{

View File

@ -313,12 +313,6 @@
<data name="UrlNotLocal" xml:space="preserve">
<value>The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local.</value>
</data>
<data name="HttpRequestStreamReader_StreamNotReadable" xml:space="preserve">
<value>The stream must support reading.</value>
</data>
<data name="HttpResponseStreamWriter_StreamNotWritable" xml:space="preserve">
<value>The stream must support writing.</value>
</data>
<data name="FormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat" xml:space="preserve">
<value>The argument '{0}' is invalid. Empty or null formats are not supported.</value>
</data>

View File

@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

View File

@ -1,462 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Mvc
{
public class HttpResponseStreamWriterTest
{
[Fact]
public async Task DoesNotWriteBOM()
{
// Arrange
var memoryStream = new MemoryStream();
var encodingWithBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true);
var writer = new HttpResponseStreamWriter(memoryStream, encodingWithBOM);
var expectedData = new byte[] { 97, 98, 99, 100 }; // without BOM
// Act
using (writer)
{
await writer.WriteAsync("abcd");
}
// Assert
Assert.Equal(expectedData, memoryStream.ToArray());
}
#if DNX451
[Fact]
public async Task DoesNotFlush_UnderlyingStream_OnClosingWriter()
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
// Act
await writer.WriteAsync("Hello");
writer.Close();
// Assert
Assert.Equal(0, stream.FlushCallCount);
Assert.Equal(0, stream.FlushAsyncCallCount);
}
#endif
[Fact]
public async Task DoesNotFlush_UnderlyingStream_OnDisposingWriter()
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
// Act
await writer.WriteAsync("Hello");
writer.Dispose();
// Assert
Assert.Equal(0, stream.FlushCallCount);
Assert.Equal(0, stream.FlushAsyncCallCount);
}
#if DNX451
[Fact]
public async Task DoesNotClose_UnderlyingStream_OnDisposingWriter()
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
// Act
await writer.WriteAsync("Hello");
writer.Close();
// Assert
Assert.Equal(0, stream.CloseCallCount);
}
#endif
[Fact]
public async Task DoesNotDispose_UnderlyingStream_OnDisposingWriter()
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
// Act
await writer.WriteAsync("Hello world");
writer.Dispose();
// Assert
Assert.Equal(0, stream.DisposeCallCount);
}
[Theory]
[InlineData(1023)]
[InlineData(1024)]
[InlineData(1050)]
[InlineData(2048)]
public async Task FlushesBuffer_OnClose(int byteLength)
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
await writer.WriteAsync(new string('a', byteLength));
// Act
#if DNX451
writer.Close();
#else
writer.Dispose();
#endif
// Assert
Assert.Equal(0, stream.FlushCallCount);
Assert.Equal(0, stream.FlushAsyncCallCount);
Assert.Equal(byteLength, stream.Length);
}
[Theory]
[InlineData(1023)]
[InlineData(1024)]
[InlineData(1050)]
[InlineData(2048)]
public async Task FlushesBuffer_OnDispose(int byteLength)
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
await writer.WriteAsync(new string('a', byteLength));
// Act
writer.Dispose();
// Assert
Assert.Equal(0, stream.FlushCallCount);
Assert.Equal(0, stream.FlushAsyncCallCount);
Assert.Equal(byteLength, stream.Length);
}
[Fact]
public void NoDataWritten_Flush_DoesNotFlushUnderlyingStream()
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
// Act
writer.Flush();
// Assert
Assert.Equal(0, stream.FlushCallCount);
Assert.Equal(0, stream.Length);
}
[Theory]
[InlineData(1023)]
[InlineData(1024)]
[InlineData(1050)]
[InlineData(2048)]
public void FlushesBuffer_ButNotStream_OnFlush(int byteLength)
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
writer.Write(new string('a', byteLength));
var expectedWriteCount = Math.Ceiling((double)byteLength / HttpResponseStreamWriter.DefaultBufferSize);
// Act
writer.Flush();
// Assert
Assert.Equal(0, stream.FlushCallCount);
Assert.Equal(expectedWriteCount, stream.WriteCallCount);
Assert.Equal(byteLength, stream.Length);
}
[Fact]
public async Task NoDataWritten_FlushAsync_DoesNotFlushUnderlyingStream()
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
// Act
await writer.FlushAsync();
// Assert
Assert.Equal(0, stream.FlushAsyncCallCount);
Assert.Equal(0, stream.Length);
}
[Theory]
[InlineData(1023)]
[InlineData(1024)]
[InlineData(1050)]
[InlineData(2048)]
public async Task FlushesBuffer_ButNotStream_OnFlushAsync(int byteLength)
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
await writer.WriteAsync(new string('a', byteLength));
var expectedWriteCount = Math.Ceiling((double)byteLength / HttpResponseStreamWriter.DefaultBufferSize);
// Act
await writer.FlushAsync();
// Assert
Assert.Equal(0, stream.FlushAsyncCallCount);
Assert.Equal(expectedWriteCount, stream.WriteAsyncCallCount);
Assert.Equal(byteLength, stream.Length);
}
[Theory]
[InlineData(1023)]
[InlineData(1024)]
[InlineData(1050)]
[InlineData(2048)]
public void WriteChar_WritesToStream(int byteLength)
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
// Act
using (writer)
{
for (var i = 0; i < byteLength; i++)
{
writer.Write('a');
}
}
// Assert
Assert.Equal(byteLength, stream.Length);
}
[Theory]
[InlineData(1023)]
[InlineData(1024)]
[InlineData(1050)]
[InlineData(2048)]
public void WriteCharArray_WritesToStream(int byteLength)
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
// Act
using (writer)
{
writer.Write((new string('a', byteLength)).ToCharArray());
}
// Assert
Assert.Equal(byteLength, stream.Length);
}
[Theory]
[InlineData(1023)]
[InlineData(1024)]
[InlineData(1050)]
[InlineData(2048)]
public async Task WriteCharAsync_WritesToStream(int byteLength)
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
// Act
using (writer)
{
for (var i = 0; i < byteLength; i++)
{
await writer.WriteAsync('a');
}
}
// Assert
Assert.Equal(byteLength, stream.Length);
}
[Theory]
[InlineData(1023)]
[InlineData(1024)]
[InlineData(1050)]
[InlineData(2048)]
public async Task WriteCharArrayAsync_WritesToStream(int byteLength)
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
// Act
using (writer)
{
await writer.WriteAsync((new string('a', byteLength)).ToCharArray());
}
// Assert
Assert.Equal(byteLength, stream.Length);
}
[Theory]
[InlineData("你好世界", "utf-16")]
#if !DNXCORE50
// CoreCLR does not like shift_jis as an encoding.
[InlineData("こんにちは世界", "shift_jis")]
#endif
[InlineData("హలో ప్రపంచ", "iso-8859-1")]
[InlineData("வணக்கம் உலக", "utf-32")]
public async Task WritesData_InExpectedEncoding(string data, string encodingName)
{
// Arrange
var encoding = Encoding.GetEncoding(encodingName);
var expectedBytes = encoding.GetBytes(data);
var stream = new MemoryStream();
var writer = new HttpResponseStreamWriter(stream, encoding);
// Act
using (writer)
{
await writer.WriteAsync(data);
}
// Assert
Assert.Equal(expectedBytes, stream.ToArray());
}
[Theory]
[InlineData('ん', 1023, "utf-8")]
[InlineData('ん', 1024, "utf-8")]
[InlineData('ん', 1050, "utf-8")]
[InlineData('你', 1023, "utf-16")]
[InlineData('你', 1024, "utf-16")]
[InlineData('你', 1050, "utf-16")]
#if !DNXCORE50
// CoreCLR does not like shift_jis as an encoding.
[InlineData('こ', 1023, "shift_jis")]
[InlineData('こ', 1024, "shift_jis")]
[InlineData('こ', 1050, "shift_jis")]
#endif
[InlineData('హ', 1023, "iso-8859-1")]
[InlineData('హ', 1024, "iso-8859-1")]
[InlineData('హ', 1050, "iso-8859-1")]
[InlineData('வ', 1023, "utf-32")]
[InlineData('வ', 1024, "utf-32")]
[InlineData('வ', 1050, "utf-32")]
public async Task WritesData_OfDifferentLength_InExpectedEncoding(
char character,
int charCount,
string encodingName)
{
// Arrange
var encoding = Encoding.GetEncoding(encodingName);
string data = new string(character, charCount);
var expectedBytes = encoding.GetBytes(data);
var stream = new MemoryStream();
var writer = new HttpResponseStreamWriter(stream, encoding);
// Act
using (writer)
{
await writer.WriteAsync(data);
}
// Assert
Assert.Equal(expectedBytes, stream.ToArray());
}
// None of the code in HttpResponseStreamWriter differs significantly when using pooled buffers.
//
// This test effectively verifies that things are correctly constructed and disposed. Pooled buffers
// throw on the finalizer thread if not disposed, so that's why it's complicated.
[Fact]
public void HttpResponseStreamWriter_UsingPooledBuffers()
{
// Arrange
var encoding = Encoding.UTF8;
var stream = new MemoryStream();
var expectedBytes = encoding.GetBytes("Hello, World!");
using (var writer = new HttpResponseStreamWriter(
stream,
encoding,
1024,
ArrayPool<byte>.Shared,
ArrayPool<char>.Shared))
{
// Act
writer.Write("Hello, World!");
}
// Assert
Assert.Equal(expectedBytes, stream.ToArray());
}
private class TestMemoryStream : MemoryStream
{
public int FlushCallCount { get; private set; }
public int FlushAsyncCallCount { get; private set; }
public int CloseCallCount { get; private set; }
public int DisposeCallCount { get; private set; }
public int WriteCallCount { get; private set; }
public int WriteAsyncCallCount { get; private set; }
public override void Flush()
{
FlushCallCount++;
base.Flush();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
FlushAsyncCallCount++;
return base.FlushAsync(cancellationToken);
}
public override void Write(byte[] buffer, int offset, int count)
{
WriteCallCount++;
base.Write(buffer, offset, count);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
WriteAsyncCallCount++;
return base.WriteAsync(buffer, offset, count, cancellationToken);
}
#if DNX451
public override void Close()
{
CloseCallCount++;
base.Close();
}
#endif
protected override void Dispose(bool disposing)
{
DisposeCallCount++;
base.Dispose(disposing);
}
}
}
}

View File

@ -1,226 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
public class HttpResponseStreamReaderTest
{
private static readonly char[] CharData = new char[]
{
char.MinValue,
char.MaxValue,
'\t',
' ',
'$',
'@',
'#',
'\0',
'\v',
'\'',
'\u3190',
'\uC3A0',
'A',
'5',
'\r',
'\uFE70',
'-',
';',
'\r',
'\n',
'T',
'3',
'\n',
'K',
'\u00E6',
};
[Fact]
public static async Task ReadToEndAsync()
{
// Arrange
var reader = new HttpRequestStreamReader(GetLargeStream(), Encoding.UTF8);
var result = await reader.ReadToEndAsync();
Assert.Equal(5000, result.Length);
}
[Fact]
public static void TestRead()
{
// Arrange
var reader = CreateReader();
// Act & Assert
for (var i = 0; i < CharData.Length; i++)
{
var tmp = reader.Read();
Assert.Equal((int)CharData[i], tmp);
}
}
[Fact]
public static void TestPeek()
{
// Arrange
var reader = CreateReader();
// Act & Assert
for (var i = 0; i < CharData.Length; i++)
{
var peek = reader.Peek();
Assert.Equal((int)CharData[i], peek);
reader.Read();
}
}
[Fact]
public static void EmptyStream()
{
// Arrange
var reader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8);
var buffer = new char[10];
// Act
var read = reader.Read(buffer, 0, 1);
// Assert
Assert.Equal(0, read);
}
[Fact]
public static void Read_ReadAllCharactersAtOnce()
{
// Arrange
var reader = CreateReader();
var chars = new char[CharData.Length];
// Act
var read = reader.Read(chars, 0, chars.Length);
// Assert
Assert.Equal(chars.Length, read);
for (var i = 0; i < CharData.Length; i++)
{
Assert.Equal(CharData[i], chars[i]);
}
}
[Fact]
public static async Task Read_ReadInTwoChunks()
{
// Arrange
var reader = CreateReader();
var chars = new char[CharData.Length];
// Act
var read = await reader.ReadAsync(chars, 4, 3);
// Assert
Assert.Equal(read, 3);
for (var i = 0; i < 3; i++)
{
Assert.Equal(CharData[i], chars[i + 4]);
}
}
[Fact]
public static void ReadLine_ReadMultipleLines()
{
// Arrange
var reader = CreateReader();
var valueString = new string(CharData);
// Act & Assert
var data = reader.ReadLine();
Assert.Equal(valueString.Substring(0, valueString.IndexOf('\r')), data);
data = reader.ReadLine();
Assert.Equal(valueString.Substring(valueString.IndexOf('\r') + 1, 3), data);
data = reader.ReadLine();
Assert.Equal(valueString.Substring(valueString.IndexOf('\n') + 1, 2), data);
data = reader.ReadLine();
Assert.Equal((valueString.Substring(valueString.LastIndexOf('\n') + 1)), data);
}
[Fact]
public static void ReadLine_ReadWithNoNewlines()
{
// Arrange
var reader = CreateReader();
var valueString = new string(CharData);
var temp = new char[10];
// Act
reader.Read(temp, 0, 1);
var data = reader.ReadLine();
// Assert
Assert.Equal(valueString.Substring(1, valueString.IndexOf('\r') - 1), data);
}
[Fact]
public static async Task ReadLineAsync_MultipleContinuousLines()
{
// Arrange
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write("\n\n\r\r\n");
writer.Flush();
stream.Position = 0;
var reader = new HttpRequestStreamReader(stream, Encoding.UTF8);
// Act & Assert
for (var i = 0; i < 4; i++)
{
var data = await reader.ReadLineAsync();
Assert.Equal(string.Empty, data);
}
var eol = await reader.ReadLineAsync();
Assert.Null(eol);
}
private static HttpRequestStreamReader CreateReader()
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(CharData);
writer.Flush();
stream.Position = 0;
return new HttpRequestStreamReader(stream, Encoding.UTF8);
}
private static MemoryStream GetSmallStream()
{
var testData = new byte[] { 72, 69, 76, 76, 79 };
return new MemoryStream(testData);
}
private static MemoryStream GetLargeStream()
{
var testData = new byte[] { 72, 69, 76, 76, 79 };
// System.Collections.Generic.
var data = new List<byte>();
for (var i = 0; i < 1000; i++)
{
data.AddRange(testData);
}
return new MemoryStream(data.ToArray());
}
}
}

View File

@ -3,8 +3,8 @@
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.WebUtilities;
namespace Microsoft.AspNetCore.Mvc
{

View File

@ -4,6 +4,7 @@
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.WebUtilities;
namespace Microsoft.AspNetCore.Mvc
{

View File

@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.TestCommon;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Moq;
@ -338,7 +339,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
// Assert
stream.Verify(s => s.FlushAsync(It.IsAny<CancellationToken>()), Times.Never());
stream.Verify(
s => s.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()),
s => s.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()),
Times.Exactly((int)expectedWriteCallCount));
stream.Verify(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Never());
}

View File

@ -3,8 +3,8 @@
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.WebUtilities;
namespace Microsoft.AspNetCore.Mvc
{