Skip to content

Commit

Permalink
Made some changes to better abstract BufferTextWriter.
Browse files Browse the repository at this point in the history
- Added BinaryTextWriter which is a buffer text writer that supports writing array segments.
- Updated tests.
- Added tests for forever frame encoding full messages.
  • Loading branch information
davidfowl committed Oct 22, 2013
1 parent f64b38e commit 33fa85a
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 25 deletions.
@@ -0,0 +1,34 @@
using System;
using Microsoft.AspNet.SignalR.Hosting;

namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// A buffering text writer that supports writing binary directly as well
/// </summary>
internal unsafe class BinaryTextWriter : BufferTextWriter, IBinaryWriter
{
public BinaryTextWriter(IResponse response) :
base((data, state) => ((IResponse)state).Write(data), response, reuseBuffers: true, bufferSize: 128)
{

}

public BinaryTextWriter(IWebSocket socket) :
base((data, state) => ((IWebSocket)state).SendChunk(data), socket, reuseBuffers: false, bufferSize: 1024)
{

}


public BinaryTextWriter(Action<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize) :
base(write, state, reuseBuffers, bufferSize)
{
}

public void Write(ArraySegment<byte> data)
{
Writer.Write(data);
}
}
}
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
/// 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 unsafe class BufferTextWriter : TextWriter, IBinaryWriter
internal abstract unsafe class BufferTextWriter : TextWriter
{
private readonly Encoding _encoding;

Expand All @@ -37,7 +37,7 @@ internal unsafe class BufferTextWriter : TextWriter, IBinaryWriter
}

[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.IO.TextWriter.#ctor", Justification = "It won't be used")]
public BufferTextWriter(Action<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize)
protected BufferTextWriter(Action<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize)
{
_write = write;
_writeState = state;
Expand All @@ -46,7 +46,7 @@ public BufferTextWriter(Action<ArraySegment<byte>, object> write, object state,
_bufferSize = bufferSize;
}

private ChunkedWriter Writer
protected internal ChunkedWriter Writer
{
get
{
Expand Down Expand Up @@ -79,17 +79,12 @@ public override void Write(char value)
Writer.Write(value);
}

public void Write(ArraySegment<byte> data)
{
Writer.Write(data);
}

public override void Flush()
{
Writer.Flush();
}

private class ChunkedWriter
internal class ChunkedWriter
{
private int _charPos;
private int _charLen;
Expand Down
Expand Up @@ -145,7 +145,7 @@ private ArraySegment<byte> GetMessageBuffer(object value)
{
using (var stream = new MemoryStream(128))
{
var bufferWriter = new BufferTextWriter((buffer, state) =>
var bufferWriter = new BinaryTextWriter((buffer, state) =>
{
((MemoryStream)state).Write(buffer.Array, buffer.Offset, buffer.Count);
},
Expand Down
Expand Up @@ -67,6 +67,7 @@
<Compile Include="Infrastructure\AckHandler.cs" />
<Compile Include="Configuration\DefaultConfigurationManager.cs" />
<Compile Include="Infrastructure\ArraySegmentTextReader.cs" />
<Compile Include="Infrastructure\BinaryTextWriter.cs" />
<Compile Include="Infrastructure\ConnectionManager.cs" />
<Compile Include="ConnectionMessage.cs" />
<Compile Include="Infrastructure\DefaultProtectedData.cs" />
Expand Down
Expand Up @@ -20,7 +20,7 @@ public sealed class PersistentResponse : IJsonWritable
private readonly Action<TextWriter> _writeCursor;

public PersistentResponse()
: this(message => true, writer => { })
: this(message => false, writer => { })
{

}
Expand Down
Expand Up @@ -186,7 +186,7 @@ public Uri Url

protected virtual TextWriter CreateResponseWriter()
{
return new BufferTextWriter(Context.Response);
return new BinaryTextWriter(Context.Response);
}

protected void IncrementErrors()
Expand Down
Expand Up @@ -95,7 +95,7 @@ public override Task ProcessRequest(ITransportConnection connection)

protected override TextWriter CreateResponseWriter()
{
return new BufferTextWriter(_socket);
return new BinaryTextWriter(_socket);
}

public override Task Send(object value)
Expand Down
10 changes: 5 additions & 5 deletions tests/Microsoft.AspNet.SignalR.Tests/BufferTextWriterFacts.cs
Expand Up @@ -13,7 +13,7 @@ public class BufferTextWriterFacts
public void CanEncodingSurrogatePairsCorrectly()
{
var bytes = new List<byte>();
var writer = new BufferTextWriter((buffer, state) =>
var writer = new BinaryTextWriter((buffer, state) =>
{
for (int i = buffer.Offset; i < buffer.Count; i++)
{
Expand All @@ -34,7 +34,7 @@ public void CanEncodingSurrogatePairsCorrectly()
public void WriteNewBufferIsUsedForWritingChunksIfReuseBuffersFalse()
{
var buffers = new List<ArraySegment<byte>>();
var writer = new BufferTextWriter((buffer, state) =>
var writer = new BinaryTextWriter((buffer, state) =>
{
buffers.Add(buffer);
},
Expand All @@ -55,7 +55,7 @@ public void WriteNewBufferIsUsedForWritingChunksIfReuseBuffersFalse()
public void WriteSameBufferIsUsedForWritingChunksIfReuseBuffersTrue()
{
var buffers = new List<ArraySegment<byte>>();
var writer = new BufferTextWriter((buffer, state) =>
var writer = new BinaryTextWriter((buffer, state) =>
{
buffers.Add(buffer);
},
Expand All @@ -79,7 +79,7 @@ public void WritesInChunks()
int size = 3000;

var buffers = new List<ArraySegment<byte>>();
var writer = new BufferTextWriter((buffer, state) =>
var writer = new BinaryTextWriter((buffer, state) =>
{
buffers.Add(buffer);
},
Expand Down Expand Up @@ -121,7 +121,7 @@ private IEnumerable<int> GetChunks(int size, int bufferSize)
public void CanInterleaveStringsAndRawBinary()
{
var buffers = new List<ArraySegment<byte>>();
var writer = new BufferTextWriter((buffer, state) =>
var writer = new BinaryTextWriter((buffer, state) =>
{
buffers.Add(buffer);
},
Expand Down
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Hosting;
using Microsoft.AspNet.SignalR.Messaging;
using Microsoft.AspNet.SignalR.Transports;
using Moq;
using Xunit;
Expand All @@ -13,17 +16,31 @@ namespace Microsoft.AspNet.SignalR.Tests.Core
{
public class ForeverFrameTransportFacts
{
[Fact]
public void ForeverFrameTransportEscapesTags()
[Theory]
[InlineData("</sCRiPT>", "\\u003c/sCRiPT\\u003e")]
[InlineData("</SCRIPT dosomething='false'>", "\\u003c/SCRIPT dosomething='false'\\u003e")]
[InlineData("<p>ELLO</p>", "\\u003cp\\u003eELLO\\u003c/p\\u003e")]
public void ForeverFrameTransportEscapesTags(string data, string expected)
{
var request = new Mock<IRequest>();
var response = new CustomResponse();
var context = new HostContext(request.Object, response);
var fft = new ForeverFrameTransport(context, new DefaultDependencyResolver());

AssertEscaped(fft, response, data, expected);
}

[Theory]
[InlineData("<script type=\"\"></script>", "\\u003cscript type=\"\"\\u003e\\u003c/script\\u003e")]
[InlineData("<script type=''></script>", "\\u003cscript type=''\\u003e\\u003c/script\\u003e")]
public void ForeverFrameTransportEscapesTagsWithPersistentResponse(string data, string expected)
{
var request = new Mock<IRequest>();
var response = new CustomResponse();
var context = new HostContext(request.Object, response);
var fft = new ForeverFrameTransport(context, new DefaultDependencyResolver());

AssertEscaped(fft, response, "</sCRiPT>", "\\u003c/sCRiPT\\u003e");
AssertEscaped(fft, response, "</SCRIPT dosomething='false'>", "\\u003c/SCRIPT dosomething='false'\\u003e");
AssertEscaped(fft, response, "<p>ELLO</p>", "\\u003cp\\u003eELLO\\u003c/p\\u003e");
AssertEscaped(fft, response, GetWrappedResponse(data), expected);
}

[Theory]
Expand All @@ -42,7 +59,7 @@ public void ForeverFrameTransportThrowsOnInvalidFrameId(string frameId)
var context = new HostContext(request.Object, response);
var connection = new Mock<ITransportConnection>();
var fft = new ForeverFrameTransport(context, new DefaultDependencyResolver());

Assert.Throws(typeof(InvalidOperationException), () => fft.InitializeResponse(connection.Object));
}

Expand All @@ -62,7 +79,7 @@ public void ForeverFrameTransportSetsCorrectContentType()
Assert.Equal("text/html; charset=UTF-8", response.ContentType);
}

private static void AssertEscaped(ForeverFrameTransport fft, CustomResponse response, string input, string expectedOutput)
private static void AssertEscaped(ForeverFrameTransport fft, CustomResponse response, object input, string expectedOutput)
{
fft.Send(input).Wait();

Expand All @@ -73,6 +90,22 @@ private static void AssertEscaped(ForeverFrameTransport fft, CustomResponse resp
Assert.True(rawResponse.Contains(expectedOutput));
}

private static PersistentResponse GetWrappedResponse(string raw)
{
var data = Encoding.Default.GetBytes(raw);
var message = new Message("foo", "key", new ArraySegment<byte>(data));

var response = new PersistentResponse
{
Messages = new List<ArraySegment<Message>>
{
new ArraySegment<Message>(new Message[] { message })
}
};

return response;
}

private class CustomResponse : IResponse
{
private MemoryStream _stream;
Expand Down

0 comments on commit 33fa85a

Please sign in to comment.