add support for binary format in /poll (#303)
This commit is contained in:
parent
4a8a28ec16
commit
1732ac5760
|
|
@ -53,8 +53,9 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
|
|
||||||
_application = application;
|
_application = application;
|
||||||
|
|
||||||
// Start sending and polling
|
// Start sending and polling (ask for binary if the server supports it)
|
||||||
_poller = Poll(Utils.AppendPath(url, "poll"), _transportCts.Token);
|
var pollUrl = Utils.AppendQueryString(Utils.AppendPath(url, "poll"), "supportsBinary=true");
|
||||||
|
_poller = Poll(pollUrl, _transportCts.Token);
|
||||||
_sender = SendMessages(Utils.AppendPath(url, "send"), _transportCts.Token);
|
_sender = SendMessages(Utils.AppendPath(url, "send"), _transportCts.Token);
|
||||||
|
|
||||||
Running = Task.WhenAll(_sender, _poller).ContinueWith(t =>
|
Running = Task.WhenAll(_sender, _poller).ContinueWith(t =>
|
||||||
|
|
@ -110,11 +111,13 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Received messages from the server");
|
_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.
|
// Until Pipeline starts natively supporting BytesReader, this is the easiest way to do this.
|
||||||
var payload = await response.Content.ReadAsByteArrayAsync();
|
var payload = await response.Content.ReadAsByteArrayAsync();
|
||||||
if (payload.Length > 0)
|
if (payload.Length > 0)
|
||||||
{
|
{
|
||||||
var messages = ParsePayload(payload);
|
var messages = ParsePayload(payload, messageFormat);
|
||||||
|
|
||||||
foreach (var message in messages)
|
foreach (var message in messages)
|
||||||
{
|
{
|
||||||
|
|
@ -148,10 +151,13 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
_logger.LogInformation("Receive loop stopped");
|
_logger.LogInformation("Receive loop stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
private IList<Message> ParsePayload(byte[] payload)
|
private IList<Message> ParsePayload(byte[] payload, MessageFormat messageFormat)
|
||||||
{
|
{
|
||||||
var reader = new BytesReader(payload);
|
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);
|
reader.Advance(1);
|
||||||
|
|
||||||
_parser.Reset();
|
_parser.Reset();
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,19 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder = new UriBuilder(url);
|
var builder = new UriBuilder(url);
|
||||||
|
var newQueryString = builder.Query;
|
||||||
if (!string.IsNullOrEmpty(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;
|
return builder.Uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters
|
||||||
_binaryParser.TryParseMessage(ref buffer, out message);
|
_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)
|
// 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)
|
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));
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,10 @@ namespace Microsoft.AspNetCore.Sockets.Transports
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add support for binary protocol
|
// REVIEW: We could also use the 'Accept' header, in theory...
|
||||||
var messageFormat = MessageFormat.Text;
|
var messageFormat = string.Equals(context.Request.Query["supportsBinary"], "true", StringComparison.OrdinalIgnoreCase) ?
|
||||||
|
MessageFormat.Binary :
|
||||||
|
MessageFormat.Text;
|
||||||
context.Response.ContentType = MessageFormatter.GetContentType(messageFormat);
|
context.Response.ContentType = MessageFormatter.GetContentType(messageFormat);
|
||||||
|
|
||||||
var writer = context.Response.Body.AsPipelineWriter();
|
var writer = context.Response.Body.AsPipelineWriter();
|
||||||
|
|
|
||||||
|
|
@ -67,19 +67,22 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
yield return new object[] { new WebSocketsTransport() };
|
yield return new object[] { new Func<ILoggerFactory, ITransport>(loggerFactory => new WebSocketsTransport(loggerFactory)) };
|
||||||
yield return new object[] { new LongPollingTransport(new HttpClient()) };
|
yield return new object[] { new Func<ILoggerFactory, ITransport>(loggerFactory => new LongPollingTransport(new HttpClient(), loggerFactory)) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[ConditionalTheory]
|
[ConditionalTheory]
|
||||||
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2, SkipReason = "No WebSockets Client for this platform")]
|
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2, SkipReason = "No WebSockets Client for this platform")]
|
||||||
[MemberData(nameof(Transports))]
|
[MemberData(nameof(Transports))]
|
||||||
public async Task ConnectionCanSendAndReceiveMessages(ITransport transport)
|
public async Task ConnectionCanSendAndReceiveMessages(Func<ILoggerFactory, ITransport> transportFactory)
|
||||||
{
|
{
|
||||||
const string message = "Major Key";
|
const string message = "Major Key";
|
||||||
var baseUrl = _serverFixture.BaseUrl;
|
var baseUrl = _serverFixture.BaseUrl;
|
||||||
var loggerFactory = new LoggerFactory();
|
var loggerFactory = new LoggerFactory();
|
||||||
|
loggerFactory.AddXUnit(_output, LogLevel.Trace);
|
||||||
|
|
||||||
|
var transport = transportFactory(loggerFactory);
|
||||||
|
|
||||||
using (var httpClient = new HttpClient())
|
using (var httpClient = new HttpClient())
|
||||||
{
|
{
|
||||||
|
|
@ -119,8 +122,8 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
yield return new object[] { new string('A', 5 * 1024)};
|
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 * 1024 + 32) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
@ -15,6 +9,13 @@ using System.Text;
|
||||||
using System.Text.Formatting;
|
using System.Text.Formatting;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
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;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
|
|
@ -265,7 +266,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
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))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
@ -438,7 +439,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
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))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
@ -479,7 +480,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
{
|
{
|
||||||
sendTcs.SetResult(await request.Content.ReadAsByteArrayAsync());
|
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))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
@ -555,9 +556,9 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
if (request.RequestUri.AbsolutePath.EndsWith("/send"))
|
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))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
@ -589,7 +590,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
{
|
{
|
||||||
content = "T2:T:42;";
|
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))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Client.Tests;
|
||||||
using Microsoft.AspNetCore.SignalR.Tests.Common;
|
using Microsoft.AspNetCore.SignalR.Tests.Common;
|
||||||
using Microsoft.AspNetCore.Sockets.Client;
|
using Microsoft.AspNetCore.Sockets.Client;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -41,7 +42,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
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))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
@ -73,7 +74,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
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))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
@ -120,7 +121,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
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))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
@ -152,7 +153,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
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))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -10,13 +11,16 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks.Channels;
|
using System.Threading.Tasks.Channels;
|
||||||
using Microsoft.AspNetCore.SignalR.Tests.Common;
|
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;
|
||||||
|
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Moq.Protected;
|
using Moq.Protected;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
namespace Microsoft.AspNetCore.Client.Tests
|
||||||
{
|
{
|
||||||
public class LongPollingTransportTests
|
public class LongPollingTransportTests
|
||||||
{
|
{
|
||||||
|
|
@ -29,7 +33,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(string.Empty) };
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||||
});
|
});
|
||||||
|
|
||||||
Task transportActiveTask;
|
Task transportActiveTask;
|
||||||
|
|
@ -67,7 +71,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
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))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
@ -100,7 +104,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
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))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
@ -136,7 +140,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
var statusCode = request.RequestUri.AbsolutePath.EndsWith("send")
|
var statusCode = request.RequestUri.AbsolutePath.EndsWith("send")
|
||||||
? HttpStatusCode.InternalServerError
|
? HttpStatusCode.InternalServerError
|
||||||
: HttpStatusCode.OK;
|
: HttpStatusCode.OK;
|
||||||
return new HttpResponseMessage(statusCode) { Content = new StringContent(string.Empty) };
|
return ResponseUtils.CreateResponse(statusCode);
|
||||||
});
|
});
|
||||||
|
|
||||||
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
@ -177,7 +181,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
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))
|
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<HttpMessageHandler>();
|
||||||
|
var sentRequests = new List<HttpRequestMessage>();
|
||||||
|
mockHttpHandler.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||||
|
.Returns<HttpRequestMessage, CancellationToken>(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<SendMessage>();
|
||||||
|
var transportToConnection = Channel.CreateUnbounded<Message>();
|
||||||
|
var channelConnection = new ChannelConnection<SendMessage, Message>(connectionToTransport, transportToConnection);
|
||||||
|
|
||||||
|
// Start the transport
|
||||||
|
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), channelConnection);
|
||||||
|
|
||||||
|
// Transport should fail
|
||||||
|
var ex = await Assert.ThrowsAsync<FormatException>(() => 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<HttpMessageHandler>();
|
||||||
|
var sentRequests = new List<HttpRequestMessage>();
|
||||||
|
mockHttpHandler.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||||
|
.Returns<HttpRequestMessage, CancellationToken>(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<SendMessage>();
|
||||||
|
var transportToConnection = Channel.CreateUnbounded<Message>();
|
||||||
|
var channelConnection = new ChannelConnection<SendMessage, Message>(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<Message>();
|
||||||
|
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]
|
[Fact]
|
||||||
public async Task LongPollingTransportSendsAvailableMessagesWhenTheyArrive()
|
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
|
// Build a new request object, but convert the entire payload to string
|
||||||
sentRequests.Add(await request.Content.ReadAsByteArrayAsync());
|
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))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -51,11 +52,17 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
Assert.Equal("T11:T:Hello World;", Encoding.UTF8.GetString(ms.ToArray()));
|
Assert.Equal("T11:T:Hello World;", Encoding.UTF8.GetString(ms.ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Theory]
|
||||||
public async Task MultipleFramesSentAsSingleResponse()
|
[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<Message>();
|
var channel = Channel.CreateUnbounded<Message>();
|
||||||
var context = new DefaultHttpContext();
|
var context = new DefaultHttpContext();
|
||||||
|
if (format == MessageFormat.Binary)
|
||||||
|
{
|
||||||
|
context.Request.QueryString = QueryString.Create("supportsBinary", "true");
|
||||||
|
}
|
||||||
var poll = new LongPollingTransport(channel, new LoggerFactory());
|
var poll = new LongPollingTransport(channel, new LoggerFactory());
|
||||||
var ms = new MemoryStream();
|
var ms = new MemoryStream();
|
||||||
context.Response.Body = ms;
|
context.Response.Body = ms;
|
||||||
|
|
@ -78,7 +85,12 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
await poll.ProcessRequestAsync(context, context.RequestAborted);
|
await poll.ProcessRequestAsync(context, context.RequestAborted);
|
||||||
|
|
||||||
Assert.Equal(200, context.Response.StatusCode);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue