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