Detect availability of web sockets on client and server (#1682)
This commit is contained in:
parent
71c2ddd155
commit
3f84eee116
|
|
@ -269,6 +269,52 @@ describe("HttpConnection", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("does not select ServerSentEvents transport when not available in environment", async (done) => {
|
||||
const serverSentEventsTransport = { transport: "ServerSentEvents", transferFormats: [ "Text" ] };
|
||||
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", (r) => ({ connectionId: "42", availableTransports: [serverSentEventsTransport] })),
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
fail();
|
||||
done();
|
||||
} catch (e) {
|
||||
// ServerSentEvents is only transport returned from server but is not selected
|
||||
// because there is no support in the environment, leading to the following error
|
||||
expect(e.message).toBe("Unable to initialize any of the available transports.");
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it("does not select WebSockets transport when not available in environment", async (done) => {
|
||||
const webSocketsTransport = { transport: "WebSockets", transferFormats: [ "Text" ] };
|
||||
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", (r) => ({ connectionId: "42", availableTransports: [webSocketsTransport] })),
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
fail();
|
||||
done();
|
||||
} catch (e) {
|
||||
// WebSockets is only transport returned from server but is not selected
|
||||
// because there is no support in the environment, leading to the following error
|
||||
expect(e.message).toBe("Unable to initialize any of the available transports.");
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
describe(".constructor", () => {
|
||||
it("throws if no Url is provided", async () => {
|
||||
// Force TypeScript to let us call the constructor incorrectly :)
|
||||
|
|
|
|||
|
|
@ -160,8 +160,13 @@ export class HttpConnection implements IConnection {
|
|||
const transferFormats = endpoint.transferFormats.map((s) => TransferFormat[s]);
|
||||
if (!requestedTransport || transport === requestedTransport) {
|
||||
if (transferFormats.indexOf(requestedTransferFormat) >= 0) {
|
||||
this.logger.log(LogLevel.Trace, `Selecting transport '${TransportType[transport]}'`);
|
||||
return transport;
|
||||
if ((transport === TransportType.WebSockets && typeof WebSocket === "undefined") ||
|
||||
(transport === TransportType.ServerSentEvents && typeof EventSource === "undefined")) {
|
||||
this.logger.log(LogLevel.Trace, `Skipping transport '${TransportType[transport]}' because it is not supported in your environment.'`);
|
||||
} else {
|
||||
this.logger.log(LogLevel.Trace, `Selecting transport '${TransportType[transport]}'`);
|
||||
return transport;
|
||||
}
|
||||
} else {
|
||||
this.logger.log(LogLevel.Trace, `Skipping transport '${TransportType[transport]}' because it does not support the requested transfer format '${TransferFormat[requestedTransferFormat]}'.`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
|||
private static readonly Action<ILogger, string, Exception> _transportFailed =
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(29, "TransportFailed"), "Skipping transport {TransportName} because it failed to initialize.");
|
||||
|
||||
private static readonly Action<ILogger, Exception> _webSocketsNotSupportedByOperatingSystem =
|
||||
LoggerMessage.Define(LogLevel.Debug, new EventId(30, "WebSocketsNotSupportedByOperatingSystem"), "Skipping WebSockets because they are not supported by the operating system.");
|
||||
|
||||
public static void HttpConnectionStarting(ILogger logger)
|
||||
{
|
||||
_httpConnectionStarting(logger, null);
|
||||
|
|
@ -260,6 +263,11 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
|||
_transportFailed(logger, transport.ToString(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void WebSocketsNotSupportedByOperatingSystem(ILogger logger)
|
||||
{
|
||||
_webSocketsNotSupportedByOperatingSystem(logger, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.IO;
|
|||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
|
@ -25,6 +26,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
|||
public partial class HttpConnection : IConnection
|
||||
{
|
||||
private static readonly TimeSpan HttpClientTimeout = TimeSpan.FromSeconds(120);
|
||||
private static readonly Version Windows8Version = new Version(6, 2);
|
||||
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILogger _logger;
|
||||
|
|
@ -203,6 +205,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
|||
var connectUrl = Url;
|
||||
if (_requestedTransportType == TransportType.WebSockets)
|
||||
{
|
||||
// if we're running on Windows 7 this could throw because the OS does not support web sockets
|
||||
Log.StartingTransport(_logger, _requestedTransportType, connectUrl);
|
||||
await StartTransport(connectUrl, _requestedTransportType, transferFormat);
|
||||
}
|
||||
|
|
@ -233,6 +236,12 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
|||
continue;
|
||||
}
|
||||
|
||||
if (transportType == TransportType.WebSockets && !IsWebSocketsSupported())
|
||||
{
|
||||
Log.WebSocketsNotSupportedByOperatingSystem(_logger);
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if ((transportType & _requestedTransportType) == 0)
|
||||
|
|
@ -711,6 +720,26 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
|||
}
|
||||
}
|
||||
|
||||
private static bool IsWebSocketsSupported()
|
||||
{
|
||||
#if NETCOREAPP2_1
|
||||
// .NET Core 2.1 and above has a managed implementation
|
||||
return true;
|
||||
#else
|
||||
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
if (!isWindows)
|
||||
{
|
||||
// Assume other OSes have websockets
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Windows 8 and above has websockets
|
||||
return Environment.OSVersion.Version >= Windows8Version;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Internal because it's used by logging to avoid ToStringing prematurely.
|
||||
internal enum ConnectionState
|
||||
{
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ namespace Microsoft.AspNetCore.Sockets
|
|||
logScope.ConnectionId = connection.ConnectionId;
|
||||
|
||||
// Get the bytes for the connection id
|
||||
var negotiateResponseBuffer = Encoding.UTF8.GetBytes(GetNegotiatePayload(connection.ConnectionId, options));
|
||||
var negotiateResponseBuffer = Encoding.UTF8.GetBytes(GetNegotiatePayload(connection.ConnectionId, context, options));
|
||||
|
||||
Log.NegotiationRequest(_logger);
|
||||
|
||||
|
|
@ -377,7 +377,7 @@ namespace Microsoft.AspNetCore.Sockets
|
|||
return context.Response.Body.WriteAsync(negotiateResponseBuffer, 0, negotiateResponseBuffer.Length);
|
||||
}
|
||||
|
||||
private static string GetNegotiatePayload(string connectionId, HttpSocketOptions options)
|
||||
private static string GetNegotiatePayload(string connectionId, HttpContext context, HttpSocketOptions options)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
using (var jsonWriter = new JsonTextWriter(new StringWriter(sb)))
|
||||
|
|
@ -387,18 +387,25 @@ namespace Microsoft.AspNetCore.Sockets
|
|||
jsonWriter.WriteValue(connectionId);
|
||||
jsonWriter.WritePropertyName("availableTransports");
|
||||
jsonWriter.WriteStartArray();
|
||||
if ((options.Transports & TransportType.WebSockets) != 0)
|
||||
|
||||
if (ServerHasWebSockets(context.Features))
|
||||
{
|
||||
WriteTransport(jsonWriter, nameof(TransportType.WebSockets), TransferFormat.Text | TransferFormat.Binary);
|
||||
if ((options.Transports & TransportType.WebSockets) != 0)
|
||||
{
|
||||
WriteTransport(jsonWriter, nameof(TransportType.WebSockets), TransferFormat.Text | TransferFormat.Binary);
|
||||
}
|
||||
}
|
||||
|
||||
if ((options.Transports & TransportType.ServerSentEvents) != 0)
|
||||
{
|
||||
WriteTransport(jsonWriter, nameof(TransportType.ServerSentEvents), TransferFormat.Text);
|
||||
}
|
||||
|
||||
if ((options.Transports & TransportType.LongPolling) != 0)
|
||||
{
|
||||
WriteTransport(jsonWriter, nameof(TransportType.LongPolling), TransferFormat.Text | TransferFormat.Binary);
|
||||
}
|
||||
|
||||
jsonWriter.WriteEndArray();
|
||||
jsonWriter.WriteEndObject();
|
||||
}
|
||||
|
|
@ -406,6 +413,11 @@ namespace Microsoft.AspNetCore.Sockets
|
|||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static bool ServerHasWebSockets(IFeatureCollection features)
|
||||
{
|
||||
return features.Get<IHttpWebSocketFeature>() != null;
|
||||
}
|
||||
|
||||
private static void WriteTransport(JsonWriter writer, string transportName, TransferFormat supportedTransferFormats)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
|||
var dispatcher = new HttpConnectionDispatcher(manager, loggerFactory);
|
||||
var context = new DefaultHttpContext();
|
||||
context.Features.Set<IHttpResponseFeature>(new ResponseFeature());
|
||||
context.Features.Set<IHttpWebSocketFeature>(new TestWebSocketConnectionFeature());
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<TestEndPoint>();
|
||||
services.AddOptions();
|
||||
|
|
@ -1225,6 +1226,31 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NegotiateDoesNotReturnWebSocketsWhenNotAvailable()
|
||||
{
|
||||
using (StartLog(out var loggerFactory, LogLevel.Debug))
|
||||
{
|
||||
var manager = CreateConnectionManager(loggerFactory);
|
||||
var dispatcher = new HttpConnectionDispatcher(manager, loggerFactory);
|
||||
var context = new DefaultHttpContext();
|
||||
context.Features.Set<IHttpResponseFeature>(new ResponseFeature());
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<TestEndPoint>();
|
||||
services.AddOptions();
|
||||
var ms = new MemoryStream();
|
||||
context.Request.Path = "/foo";
|
||||
context.Request.Method = "POST";
|
||||
context.Response.Body = ms;
|
||||
await dispatcher.ExecuteNegotiateAsync(context, new HttpSocketOptions { Transports = TransportType.WebSockets });
|
||||
|
||||
var negotiateResponse = JsonConvert.DeserializeObject<JObject>(Encoding.UTF8.GetString(ms.ToArray()));
|
||||
var availableTransports = (JArray)negotiateResponse["availableTransports"];
|
||||
|
||||
Assert.Empty(availableTransports);
|
||||
}
|
||||
}
|
||||
|
||||
private class RejectHandler : TestAuthenticationHandler
|
||||
{
|
||||
protected override bool ShouldAccept => false;
|
||||
|
|
|
|||
Loading…
Reference in New Issue