Work around for a Firefox bug
Firefox won't fire EventSource open event until it receives some data. The workaround is to send an empty comment when starting ServerSentEvent transport. Fixes: #352
This commit is contained in:
parent
39b2990b62
commit
82f99a1424
|
|
@ -14,26 +14,45 @@
|
|||
</div>
|
||||
<script src="lib/signalr-client/signalr-client.js"></script>
|
||||
<script>
|
||||
let transportType = signalR.TransportType[getParameterByName('transport')] || signalR.TransportType.WebSockets;
|
||||
let connection = new signalR.HubConnection(`http://${document.location.host}/chat`, 'formatType=json&format=text');
|
||||
|
||||
connection.on('Send', function (message) {
|
||||
var child = document.createElement('li');
|
||||
child.innerText = message;
|
||||
document.getElementById('messages').appendChild(child);
|
||||
});
|
||||
connection.on('Send', message => appendLine(message));
|
||||
connection.onClosed = e => {
|
||||
if (e) {
|
||||
appendLine('Connection closed with error: ' + e, 'red');
|
||||
}
|
||||
else {
|
||||
appendLine('Disconnected', 'green');
|
||||
}
|
||||
};
|
||||
|
||||
connection.start();
|
||||
connection.start(transportType).catch(err => appendLine(err, 'red'));
|
||||
|
||||
document.getElementById('sendmessage').addEventListener('submit', event => {
|
||||
let data = document.getElementById('new-message').value;
|
||||
|
||||
connection.invoke('Send', data).catch(err => {
|
||||
var child = document.createElement('li');
|
||||
child.style.color = 'red';
|
||||
child.innerText = err;
|
||||
document.getElementById('messages').appendChild(child);
|
||||
});
|
||||
|
||||
connection.invoke('Send', data).catch(err => appendLine(err, 'red'));
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
function appendLine(line, color) {
|
||||
let child = document.createElement('li');
|
||||
if (color) {
|
||||
child.style.color = color;
|
||||
}
|
||||
child.innerText = line;
|
||||
document.getElementById('messages').appendChild(child);
|
||||
}
|
||||
|
||||
function getParameterByName(name, url) {
|
||||
if (!url) {
|
||||
url = window.location.href;
|
||||
}
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
</script>
|
||||
|
|
@ -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\<Anytine except \n>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Message>();
|
||||
|
|
|
|||
Loading…
Reference in New Issue