Fix SignalR+WebSockets on WASM (#25922)

* Fix SignalR+WebSockets on WASM

* fb

* move stuff

* select
This commit is contained in:
Brennan 2020-09-17 11:30:31 -07:00 committed by GitHub
parent e51468db03
commit 899deacb22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 86 additions and 47 deletions

View File

@ -7,7 +7,9 @@ using BasicTestApp;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting; using Microsoft.AspNetCore.E2ETesting;
using Microsoft.AspNetCore.Http.Connections;
using OpenQA.Selenium; using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using TestServer; using TestServer;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -38,13 +40,28 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
} }
[Fact] [Fact]
public void SignalRClientWorks() public void SignalRClientWorksWithLongPolling()
{ {
Browser.FindElement(By.Id("hub-url")).SendKeys( Browser.FindElement(By.Id("hub-url")).SendKeys(
new Uri(_apiServerFixture.RootUri, "/subdir/chathub").AbsoluteUri); new Uri(_apiServerFixture.RootUri, "/subdir/chathub").AbsoluteUri);
var target = new SelectElement(Browser.FindElement(By.Id("transport-type")));
target.SelectByText("LongPolling");
Browser.FindElement(By.Id("hub-connect")).Click(); Browser.FindElement(By.Id("hub-connect")).Click();
Browser.Equal("SignalR Client: Echo", Browser.Equal("SignalR Client: Echo LongPolling",
() => Browser.FindElements(By.CssSelector("li")).FirstOrDefault()?.Text);
}
[Fact]
public void SignalRClientWorksWithWebSockets()
{
Browser.FindElement(By.Id("hub-url")).SendKeys(
new Uri(_apiServerFixture.RootUri, "/subdir/chathub").AbsoluteUri);
var target = new SelectElement(Browser.FindElement(By.Id("transport-type")));
target.SelectByText("WebSockets");
Browser.FindElement(By.Id("hub-connect")).Click();
Browser.Equal("SignalR Client: Echo WebSockets",
() => Browser.FindElements(By.CssSelector("li")).FirstOrDefault()?.Text); () => Browser.FindElements(By.CssSelector("li")).FirstOrDefault()?.Text);
} }
} }

View File

@ -1,4 +1,5 @@
@using Microsoft.AspNetCore.SignalR.Client @using Microsoft.AspNetCore.SignalR.Client
@using Microsoft.AspNetCore.Http.Connections
<h1 id="signalr-client">SignalR Client</h1> <h1 id="signalr-client">SignalR Client</h1>
@ -7,6 +8,10 @@
<p> <p>
Hub URL: Hub URL:
<input id="hub-url" @bind="hubUrl" /> <input id="hub-url" @bind="hubUrl" />
<select id="transport-type" @bind="transportType">
<option value=@HttpTransportType.LongPolling>LongPolling</option>
<option value=@HttpTransportType.WebSockets>WebSockets</option>
</select>
<button id="hub-connect" @onclick="Connect">Connect</button> <button id="hub-connect" @onclick="Connect">Connect</button>
</p> </p>
@ -21,13 +26,14 @@
@code { @code {
private string hubUrl; private string hubUrl;
private HttpTransportType transportType;
private HubConnection hubConnection; private HubConnection hubConnection;
private List<string> messages = new List<string>(); private List<string> messages = new List<string>();
protected async Task Connect() protected async Task Connect()
{ {
hubConnection = new HubConnectionBuilder() hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl) .WithUrl(hubUrl, transportType)
.Build(); .Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) => hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
@ -38,7 +44,7 @@
}); });
await hubConnection.StartAsync(); await hubConnection.StartAsync();
await hubConnection.SendAsync("SendMessage", "SignalR Client", "Echo"); await hubConnection.SendAsync("SendMessage", "SignalR Client", $"Echo {transportType}");
} }
public bool IsConnected => public bool IsConnected =>

View File

@ -39,8 +39,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
{ {
return new WebSocketsTransport(_httpConnectionOptions, _loggerFactory, _accessTokenProvider); return new WebSocketsTransport(_httpConnectionOptions, _loggerFactory, _accessTokenProvider);
} }
catch (PlatformNotSupportedException) catch (PlatformNotSupportedException ex)
{ {
Log.TransportNotSupported(_loggerFactory.CreateLogger<DefaultTransportFactory>(), HttpTransportType.WebSockets, ex);
_websocketsSupported = false; _websocketsSupported = false;
} }
} }
@ -59,5 +60,16 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
throw new InvalidOperationException("No requested transports available on the server."); throw new InvalidOperationException("No requested transports available on the server.");
} }
private static class Log
{
private static readonly Action<ILogger, HttpTransportType, Exception> _transportNotSupported =
LoggerMessage.Define<HttpTransportType>(LogLevel.Debug, new EventId(1, "TransportNotSupported"), "Transport '{TransportType}' is not supported.");
public static void TransportNotSupported(ILogger logger, HttpTransportType transportType, Exception ex)
{
_transportNotSupported(logger, transportType, ex);
}
}
} }
} }

View File

@ -37,66 +37,70 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
public WebSocketsTransport(HttpConnectionOptions httpConnectionOptions, ILoggerFactory loggerFactory, Func<Task<string>> accessTokenProvider) public WebSocketsTransport(HttpConnectionOptions httpConnectionOptions, ILoggerFactory loggerFactory, Func<Task<string>> accessTokenProvider)
{ {
_webSocket = new ClientWebSocket(); _webSocket = new ClientWebSocket();
_isRunningInBrowser = Utils.IsRunningInBrowser();
// Full Framework will throw when trying to set the User-Agent header // ClientWebSocketOptions throws PNSE when accessing and setting properties
// So avoid setting it in netstandard2.0 and only set it in netstandard2.1 and higher if (!_isRunningInBrowser)
{
// Full Framework will throw when trying to set the User-Agent header
// So avoid setting it in netstandard2.0 and only set it in netstandard2.1 and higher
#if !NETSTANDARD2_0 #if !NETSTANDARD2_0
_webSocket.Options.SetRequestHeader("User-Agent", Constants.UserAgentHeader.ToString()); _webSocket.Options.SetRequestHeader("User-Agent", Constants.UserAgentHeader.ToString());
#else #else
// Set an alternative user agent header on Full framework // Set an alternative user agent header on Full framework
_webSocket.Options.SetRequestHeader("X-SignalR-User-Agent", Constants.UserAgentHeader.ToString()); _webSocket.Options.SetRequestHeader("X-SignalR-User-Agent", Constants.UserAgentHeader.ToString());
#endif #endif
if (httpConnectionOptions != null) if (httpConnectionOptions != null)
{
if (httpConnectionOptions.Headers != null)
{ {
foreach (var header in httpConnectionOptions.Headers) if (httpConnectionOptions.Headers != null)
{ {
_webSocket.Options.SetRequestHeader(header.Key, header.Value); foreach (var header in httpConnectionOptions.Headers)
{
_webSocket.Options.SetRequestHeader(header.Key, header.Value);
}
} }
if (httpConnectionOptions.Cookies != null)
{
_webSocket.Options.Cookies = httpConnectionOptions.Cookies;
}
if (httpConnectionOptions.ClientCertificates != null)
{
_webSocket.Options.ClientCertificates.AddRange(httpConnectionOptions.ClientCertificates);
}
if (httpConnectionOptions.Credentials != null)
{
_webSocket.Options.Credentials = httpConnectionOptions.Credentials;
}
if (httpConnectionOptions.Proxy != null)
{
_webSocket.Options.Proxy = httpConnectionOptions.Proxy;
}
if (httpConnectionOptions.UseDefaultCredentials != null)
{
_webSocket.Options.UseDefaultCredentials = httpConnectionOptions.UseDefaultCredentials.Value;
}
httpConnectionOptions.WebSocketConfiguration?.Invoke(_webSocket.Options);
} }
if (httpConnectionOptions.Cookies != null)
{
_webSocket.Options.Cookies = httpConnectionOptions.Cookies;
}
if (httpConnectionOptions.ClientCertificates != null) // Set this header so the server auth middleware will set an Unauthorized instead of Redirect status code
{ // See: https://github.com/aspnet/Security/blob/ff9f145a8e89c9756ea12ff10c6d47f2f7eb345f/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieAuthenticationEvents.cs#L42
_webSocket.Options.ClientCertificates.AddRange(httpConnectionOptions.ClientCertificates); _webSocket.Options.SetRequestHeader("X-Requested-With", "XMLHttpRequest");
}
if (httpConnectionOptions.Credentials != null)
{
_webSocket.Options.Credentials = httpConnectionOptions.Credentials;
}
if (httpConnectionOptions.Proxy != null)
{
_webSocket.Options.Proxy = httpConnectionOptions.Proxy;
}
if (httpConnectionOptions.UseDefaultCredentials != null)
{
_webSocket.Options.UseDefaultCredentials = httpConnectionOptions.UseDefaultCredentials.Value;
}
httpConnectionOptions.WebSocketConfiguration?.Invoke(_webSocket.Options);
_closeTimeout = httpConnectionOptions.CloseTimeout;
} }
// Set this header so the server auth middleware will set an Unauthorized instead of Redirect status code _closeTimeout = httpConnectionOptions?.CloseTimeout ?? default;
// See: https://github.com/aspnet/Security/blob/ff9f145a8e89c9756ea12ff10c6d47f2f7eb345f/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieAuthenticationEvents.cs#L42
_webSocket.Options.SetRequestHeader("X-Requested-With", "XMLHttpRequest");
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<WebSocketsTransport>(); _logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<WebSocketsTransport>();
// Ignore the HttpConnectionOptions access token provider. We were given an updated delegate from the HttpConnection. // Ignore the HttpConnectionOptions access token provider. We were given an updated delegate from the HttpConnection.
_accessTokenProvider = accessTokenProvider; _accessTokenProvider = accessTokenProvider;
_isRunningInBrowser = Utils.IsRunningInBrowser();
} }
public async Task StartAsync(Uri url, TransferFormat transferFormat, CancellationToken cancellationToken = default) public async Task StartAsync(Uri url, TransferFormat transferFormat, CancellationToken cancellationToken = default)