* Move HttpResponseStreamWriter from Mvc

This commit is contained in:
ryanbrandenburg 2016-01-12 16:57:57 -08:00
parent 38feebc0d6
commit da478b02ed
7 changed files with 1725 additions and 0 deletions

View File

@ -0,0 +1,437 @@
// 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;
namespace Microsoft.AspNet.WebUtilities
{
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 NET451
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

@ -0,0 +1,395 @@
// 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;
namespace Microsoft.AspNet.WebUtilities
{
/// <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

@ -0,0 +1,80 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.AspNet.WebUtilities {
using System;
using System.Reflection;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.WebUtilities.Resources", typeof(Resources).GetTypeInfo().Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to The stream must support reading..
/// </summary>
internal static string HttpRequestStreamReader_StreamNotReadable {
get {
return ResourceManager.GetString("HttpRequestStreamReader_StreamNotReadable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The stream must support writing..
/// </summary>
internal static string HttpResponseStreamWriter_StreamNotWritable {
get {
return ResourceManager.GetString("HttpResponseStreamWriter_StreamNotWritable", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<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>
</root>

View File

@ -11,6 +11,7 @@
},
"dependencies": {
"Microsoft.Extensions.Primitives": "1.0.0-*",
"System.Buffers": "4.0.0-*",
"System.Text.Encodings.Web": "4.0.0-*"
},
"frameworks": {

View File

@ -0,0 +1,225 @@
// 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.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNet.WebUtilities.Test
{
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

@ -0,0 +1,461 @@
// 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 Xunit;
namespace Microsoft.AspNet.WebUtilities.Test
{
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 NET451
[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 NET451
[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 NET451
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 NET451
public override void Close()
{
CloseCallCount++;
base.Close();
}
#endif
protected override void Dispose(bool disposing)
{
DisposeCallCount++;
base.Dispose(disposing);
}
}
}
}