Skip to content

Commit c17f75d

Browse files
authored
Blazor Server Add Console Warning if Long Polling (#36764)
* Blazor Server Add Console Error if Long Polling * E2E Testing * Fix tests * Update Boot.Server.ts
1 parent 7c6edb4 commit c17f75d

File tree

6 files changed

+332
-3
lines changed

6 files changed

+332
-3
lines changed

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/src/Boot.Server.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { DotNet } from '@microsoft/dotnet-js-interop';
22
import { Blazor } from './GlobalExports';
3-
import { HubConnectionBuilder, HubConnection } from '@microsoft/signalr';
3+
import { HubConnectionBuilder, HubConnection, HttpTransportType } from '@microsoft/signalr';
44
import { MessagePackHubProtocol } from '@microsoft/signalr-protocol-msgpack';
55
import { showErrorNotification } from './BootErrors';
66
import { shouldAutoStart } from './BootCommon';
@@ -147,6 +147,22 @@ async function initializeConnection(options: CircuitStartOptions, logger: Logger
147147
} else {
148148
showErrorNotification();
149149
}
150+
151+
if (ex.innerErrors) {
152+
if (ex.innerErrors.some(e => e.errorType === 'UnsupportedTransportError' && e.transport === HttpTransportType.WebSockets)) {
153+
logger.log(LogLevel.Error, 'Unable to connect, please ensure you are using an updated browser that supports WebSockets.');
154+
} else if (ex.innerErrors.some(e => e.errorType === 'FailedToStartTransportError' && e.transport === HttpTransportType.WebSockets)) {
155+
logger.log(LogLevel.Error, 'Unable to connect, please ensure WebSockets are available. A VPN or proxy may be blocking the connection.');
156+
} else if (ex.innerErrors.some(e => e.errorType === 'DisabledTransportError' && e.transport === HttpTransportType.LongPolling)) {
157+
logger.log(LogLevel.Error, 'Unable to initiate a SignalR connection to the server. This might be because the server is not configured to support WebSockets. For additional details, visit https://aka.ms/blazor-server-websockets-error.');
158+
}
159+
}
160+
}
161+
162+
// Check if the connection is established using the long polling transport,
163+
// using the `features.inherentKeepAlive` property only present with long polling.
164+
if ((connection as any).connection?.features?.inherentKeepAlive) {
165+
logger.log(LogLevel.Warning, 'Failed to connect via WebSockets, using the Long Polling fallback transport. This may be due to a VPN or proxy blocking the connection. To troubleshoot this, visit https://aka.ms/blazor-server-using-fallback-long-polling.');
150166
}
151167

152168
DotNet.attachDispatcher({
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Linq;
5+
using System.Threading;
6+
using BasicTestApp;
7+
using BasicTestApp.Reconnection;
8+
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
9+
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
10+
using Microsoft.AspNetCore.E2ETesting;
11+
using OpenQA.Selenium;
12+
using TestServer;
13+
using Xunit;
14+
using Xunit.Abstractions;
15+
16+
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
17+
{
18+
public class ServerTransportsTest : ServerTestBase<BasicTestAppServerSiteFixture<TransportsServerStartup>>
19+
{
20+
public ServerTransportsTest(
21+
BrowserFixture browserFixture,
22+
BasicTestAppServerSiteFixture<TransportsServerStartup> serverFixture,
23+
ITestOutputHelper output)
24+
: base(browserFixture, serverFixture, output)
25+
{
26+
}
27+
28+
[Fact]
29+
public void DefaultTransportsWorks_WithWebSockets()
30+
{
31+
Navigate("/defaultTransport/Transports");
32+
33+
Browser.Exists(By.Id("startBlazorServerBtn")).Click();
34+
35+
var javascript = (IJavaScriptExecutor)Browser;
36+
Browser.True(() => (bool)javascript.ExecuteScript("return window['__aspnetcore__testing__blazor__start__script__executed__'] === true;"));
37+
38+
AssertLogContainsMessages(
39+
"Starting up Blazor server-side application.",
40+
"WebSocket connected to ws://",
41+
"Received render batch with",
42+
"The HttpConnection connected successfully.",
43+
"Blazor server-side application started.");
44+
45+
AssertGlobalErrorState(hasGlobalError: false);
46+
}
47+
48+
[Fact]
49+
public void ErrorIfClientAttemptsLongPolling_WithServerOnWebSockets()
50+
{
51+
Navigate("/webSockets/Transports");
52+
53+
Browser.Exists(By.Id("startWithLongPollingBtn")).Click();
54+
55+
var javascript = (IJavaScriptExecutor)Browser;
56+
Browser.True(() => (bool)javascript.ExecuteScript("return window['__aspnetcore__testing__blazor__start__script__executed__'] === true;"));
57+
58+
AssertLogContainsMessages(
59+
"Information: Starting up Blazor server-side application.",
60+
"Skipping transport 'WebSockets' because it was disabled by the client",
61+
"Failed to start the connection: Error: Unable to connect to the server with any of the available transports.",
62+
"Failed to start the circuit.");
63+
64+
AssertGlobalErrorState(hasGlobalError: true);
65+
}
66+
67+
[Fact]
68+
public void WebSocketsConnectionIsRejected_FallbackToLongPolling()
69+
{
70+
Navigate("/defaultTransport/Transports");
71+
72+
Browser.Exists(By.Id("startAndRejectWebSocketConnectionBtn")).Click();
73+
74+
var javascript = (IJavaScriptExecutor)Browser;
75+
Browser.True(() => (bool)javascript.ExecuteScript("return window['__aspnetcore__testing__blazor__start__script__executed__'] === true;"));
76+
77+
AssertLogContainsMessages(
78+
"Information: Starting up Blazor server-side application.",
79+
"Selecting transport 'LongPolling'",
80+
"Failed to connect via WebSockets, using the Long Polling fallback transport. This may be due to a VPN or proxy blocking the connection. To troubleshoot this, visit",
81+
"Blazor server-side application started.");
82+
83+
AssertGlobalErrorState(hasGlobalError: false);
84+
}
85+
86+
[Fact]
87+
public void ErrorIfWebSocketsConnectionIsRejected_WithServerOnWebSockets()
88+
{
89+
Navigate("/webSockets/Transports");
90+
91+
Browser.Exists(By.Id("startAndRejectWebSocketConnectionBtn")).Click();
92+
93+
var javascript = (IJavaScriptExecutor)Browser;
94+
Browser.True(() => (bool)javascript.ExecuteScript("return window['__aspnetcore__testing__blazor__start__script__executed__'] === true;"));
95+
96+
AssertLogContainsMessages(
97+
"Information: Starting up Blazor server-side application.",
98+
"Selecting transport 'WebSockets'.",
99+
"Error: Failed to start the transport 'WebSockets': Error: Don't allow Websockets.",
100+
"Error: Failed to start the connection: Error: Unable to connect to the server with any of the available transports. Error: WebSockets failed: Error: Don't allow Websockets.",
101+
"Failed to start the circuit.");
102+
103+
AssertGlobalErrorState(hasGlobalError: true);
104+
}
105+
106+
[Fact]
107+
public void ServerOnlySupportsLongPolling_FallbackToLongPolling()
108+
{
109+
Navigate("/longPolling/Transports");
110+
111+
Browser.Exists(By.Id("startBlazorServerBtn")).Click();
112+
113+
var javascript = (IJavaScriptExecutor)Browser;
114+
Browser.True(() => (bool)javascript.ExecuteScript("return window['__aspnetcore__testing__blazor__start__script__executed__'] === true;"));
115+
116+
AssertLogContainsMessages(
117+
"Starting up Blazor server-side application.",
118+
"Selecting transport 'LongPolling'.",
119+
"Failed to connect via WebSockets, using the Long Polling fallback transport. This may be due to a VPN or proxy blocking the connection. To troubleshoot this, visit",
120+
"Blazor server-side application started.");
121+
122+
AssertGlobalErrorState(hasGlobalError: false);
123+
}
124+
125+
[Fact]
126+
public void ErrorIfClientDisablesLongPolling_WithServerOnLongPolling()
127+
{
128+
Navigate("/longPolling/Transports");
129+
130+
Browser.Exists(By.Id("startWithWebSocketsBtn")).Click();
131+
132+
var javascript = (IJavaScriptExecutor)Browser;
133+
Browser.True(() => (bool)javascript.ExecuteScript("return window['__aspnetcore__testing__blazor__start__script__executed__'] === true;"));
134+
135+
AssertLogContainsMessages(
136+
"Starting up Blazor server-side application.",
137+
"Unable to connect to the server with any of the available transports. LongPolling failed: Error: 'LongPolling' is disabled by the client.",
138+
"Unable to initiate a SignalR connection to the server. This might be because the server is not configured to support WebSockets. For additional details, visit");
139+
140+
AssertGlobalErrorState(hasGlobalError: true);
141+
}
142+
143+
void AssertLogContainsMessages(params string[] messages)
144+
{
145+
var log = Browser.Manage().Logs.GetLog(LogType.Browser);
146+
foreach (var message in messages)
147+
{
148+
Assert.Contains(log, entry =>
149+
{
150+
return entry.Message.Contains(message, StringComparison.InvariantCulture);
151+
});
152+
}
153+
}
154+
155+
void AssertGlobalErrorState(bool hasGlobalError)
156+
{
157+
var globalErrorUi = Browser.Exists(By.Id("blazor-error-ui"));
158+
Browser.Equal(hasGlobalError ? "block" : "none", () => globalErrorUi.GetCssValue("display"));
159+
}
160+
}
161+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
@page
2+
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
3+
4+
<!DOCTYPE html>
5+
<html>
6+
<head>
7+
<title>Transports Tests</title>
8+
<base href="~/" />
9+
10+
<link href="style.css" rel="stylesheet" />
11+
</head>
12+
<body>
13+
14+
<root><component type="typeof(BasicTestApp.Index)" render-mode="Server" /></root>
15+
16+
<div id="blazor-error-ui">
17+
An unhandled exception has occurred. See browser dev tools for details.
18+
<a href="" class="reload">Reload</a>
19+
<a class="dismiss">🗙</a>
20+
</div>
21+
22+
<button id="startBlazorServerBtn" onclick="startBlazorServer()">Start Normally</button>
23+
<button id="startWithLongPollingBtn" onclick="startWithLongPolling()">Start with Long Polling</button>
24+
<button id="startWithWebSocketsBtn" onclick="startWithWebSockets()">Start with Web Sockets</button>
25+
<button id="startAndRejectWebSocketConnectionBtn" onclick="startAndRejectWebSocketConnection()">Start with WebSockets and Reject Connection</button>
26+
27+
<script src="_framework/blazor.server.js" autostart="false"></script>
28+
<script src="js/jsRootComponentInitializers.js"></script>
29+
<script>
30+
console.log('Blazor server-side');
31+
function startBlazorServer() {
32+
Blazor.start({
33+
logLevel: 1, // LogLevel.Debug
34+
configureSignalR: builder => {
35+
builder.configureLogging("debug") // LogLevel.Debug
36+
}
37+
}).then(function () {
38+
window['__aspnetcore__testing__blazor__start__script__executed__'] = true;
39+
});
40+
}
41+
function startWithLongPolling() {
42+
Blazor.start({
43+
logLevel: 1, // LogLevel.Debug
44+
configureSignalR: builder => {
45+
builder.configureLogging("debug") // LogLevel.Debug
46+
.withUrl('_blazor', 4) // Long Polling (4)
47+
}
48+
}).then(function () {
49+
window['__aspnetcore__testing__blazor__start__script__executed__'] = true;
50+
});
51+
}
52+
function startWithWebSockets() {
53+
Blazor.start({
54+
logLevel: 1, // LogLevel.Debug
55+
configureSignalR: builder => {
56+
builder.configureLogging("debug") // LogLevel.Debug
57+
.withUrl('_blazor', 1) // Web Sockets (1)
58+
}
59+
}).then(function () {
60+
window['__aspnetcore__testing__blazor__start__script__executed__'] = true;
61+
});
62+
}
63+
function WebSocketNotAllowed() { throw new Error("Don't allow Websockets."); }
64+
function startAndRejectWebSocketConnection() {
65+
Blazor.start({
66+
logLevel: 1, // LogLevel.Debug
67+
configureSignalR: builder => {
68+
builder.configureLogging("debug") // LogLevel.Debug
69+
.withUrl('_blazor', {
70+
WebSocket: WebSocketNotAllowed
71+
})
72+
}
73+
}).then(function () {
74+
window['__aspnetcore__testing__blazor__start__script__executed__'] = true;
75+
});
76+
}
77+
</script>
78+
</body>
79+
</html>

src/Components/test/testassets/TestServer/ServerStartup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public void ConfigureServices(IServiceCollection services)
4242
}
4343

4444
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
45-
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ResourceRequestLog resourceRequestLog)
45+
public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env, ResourceRequestLog resourceRequestLog)
4646
{
4747
var enUs = new CultureInfo("en-US");
4848
CultureInfo.DefaultThreadCurrentCulture = enUs;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Globalization;
6+
using Microsoft.AspNetCore.Builder;
7+
using Microsoft.AspNetCore.DataProtection;
8+
using Microsoft.AspNetCore.Hosting;
9+
using Microsoft.Extensions.Configuration;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using Microsoft.Extensions.Hosting;
12+
13+
namespace TestServer
14+
{
15+
public class TransportsServerStartup : ServerStartup
16+
{
17+
public TransportsServerStartup(IConfiguration configuration)
18+
: base (configuration)
19+
{
20+
}
21+
22+
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
23+
public override void Configure(IApplicationBuilder app, IWebHostEnvironment env, ResourceRequestLog resourceRequestLog)
24+
{
25+
if (env.IsDevelopment())
26+
{
27+
app.UseDeveloperExceptionPage();
28+
}
29+
30+
app.Map("/defaultTransport", app =>
31+
{
32+
app.UseStaticFiles();
33+
34+
app.UseRouting();
35+
app.UseEndpoints(endpoints =>
36+
{
37+
endpoints.MapBlazorHub();
38+
endpoints.MapFallbackToPage("/_ServerHost");
39+
});
40+
});
41+
42+
app.Map("/longPolling", app =>
43+
{
44+
app.UseStaticFiles();
45+
46+
app.UseRouting();
47+
app.UseEndpoints(endpoints =>
48+
{
49+
endpoints.MapBlazorHub(configureOptions: options =>
50+
{
51+
options.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.LongPolling;
52+
});
53+
endpoints.MapFallbackToPage("/_ServerHost");
54+
});
55+
});
56+
57+
app.Map("/webSockets", app =>
58+
{
59+
app.UseStaticFiles();
60+
61+
app.UseRouting();
62+
app.UseEndpoints(endpoints =>
63+
{
64+
endpoints.MapBlazorHub(configureOptions: options =>
65+
{
66+
options.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.WebSockets;
67+
});
68+
endpoints.MapFallbackToPage("/_ServerHost");
69+
});
70+
});
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)