diff --git a/samples/ChatSample/Views/Home/Index.cshtml b/samples/ChatSample/Views/Home/Index.cshtml
index 9ad60aa18b..1c29e29eef 100644
--- a/samples/ChatSample/Views/Home/Index.cshtml
+++ b/samples/ChatSample/Views/Home/Index.cshtml
@@ -14,26 +14,45 @@
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Sockets.Common/Internal/Formatters/ServerSentEventsMessageParser.cs b/src/Microsoft.AspNetCore.Sockets.Common/Internal/Formatters/ServerSentEventsMessageParser.cs
index 3d22d6a5df..b786986dbc 100644
--- a/src/Microsoft.AspNetCore.Sockets.Common/Internal/Formatters/ServerSentEventsMessageParser.cs
+++ b/src/Microsoft.AspNetCore.Sockets.Common/Internal/Formatters/ServerSentEventsMessageParser.cs
@@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters
{
private const byte ByteCR = (byte)'\r';
private const byte ByteLF = (byte)'\n';
+ private const byte ByteColon = (byte)':';
private const byte ByteT = (byte)'T';
private const byte ByteB = (byte)'B';
private const byte ByteC = (byte)'C';
@@ -38,7 +39,6 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters
while (!reader.End)
{
-
if (ReadCursorOperations.Seek(start, end, out var lineEnd, ByteLF) == -1)
{
// For the case of data: Foo\r\n\r\
@@ -63,6 +63,14 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters
throw new FormatException("There was an error in the frame format");
}
+ // Skip comments
+ if (line[0] == ByteColon)
+ {
+ start = lineEnd;
+ consumed = lineEnd;
+ continue;
+ }
+
if (IsMessageEnd(line))
{
_internalParserState = InternalParseState.ReadEndOfMessage;
diff --git a/src/Microsoft.AspNetCore.Sockets/Transports/ServerSentEventsTransport.cs b/src/Microsoft.AspNetCore.Sockets/Transports/ServerSentEventsTransport.cs
index eaa5b98186..cdd4b020f7 100644
--- a/src/Microsoft.AspNetCore.Sockets/Transports/ServerSentEventsTransport.cs
+++ b/src/Microsoft.AspNetCore.Sockets/Transports/ServerSentEventsTransport.cs
@@ -37,6 +37,9 @@ namespace Microsoft.AspNetCore.Sockets.Transports
context.Response.Headers["Content-Encoding"] = "identity";
+ // Workaround for a Firefox bug where EventSource won't fire the open event
+ // until it receives some data
+ await context.Response.WriteAsync(":\r\n");
await context.Response.Body.FlushAsync();
var pipe = context.Response.Body.AsPipelineWriter();
diff --git a/test/Microsoft.AspNetCore.Sockets.Common.Tests/Internal/Formatters/ServerSentEventsParserTests.cs b/test/Microsoft.AspNetCore.Sockets.Common.Tests/Internal/Formatters/ServerSentEventsParserTests.cs
index 2a6f3e1788..302608b9a2 100644
--- a/test/Microsoft.AspNetCore.Sockets.Common.Tests/Internal/Formatters/ServerSentEventsParserTests.cs
+++ b/test/Microsoft.AspNetCore.Sockets.Common.Tests/Internal/Formatters/ServerSentEventsParserTests.cs
@@ -16,14 +16,20 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters
[Theory]
[InlineData("data: T\r\n\r\n", "", MessageType.Text)]
[InlineData("data: B\r\n\r\n", "", MessageType.Binary)]
+ [InlineData("data: T\r\n\r\n:\r\n", "", MessageType.Text)]
+ [InlineData("data: T\r\n\r\n:comment\r\n", "", MessageType.Text)]
[InlineData("data: T\r\ndata: \r\r\n\r\n", "\r", MessageType.Text)]
+ [InlineData("data: T\r\n:comment\r\ndata: \r\r\n\r\n", "\r", MessageType.Text)]
[InlineData("data: T\r\ndata: A\rB\r\n\r\n", "A\rB", MessageType.Text)]
[InlineData("data: T\r\ndata: Hello, World\r\n\r\n", "Hello, World", MessageType.Text)]
- [InlineData("data: T\r\ndata: Hello, World\r\n\r\n", "Hello, World", MessageType.Text)]
[InlineData("data: T\r\ndata: Hello, World\r\n\r\ndata: ", "Hello, World", MessageType.Text)]
+ [InlineData("data: T\r\ndata: Hello, World\r\n\r\n:comment\r\ndata: ", "Hello, World", MessageType.Text)]
+ [InlineData("data: T\r\ndata: Hello, World\r\n\r\n:comment", "Hello, World", MessageType.Text)]
+ [InlineData("data: T\r\ndata: Hello, World\r\n\r\n:comment\r\n", "Hello, World", MessageType.Text)]
+ [InlineData("data: T\r\ndata: Hello, World\r\n:comment\r\n\r\n", "Hello, World", MessageType.Text)]
[InlineData("data: B\r\ndata: SGVsbG8sIFdvcmxk\r\n\r\n", "Hello, World", MessageType.Binary)]
[InlineData("data: B\r\ndata: SGVsbG8g\r\ndata: V29ybGQ=\r\n\r\n", "Hello World", MessageType.Binary)]
- public void ParseSSEMessageSuccessCases(string encodedMessage, string expectedMessage, MessageType messageType)
+ public void ParseSSEMessageSuccessCases(string encodedMessage, string expectedMessage, MessageType messageType)
{
var buffer = Encoding.UTF8.GetBytes(encodedMessage);
var readableBuffer = ReadableBuffer.Create(buffer);
@@ -68,6 +74,9 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters
[Theory]
[InlineData("")]
+ [InlineData(":")]
+ [InlineData(":comment")]
+ [InlineData(":comment\r\n")]
[InlineData("data:")]
[InlineData("data: \r")]
[InlineData("data: T\r\nda")]
@@ -77,6 +86,10 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters
[InlineData("data: T\r\ndata: Hello, World\r\n")]
[InlineData("data: T\r\ndata: Hello, World\r\n\r")]
[InlineData("data: B\r\ndata: SGVsbG8sIFd")]
+ [InlineData(":\r\ndata:")]
+ [InlineData("data: T\r\n:\r\n")]
+ [InlineData("data: T\r\n:\r\ndata:")]
+ [InlineData("data: T\r\ndata: Hello, World\r\n:comment")]
public void ParseSSEMessageIncompleteParseResult(string encodedMessage)
{
var buffer = Encoding.UTF8.GetBytes(encodedMessage);
@@ -89,40 +102,47 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters
}
[Theory]
- [InlineData("d", "ata: T\r\ndata: Hello, World\r\n\r\n", "Hello, World", MessageType.Text)]
- [InlineData("data: T", "\r\ndata: Hello, World\r\n\r\n", "Hello, World", MessageType.Text)]
- [InlineData("data: T\r", "\ndata: Hello, World\r\n\r\n", "Hello, World", MessageType.Text)]
- [InlineData("data: T\r\n", "data: Hello, World\r\n\r\n", "Hello, World", MessageType.Text)]
- [InlineData("data: T\r\nd", "ata: Hello, World\r\n\r\n", "Hello, World", MessageType.Text)]
- [InlineData("data: T\r\ndata: ", "Hello, World\r\n\r\n", "Hello, World", MessageType.Text)]
- [InlineData("data: T\r\ndata: Hello, World", "\r\n\r\n", "Hello, World", MessageType.Text)]
- [InlineData("data: T\r\ndata: Hello, World\r\n", "\r\n", "Hello, World", MessageType.Text)]
- [InlineData("data: T", "\r\ndata: Hello, World\r\n\r\n", "Hello, World", MessageType.Text)]
- [InlineData("data: ", "T\r\ndata: Hello, World\r\n\r\n", "Hello, World", MessageType.Text)]
- [InlineData("data: B\r\ndata: SGVs", "bG8sIFdvcmxk\r\n\r\n", "Hello, World", MessageType.Binary)]
- public async Task ParseMessageAcrossMultipleReadsSuccess(string encodedMessagePart1, string encodedMessagePart2, string expectedMessage, MessageType expectedMessageType)
+ [InlineData(new[] { "d", "ata: T\r\ndata: Hello, World\r\n\r\n" }, "Hello, World", MessageType.Text)]
+ [InlineData(new[] { "data: T", "\r\ndata: Hello, World\r\n\r\n" }, "Hello, World", MessageType.Text)]
+ [InlineData(new[] { "data: T\r", "\ndata: Hello, World\r\n\r\n" }, "Hello, World", MessageType.Text)]
+ [InlineData(new[] { "data: T\r\n", "data: Hello, World\r\n\r\n" }, "Hello, World", MessageType.Text)]
+ [InlineData(new[] { "data: T\r\nd", "ata: Hello, World\r\n\r\n" }, "Hello, World", MessageType.Text)]
+ [InlineData(new[] { "data: T\r\ndata: ", "Hello, World\r\n\r\n" }, "Hello, World", MessageType.Text)]
+ [InlineData(new[] { "data: T\r\ndata: Hello, World", "\r\n\r\n" }, "Hello, World", MessageType.Text)]
+ [InlineData(new[] { "data: T\r\ndata: Hello, World\r\n", "\r\n" }, "Hello, World", MessageType.Text)]
+ [InlineData(new[] { "data: ", "T\r\ndata: Hello, World\r\n\r\n" }, "Hello, World", MessageType.Text)]
+ [InlineData(new[] { ":", "comment", "\r\n", "d", "ata: T\r\ndata: Hello, World\r\n\r\n" }, "Hello, World", MessageType.Text)]
+ [InlineData(new[] { "data: T\r\n", ":comment", "\r\n", "data: Hello, World", "\r\n\r\n" }, "Hello, World", MessageType.Text)]
+ [InlineData(new[] { "data: T\r\ndata: Hello, World\r\n", ":comment\r\n", "\r\n" }, "Hello, World", MessageType.Text)]
+ [InlineData(new[] { "data: B\r\ndata: SGVs", "bG8sIFdvcmxk\r\n\r\n" }, "Hello, World", MessageType.Binary)]
+ public async Task ParseMessageAcrossMultipleReadsSuccess(string[] messageParts, string expectedMessage, MessageType expectedMessageType)
{
using (var pipeFactory = new PipeFactory())
{
+ var parser = new ServerSentEventsMessageParser();
var pipe = pipeFactory.Create();
- // Read the first part of the message
- await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes(encodedMessagePart1));
+ Message message = default(Message);
+ ReadCursor consumed = default(ReadCursor), examined = default(ReadCursor);
- var result = await pipe.Reader.ReadAsync();
- var parser = new ServerSentEventsMessageParser();
+ for (var i = 0; i < messageParts.Length; i++)
+ {
+ var messagePart = messageParts[i];
+ await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes(messagePart));
+ var result = await pipe.Reader.ReadAsync();
- var parseResult = parser.ParseMessage(result.Buffer, out var consumed, out var examined, out Message message);
- Assert.Equal(ServerSentEventsMessageParser.ParseResult.Incomplete, parseResult);
+ var parseResult = parser.ParseMessage(result.Buffer, out consumed, out examined, out message);
+ pipe.Reader.Advance(consumed, examined);
- pipe.Reader.Advance(consumed, examined);
+ // parse result should be complete only after we parsed the last message part
+ var expectedResult =
+ i == messageParts.Length - 1
+ ? ServerSentEventsMessageParser.ParseResult.Completed
+ : ServerSentEventsMessageParser.ParseResult.Incomplete;
- // Send the rest of the data and parse the complete message
- await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes(encodedMessagePart2));
- result = await pipe.Reader.ReadAsync();
+ Assert.Equal(expectedResult, parseResult);
+ }
- parseResult = parser.ParseMessage(result.Buffer, out consumed, out examined, out message);
- Assert.Equal(ServerSentEventsMessageParser.ParseResult.Completed, parseResult);
Assert.Equal(expectedMessageType, message.Type);
Assert.Equal(consumed, examined);
diff --git a/test/Microsoft.AspNetCore.Sockets.Tests/ServerSentEventsTests.cs b/test/Microsoft.AspNetCore.Sockets.Tests/ServerSentEventsTests.cs
index 05dc925f5c..17883e6c3d 100644
--- a/test/Microsoft.AspNetCore.Sockets.Tests/ServerSentEventsTests.cs
+++ b/test/Microsoft.AspNetCore.Sockets.Tests/ServerSentEventsTests.cs
@@ -49,9 +49,9 @@ namespace Microsoft.AspNetCore.Sockets.Tests
}
[Theory]
- [InlineData("Hello World", "data: T\r\ndata: Hello World\r\n\r\n")]
- [InlineData("Hello\nWorld", "data: T\r\ndata: Hello\r\ndata: World\r\n\r\n")]
- [InlineData("Hello\r\nWorld", "data: T\r\ndata: Hello\r\ndata: World\r\n\r\n")]
+ [InlineData("Hello World", ":\r\ndata: T\r\ndata: Hello World\r\n\r\n")]
+ [InlineData("Hello\nWorld", ":\r\ndata: T\r\ndata: Hello\r\ndata: World\r\n\r\n")]
+ [InlineData("Hello\r\nWorld", ":\r\ndata: T\r\ndata: Hello\r\ndata: World\r\n\r\n")]
public async Task SSEAddsAppropriateFraming(string message, string expected)
{
var channel = Channel.CreateUnbounded();