diff --git a/src/Microsoft.AspNetCore.Sockets.Client/LongPollingTransport.cs b/src/Microsoft.AspNetCore.Sockets.Client/LongPollingTransport.cs index 42d1995215..0fbc92b12e 100644 --- a/src/Microsoft.AspNetCore.Sockets.Client/LongPollingTransport.cs +++ b/src/Microsoft.AspNetCore.Sockets.Client/LongPollingTransport.cs @@ -53,8 +53,9 @@ namespace Microsoft.AspNetCore.Sockets.Client _application = application; - // Start sending and polling - _poller = Poll(Utils.AppendPath(url, "poll"), _transportCts.Token); + // Start sending and polling (ask for binary if the server supports it) + var pollUrl = Utils.AppendQueryString(Utils.AppendPath(url, "poll"), "supportsBinary=true"); + _poller = Poll(pollUrl, _transportCts.Token); _sender = SendMessages(Utils.AppendPath(url, "send"), _transportCts.Token); Running = Task.WhenAll(_sender, _poller).ContinueWith(t => @@ -110,11 +111,13 @@ namespace Microsoft.AspNetCore.Sockets.Client { _logger.LogDebug("Received messages from the server"); + var messageFormat = MessageParser.GetFormatFromContentType(response.Content.Headers.ContentType.ToString()); + // Until Pipeline starts natively supporting BytesReader, this is the easiest way to do this. var payload = await response.Content.ReadAsByteArrayAsync(); if (payload.Length > 0) { - var messages = ParsePayload(payload); + var messages = ParsePayload(payload, messageFormat); foreach (var message in messages) { @@ -148,10 +151,13 @@ namespace Microsoft.AspNetCore.Sockets.Client _logger.LogInformation("Receive loop stopped"); } - private IList ParsePayload(byte[] payload) + private IList ParsePayload(byte[] payload, MessageFormat messageFormat) { var reader = new BytesReader(payload); - var messageFormat = MessageParser.GetFormat(reader.Unread[0]); + if (messageFormat != MessageParser.GetFormatFromIndicator(reader.Unread[0])) + { + throw new FormatException($"Format indicator '{(char)reader.Unread[0]}' does not match format determined by Content-Type '{MessageFormatter.GetContentType(messageFormat)}'"); + } reader.Advance(1); _parser.Reset(); diff --git a/src/Microsoft.AspNetCore.Sockets.Client/Utils.cs b/src/Microsoft.AspNetCore.Sockets.Client/Utils.cs index db17b2a0ed..71280862a1 100644 --- a/src/Microsoft.AspNetCore.Sockets.Client/Utils.cs +++ b/src/Microsoft.AspNetCore.Sockets.Client/Utils.cs @@ -26,11 +26,19 @@ namespace Microsoft.AspNetCore.Sockets.Client } var builder = new UriBuilder(url); + var newQueryString = builder.Query; if (!string.IsNullOrEmpty(builder.Query)) { - builder.Query += "&"; + newQueryString += "&"; } - builder.Query += qs; + newQueryString += qs; + + if (newQueryString.Length > 0 && newQueryString[0] == '?') + { + newQueryString = newQueryString.Substring(1); + } + + builder.Query = newQueryString; return builder.Uri; } } diff --git a/src/Microsoft.AspNetCore.Sockets.Common/Internal/Formatters/MessageParser.cs b/src/Microsoft.AspNetCore.Sockets.Common/Internal/Formatters/MessageParser.cs index dcd8eef98f..de08a52373 100644 --- a/src/Microsoft.AspNetCore.Sockets.Common/Internal/Formatters/MessageParser.cs +++ b/src/Microsoft.AspNetCore.Sockets.Common/Internal/Formatters/MessageParser.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters _binaryParser.TryParseMessage(ref buffer, out message); } - public static MessageFormat GetFormat(byte formatIndicator) + public static MessageFormat GetFormatFromIndicator(byte formatIndicator) { // Can't use switch because our "constants" are not consts, they're "static readonly" (which is good, because they are public) if (formatIndicator == MessageFormatter.TextFormatIndicator) @@ -39,5 +39,21 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters throw new ArgumentException($"Invalid message format: 0x{formatIndicator:X}", nameof(formatIndicator)); } + + public static MessageFormat GetFormatFromContentType(string contentType) + { + // Can't use switch because our "constants" are not consts, they're "static readonly" (which is good, because they are public) + if (string.Equals(contentType, MessageFormatter.TextContentType, StringComparison.OrdinalIgnoreCase)) + { + return MessageFormat.Text; + } + + if (string.Equals(contentType, MessageFormatter.BinaryContentType, StringComparison.OrdinalIgnoreCase)) + { + return MessageFormat.Binary; + } + + throw new ArgumentException($"Invalid Content-Type: '{contentType}'", nameof(contentType)); + } } } diff --git a/src/Microsoft.AspNetCore.Sockets/Transports/LongPollingTransport.cs b/src/Microsoft.AspNetCore.Sockets/Transports/LongPollingTransport.cs index 50dbcc3228..1904ac5e7c 100644 --- a/src/Microsoft.AspNetCore.Sockets/Transports/LongPollingTransport.cs +++ b/src/Microsoft.AspNetCore.Sockets/Transports/LongPollingTransport.cs @@ -38,8 +38,10 @@ namespace Microsoft.AspNetCore.Sockets.Transports return; } - // TODO: Add support for binary protocol - var messageFormat = MessageFormat.Text; + // REVIEW: We could also use the 'Accept' header, in theory... + var messageFormat = string.Equals(context.Request.Query["supportsBinary"], "true", StringComparison.OrdinalIgnoreCase) ? + MessageFormat.Binary : + MessageFormat.Text; context.Response.ContentType = MessageFormatter.GetContentType(messageFormat); var writer = context.Response.Body.AsPipelineWriter(); diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs index 09958ed2c4..5a8e346574 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs @@ -67,19 +67,22 @@ namespace Microsoft.AspNetCore.SignalR.Tests { get { - yield return new object[] { new WebSocketsTransport() }; - yield return new object[] { new LongPollingTransport(new HttpClient()) }; + yield return new object[] { new Func(loggerFactory => new WebSocketsTransport(loggerFactory)) }; + yield return new object[] { new Func(loggerFactory => new LongPollingTransport(new HttpClient(), loggerFactory)) }; } } [ConditionalTheory] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2, SkipReason = "No WebSockets Client for this platform")] [MemberData(nameof(Transports))] - public async Task ConnectionCanSendAndReceiveMessages(ITransport transport) + public async Task ConnectionCanSendAndReceiveMessages(Func transportFactory) { const string message = "Major Key"; var baseUrl = _serverFixture.BaseUrl; var loggerFactory = new LoggerFactory(); + loggerFactory.AddXUnit(_output, LogLevel.Trace); + + var transport = transportFactory(loggerFactory); using (var httpClient = new HttpClient()) { @@ -119,8 +122,8 @@ namespace Microsoft.AspNetCore.SignalR.Tests { get { - yield return new object[] { new string('A', 5 * 1024)}; - yield return new object[] { new string('A', 5 * 1024 * 1024 + 32)}; + yield return new object[] { new string('A', 5 * 1024) }; + yield return new object[] { new string('A', 5 * 1024 * 1024 + 32) }; } } diff --git a/test/Microsoft.AspNetCore.Sockets.Client.Tests/ConnectionTests.cs b/test/Microsoft.AspNetCore.Sockets.Client.Tests/ConnectionTests.cs index a5c73f6b30..9cc0facf67 100644 --- a/test/Microsoft.AspNetCore.Sockets.Client.Tests/ConnectionTests.cs +++ b/test/Microsoft.AspNetCore.Sockets.Client.Tests/ConnectionTests.cs @@ -1,12 +1,6 @@ // 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 Microsoft.AspNetCore.SignalR.Tests.Common; -using Microsoft.AspNetCore.Sockets.Internal.Formatters; -using Microsoft.AspNetCore.Sockets.Tests.Internal; -using Microsoft.Extensions.Logging; -using Moq; -using Moq.Protected; using System; using System.Linq; using System.Net; @@ -15,6 +9,13 @@ using System.Text; using System.Text.Formatting; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Client.Tests; +using Microsoft.AspNetCore.SignalR.Tests.Common; +using Microsoft.AspNetCore.Sockets.Internal.Formatters; +using Microsoft.AspNetCore.Sockets.Tests.Internal; +using Microsoft.Extensions.Logging; +using Moq; +using Moq.Protected; using Xunit; namespace Microsoft.AspNetCore.Sockets.Client.Tests @@ -265,7 +266,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests .Returns(async (request, cancellationToken) => { await Task.Yield(); - return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) @@ -438,7 +439,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests .Returns(async (request, cancellationToken) => { await Task.Yield(); - return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) @@ -479,7 +480,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests { sendTcs.SetResult(await request.Content.ReadAsByteArrayAsync()); } - return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) @@ -555,9 +556,9 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests await Task.Yield(); if (request.RequestUri.AbsolutePath.EndsWith("/send")) { - return new HttpResponseMessage(HttpStatusCode.InternalServerError) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.InternalServerError); } - return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) @@ -589,7 +590,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests { content = "T2:T:42;"; } - return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(content) }; + return ResponseUtils.CreateResponse(HttpStatusCode.OK, MessageFormatter.TextContentType, content); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) diff --git a/test/Microsoft.AspNetCore.Sockets.Client.Tests/HubConnectionTests.cs b/test/Microsoft.AspNetCore.Sockets.Client.Tests/HubConnectionTests.cs index f3b0dad029..ad9d910ee3 100644 --- a/test/Microsoft.AspNetCore.Sockets.Client.Tests/HubConnectionTests.cs +++ b/test/Microsoft.AspNetCore.Sockets.Client.Tests/HubConnectionTests.cs @@ -6,6 +6,7 @@ using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Client.Tests; using Microsoft.AspNetCore.SignalR.Tests.Common; using Microsoft.AspNetCore.Sockets.Client; using Microsoft.Extensions.Logging; @@ -41,7 +42,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests .Returns(async (request, cancellationToken) => { await Task.Yield(); - return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) @@ -73,7 +74,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests .Returns(async (request, cancellationToken) => { await Task.Yield(); - return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) @@ -120,7 +121,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests .Returns(async (request, cancellationToken) => { await Task.Yield(); - return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) @@ -152,7 +153,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests .Returns(async (request, cancellationToken) => { await Task.Yield(); - return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) diff --git a/test/Microsoft.AspNetCore.Sockets.Client.Tests/LongPollingTransportTests.cs b/test/Microsoft.AspNetCore.Sockets.Client.Tests/LongPollingTransportTests.cs index 2bc976126e..666acfd0a3 100644 --- a/test/Microsoft.AspNetCore.Sockets.Client.Tests/LongPollingTransportTests.cs +++ b/test/Microsoft.AspNetCore.Sockets.Client.Tests/LongPollingTransportTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using System.Text; @@ -10,13 +11,16 @@ using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Channels; using Microsoft.AspNetCore.SignalR.Tests.Common; +using Microsoft.AspNetCore.Sockets; +using Microsoft.AspNetCore.Sockets.Client; using Microsoft.AspNetCore.Sockets.Internal; +using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Microsoft.Extensions.Logging; using Moq; using Moq.Protected; using Xunit; -namespace Microsoft.AspNetCore.Sockets.Client.Tests +namespace Microsoft.AspNetCore.Client.Tests { public class LongPollingTransportTests { @@ -29,7 +33,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests .Returns(async (request, cancellationToken) => { await Task.Yield(); - return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); Task transportActiveTask; @@ -67,7 +71,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests .Returns(async (request, cancellationToken) => { await Task.Yield(); - return new HttpResponseMessage(HttpStatusCode.NoContent) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.NoContent); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) @@ -100,7 +104,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests .Returns(async (request, cancellationToken) => { await Task.Yield(); - return new HttpResponseMessage(HttpStatusCode.InternalServerError) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.InternalServerError); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) @@ -136,7 +140,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests var statusCode = request.RequestUri.AbsolutePath.EndsWith("send") ? HttpStatusCode.InternalServerError : HttpStatusCode.OK; - return new HttpResponseMessage(statusCode) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(statusCode); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) @@ -177,7 +181,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests .Returns(async (request, cancellationToken) => { await Task.Yield(); - return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) @@ -204,6 +208,127 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests } } + [Fact] + public async Task LongPollingTransportThrowsIfFormatIndicatorDoesNotMatchContentType() + { + var encoded = new byte[] { (byte)'T' }; + + var firstCall = true; + var mockHttpHandler = new Mock(); + var sentRequests = new List(); + mockHttpHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .Returns(async (request, cancellationToken) => + { + sentRequests.Add(request); + + await Task.Yield(); + + if (firstCall) + { + firstCall = false; + return ResponseUtils.CreateResponse(HttpStatusCode.OK, MessageFormatter.BinaryContentType, encoded); + } + + return ResponseUtils.CreateResponse(HttpStatusCode.NoContent); + }); + + using (var httpClient = new HttpClient(mockHttpHandler.Object)) + { + var longPollingTransport = new LongPollingTransport(httpClient, new LoggerFactory()); + try + { + var connectionToTransport = Channel.CreateUnbounded(); + var transportToConnection = Channel.CreateUnbounded(); + var channelConnection = new ChannelConnection(connectionToTransport, transportToConnection); + + // Start the transport + await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), channelConnection); + + // Transport should fail + var ex = await Assert.ThrowsAsync(() => longPollingTransport.Running.OrTimeout()); + Assert.Equal($"Format indicator 'T' does not match format determined by Content-Type '{MessageFormatter.BinaryContentType}'", ex.Message); + } + finally + { + await longPollingTransport.StopAsync(); + } + } + } + + [Fact] + public async Task LongPollingTransportDispatchesMessagesReceivedFromPoll() + { + var message1Payload = new byte[] { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' }; + var message2Payload = new byte[] { (byte)'W', (byte)'o', (byte)'r', (byte)'l', (byte)'d' }; + var encoded = Enumerable.SelectMany(new[] { + new byte[] { (byte)'B', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00 }, message1Payload, + new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x01 }, message2Payload + }, b => b).ToArray(); + + var firstCall = true; + var mockHttpHandler = new Mock(); + var sentRequests = new List(); + mockHttpHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .Returns(async (request, cancellationToken) => + { + sentRequests.Add(request); + + await Task.Yield(); + + if (firstCall) + { + firstCall = false; + return ResponseUtils.CreateResponse(HttpStatusCode.OK, MessageFormatter.BinaryContentType, encoded); + } + + return ResponseUtils.CreateResponse(HttpStatusCode.NoContent); + }); + + using (var httpClient = new HttpClient(mockHttpHandler.Object)) + { + var longPollingTransport = new LongPollingTransport(httpClient, new LoggerFactory()); + try + { + var connectionToTransport = Channel.CreateUnbounded(); + var transportToConnection = Channel.CreateUnbounded(); + var channelConnection = new ChannelConnection(connectionToTransport, transportToConnection); + + // Start the transport + await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), channelConnection); + + // Wait for the transport to finish + await longPollingTransport.Running.OrTimeout(); + + // Pull Messages out of the channel + var messages = new List(); + while (await transportToConnection.In.WaitToReadAsync()) + { + while (transportToConnection.In.TryRead(out var message)) + { + messages.Add(message); + } + } + + // Check the provided request + Assert.Equal(2, sentRequests.Count); + Assert.Equal("?supportsBinary=true", sentRequests[0].RequestUri.Query); + + // Check the messages received + Assert.Equal(2, messages.Count); + Assert.Equal(MessageType.Text, messages[0].Type); + Assert.Equal(message1Payload, messages[0].Payload); + Assert.Equal(MessageType.Binary, messages[1].Type); + Assert.Equal(message2Payload, messages[1].Payload); + } + finally + { + await longPollingTransport.StopAsync(); + } + } + } + [Fact] public async Task LongPollingTransportSendsAvailableMessagesWhenTheyArrive() { @@ -220,7 +345,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests // Build a new request object, but convert the entire payload to string sentRequests.Add(await request.Content.ReadAsByteArrayAsync()); } - return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) }; + return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) diff --git a/test/Microsoft.AspNetCore.Sockets.Client.Tests/ResponseUtils.cs b/test/Microsoft.AspNetCore.Sockets.Client.Tests/ResponseUtils.cs new file mode 100644 index 0000000000..c5610f20bc --- /dev/null +++ b/test/Microsoft.AspNetCore.Sockets.Client.Tests/ResponseUtils.cs @@ -0,0 +1,31 @@ +// 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.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Sockets.Internal.Formatters; + +namespace Microsoft.AspNetCore.Client.Tests +{ + internal static class ResponseUtils + { + public static HttpResponseMessage CreateResponse(HttpStatusCode statusCode) => + CreateResponse(statusCode, MessageFormatter.TextContentType, string.Empty); + + public static HttpResponseMessage CreateResponse(HttpStatusCode statusCode, string contentType, string payload) => + CreateResponse(statusCode, contentType, new StringContent(payload)); + + public static HttpResponseMessage CreateResponse(HttpStatusCode statusCode, string contentType, byte[] payload) => + CreateResponse(statusCode, contentType, new ByteArrayContent(payload)); + + public static HttpResponseMessage CreateResponse(HttpStatusCode statusCode, string contentType, HttpContent payload) + { + payload.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + return new HttpResponseMessage(statusCode) + { + Content = payload + }; + } + } +} diff --git a/test/Microsoft.AspNetCore.Sockets.Tests/LongPollingTests.cs b/test/Microsoft.AspNetCore.Sockets.Tests/LongPollingTests.cs index a5830dbfa3..32ec726f14 100644 --- a/test/Microsoft.AspNetCore.Sockets.Tests/LongPollingTests.cs +++ b/test/Microsoft.AspNetCore.Sockets.Tests/LongPollingTests.cs @@ -1,6 +1,7 @@ // 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.IO; using System.IO.Pipelines; using System.Text; @@ -51,11 +52,17 @@ namespace Microsoft.AspNetCore.Sockets.Tests Assert.Equal("T11:T:Hello World;", Encoding.UTF8.GetString(ms.ToArray())); } - [Fact] - public async Task MultipleFramesSentAsSingleResponse() + [Theory] + [InlineData(MessageFormat.Text, "T5:T:Hello;1:T: ;5:T:World;")] + [InlineData(MessageFormat.Binary, "QgAAAAAAAAAFAEhlbGxvAAAAAAAAAAEAIAAAAAAAAAAFAFdvcmxk")] + public async Task MultipleFramesSentAsSingleResponse(MessageFormat format, string expectedPayload) { var channel = Channel.CreateUnbounded(); var context = new DefaultHttpContext(); + if (format == MessageFormat.Binary) + { + context.Request.QueryString = QueryString.Create("supportsBinary", "true"); + } var poll = new LongPollingTransport(channel, new LoggerFactory()); var ms = new MemoryStream(); context.Response.Body = ms; @@ -78,7 +85,12 @@ namespace Microsoft.AspNetCore.Sockets.Tests await poll.ProcessRequestAsync(context, context.RequestAborted); Assert.Equal(200, context.Response.StatusCode); - Assert.Equal("T5:T:Hello;1:T: ;5:T:World;", Encoding.UTF8.GetString(ms.ToArray())); + + var payload = ms.ToArray(); + var encoded = format == MessageFormat.Binary ? + Convert.ToBase64String(payload) : + Encoding.UTF8.GetString(payload); + Assert.Equal(expectedPayload, encoded); } } }