* Move HttpResponseStreamWriter from Mvc
This commit is contained in:
parent
38feebc0d6
commit
da478b02ed
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Primitives": "1.0.0-*",
|
"Microsoft.Extensions.Primitives": "1.0.0-*",
|
||||||
|
"System.Buffers": "4.0.0-*",
|
||||||
"System.Text.Encodings.Web": "4.0.0-*"
|
"System.Text.Encodings.Web": "4.0.0-*"
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue