You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
5.7 KiB
188 lines
5.7 KiB
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
|
|
|
|
using System;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Text;
|
|
using Microsoft.AspNet.SignalR.Hosting;
|
|
|
|
namespace Microsoft.AspNet.SignalR.Infrastructure
|
|
{
|
|
/// <summary>
|
|
/// TextWriter implementation over a write delegate optimized for writing in small chunks
|
|
/// we don't need to write to a long lived buffer. This saves massive amounts of memory
|
|
/// as the number of connections grows.
|
|
/// </summary>
|
|
internal abstract unsafe class BufferTextWriter : TextWriter
|
|
{
|
|
private readonly Encoding _encoding;
|
|
|
|
private readonly Action<ArraySegment<byte>, object> _write;
|
|
private readonly object _writeState;
|
|
private readonly bool _reuseBuffers;
|
|
|
|
private ChunkedWriter _writer;
|
|
private int _bufferSize;
|
|
|
|
public BufferTextWriter(IResponse response) :
|
|
this((data, state) => ((IResponse)state).Write(data), response, reuseBuffers: true, bufferSize: 128)
|
|
{
|
|
|
|
}
|
|
|
|
public BufferTextWriter(IWebSocket socket) :
|
|
this((data, state) => ((IWebSocket)state).SendChunk(data), socket, reuseBuffers: false, bufferSize: 1024 * 4)
|
|
{
|
|
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.IO.TextWriter.#ctor", Justification = "It won't be used")]
|
|
protected BufferTextWriter(Action<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize)
|
|
{
|
|
_write = write;
|
|
_writeState = state;
|
|
_encoding = new UTF8Encoding();
|
|
_reuseBuffers = reuseBuffers;
|
|
_bufferSize = bufferSize;
|
|
}
|
|
|
|
protected internal ChunkedWriter Writer
|
|
{
|
|
get
|
|
{
|
|
if (_writer == null)
|
|
{
|
|
_writer = new ChunkedWriter(_write, _writeState, _bufferSize, _encoding, _reuseBuffers);
|
|
}
|
|
|
|
return _writer;
|
|
}
|
|
}
|
|
|
|
public override Encoding Encoding
|
|
{
|
|
get { return _encoding; }
|
|
}
|
|
|
|
public override void Write(string value)
|
|
{
|
|
Writer.Write(value);
|
|
}
|
|
|
|
public override void WriteLine(string value)
|
|
{
|
|
Writer.Write(value);
|
|
}
|
|
|
|
public override void Write(char value)
|
|
{
|
|
Writer.Write(value);
|
|
}
|
|
|
|
public override void Flush()
|
|
{
|
|
Writer.Flush();
|
|
}
|
|
|
|
internal class ChunkedWriter
|
|
{
|
|
private int _charPos;
|
|
private int _charLen;
|
|
|
|
private readonly Encoder _encoder;
|
|
private readonly char[] _charBuffer;
|
|
private readonly byte[] _byteBuffer;
|
|
private readonly Action<ArraySegment<byte>, object> _write;
|
|
private readonly object _writeState;
|
|
|
|
public ChunkedWriter(Action<ArraySegment<byte>, object> write, object state, int chunkSize, Encoding encoding, bool reuseBuffers)
|
|
{
|
|
_charLen = chunkSize;
|
|
_charBuffer = new char[chunkSize];
|
|
_write = write;
|
|
_writeState = state;
|
|
_encoder = encoding.GetEncoder();
|
|
|
|
if (reuseBuffers)
|
|
{
|
|
_byteBuffer = new byte[encoding.GetMaxByteCount(chunkSize)];
|
|
}
|
|
}
|
|
|
|
public void Write(char value)
|
|
{
|
|
if (_charPos == _charLen)
|
|
{
|
|
Flush(flushEncoder: false);
|
|
}
|
|
|
|
_charBuffer[_charPos++] = value;
|
|
}
|
|
|
|
public void Write(string value)
|
|
{
|
|
int length = value.Length;
|
|
int sourceIndex = 0;
|
|
|
|
while (length > 0)
|
|
{
|
|
if (_charPos == _charLen)
|
|
{
|
|
Flush(flushEncoder: false);
|
|
}
|
|
|
|
int count = _charLen - _charPos;
|
|
if (count > length)
|
|
{
|
|
count = length;
|
|
}
|
|
|
|
value.CopyTo(sourceIndex, _charBuffer, _charPos, count);
|
|
_charPos += count;
|
|
sourceIndex += count;
|
|
length -= count;
|
|
}
|
|
}
|
|
|
|
public void Write(ArraySegment<byte> data)
|
|
{
|
|
Flush();
|
|
_write(data, _writeState);
|
|
}
|
|
|
|
public void Flush()
|
|
{
|
|
Flush(flushEncoder: true);
|
|
}
|
|
|
|
private void Flush(bool flushEncoder)
|
|
{
|
|
// If it's safe to reuse the buffer then do so
|
|
if (_byteBuffer != null)
|
|
{
|
|
Flush(_byteBuffer, flushEncoder);
|
|
}
|
|
else
|
|
{
|
|
// Allocate a byte array of the right size for this char buffer
|
|
int byteCount = _encoder.GetByteCount(_charBuffer, 0, _charPos, flush: false);
|
|
var byteBuffer = new byte[byteCount];
|
|
Flush(byteBuffer, flushEncoder);
|
|
}
|
|
}
|
|
|
|
private void Flush(byte[] byteBuffer, bool flushEncoder)
|
|
{
|
|
int count = _encoder.GetBytes(_charBuffer, 0, _charPos, byteBuffer, 0, flush: flushEncoder);
|
|
|
|
_charPos = 0;
|
|
|
|
if (count > 0)
|
|
{
|
|
_write(new ArraySegment<byte>(byteBuffer, 0, count), _writeState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|