* 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": {
|
||||
"Microsoft.Extensions.Primitives": "1.0.0-*",
|
||||
"System.Buffers": "4.0.0-*",
|
||||
"System.Text.Encodings.Web": "4.0.0-*"
|
||||
},
|
||||
"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