diff --git a/src/Components/test/E2ETest/Tests/SignalRClientTest.cs b/src/Components/test/E2ETest/Tests/SignalRClientTest.cs index aa4e82bb1d..e5aa385cc1 100644 --- a/src/Components/test/E2ETest/Tests/SignalRClientTest.cs +++ b/src/Components/test/E2ETest/Tests/SignalRClientTest.cs @@ -7,7 +7,9 @@ using BasicTestApp; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Http.Connections; using OpenQA.Selenium; +using OpenQA.Selenium.Support.UI; using TestServer; using Xunit; using Xunit.Abstractions; @@ -38,13 +40,28 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests } [Fact] - public void SignalRClientWorks() + public void SignalRClientWorksWithLongPolling() { 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("LongPolling"); 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); } } diff --git a/src/Components/test/testassets/BasicTestApp/SignalRClientComponent.razor b/src/Components/test/testassets/BasicTestApp/SignalRClientComponent.razor index 3f0869ee2d..a8fae272ab 100644 --- a/src/Components/test/testassets/BasicTestApp/SignalRClientComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/SignalRClientComponent.razor @@ -1,4 +1,5 @@ @using Microsoft.AspNetCore.SignalR.Client +@using Microsoft.AspNetCore.Http.Connections

SignalR Client

@@ -7,6 +8,10 @@

Hub URL: +

@@ -21,13 +26,14 @@ @code { private string hubUrl; + private HttpTransportType transportType; private HubConnection hubConnection; private List messages = new List(); protected async Task Connect() { hubConnection = new HubConnectionBuilder() - .WithUrl(hubUrl) + .WithUrl(hubUrl, transportType) .Build(); hubConnection.On("ReceiveMessage", (user, message) => @@ -38,7 +44,7 @@ }); await hubConnection.StartAsync(); - await hubConnection.SendAsync("SendMessage", "SignalR Client", "Echo"); + await hubConnection.SendAsync("SendMessage", "SignalR Client", $"Echo {transportType}"); } public bool IsConnected => diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/DefaultTransportFactory.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/DefaultTransportFactory.cs index 6e66673ad9..d6295d992a 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/DefaultTransportFactory.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/DefaultTransportFactory.cs @@ -39,8 +39,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { return new WebSocketsTransport(_httpConnectionOptions, _loggerFactory, _accessTokenProvider); } - catch (PlatformNotSupportedException) + catch (PlatformNotSupportedException ex) { + Log.TransportNotSupported(_loggerFactory.CreateLogger(), HttpTransportType.WebSockets, ex); _websocketsSupported = false; } } @@ -59,5 +60,16 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal throw new InvalidOperationException("No requested transports available on the server."); } + + private static class Log + { + private static readonly Action _transportNotSupported = + LoggerMessage.Define(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); + } + } } } diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs index e0b8d08aa0..02c3a9748e 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs @@ -37,66 +37,70 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal public WebSocketsTransport(HttpConnectionOptions httpConnectionOptions, ILoggerFactory loggerFactory, Func> accessTokenProvider) { _webSocket = new ClientWebSocket(); + _isRunningInBrowser = Utils.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 + // ClientWebSocketOptions throws PNSE when accessing and setting properties + 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 - _webSocket.Options.SetRequestHeader("User-Agent", Constants.UserAgentHeader.ToString()); + _webSocket.Options.SetRequestHeader("User-Agent", Constants.UserAgentHeader.ToString()); #else - // Set an alternative user agent header on Full framework - _webSocket.Options.SetRequestHeader("X-SignalR-User-Agent", Constants.UserAgentHeader.ToString()); + // Set an alternative user agent header on Full framework + _webSocket.Options.SetRequestHeader("X-SignalR-User-Agent", Constants.UserAgentHeader.ToString()); #endif - if (httpConnectionOptions != null) - { - if (httpConnectionOptions.Headers != null) + if (httpConnectionOptions != 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) - { - _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); - - _closeTimeout = httpConnectionOptions.CloseTimeout; + // 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.SetRequestHeader("X-Requested-With", "XMLHttpRequest"); } - // 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.SetRequestHeader("X-Requested-With", "XMLHttpRequest"); + _closeTimeout = httpConnectionOptions?.CloseTimeout ?? default; _logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger(); // Ignore the HttpConnectionOptions access token provider. We were given an updated delegate from the HttpConnection. _accessTokenProvider = accessTokenProvider; - - _isRunningInBrowser = Utils.IsRunningInBrowser(); } public async Task StartAsync(Uri url, TransferFormat transferFormat, CancellationToken cancellationToken = default)