The problem
We are experiencing some weird problems with our signalR service. The signalR service sometimes works, and sometimes it doesn't. The service connects without issues, but when we try to invoke some of the methods i get the following error:
An unexpected error occurred invoking XXXX on the server.
We have tried enabling detailed logging, but the error message was the same. In the snippet below you can see the error we are getting, and you can furthermore see that the client keeps pinging the server without problems after the error.
SignalR ws image
What we have tried
We have 3 different environments set up with signalR. One of the environments is working but we are experiencing the above mentioned error in our 2 other environments. We tried checking the code on the working environment down on one of those that didn't work, and that didn't fix our signalR problem. What we tried so far:
Restarting the signalR service.
Renaming the hubs.
Cleaning and rebuilding the solution.
The signalR logs doesn't tell us much too. This is the message we are getting out of the logs on Azure:
"message":"Connection aborted. Reason: Connection ping timeout.","type":"ConnectivityLogs","collection":"Connection"
This is the method that fails:
public async Task AddToItemGroupTest(Guid itemId)
{
var securityLogger = await CreateSecurityLoggerForConnectionChanges(itemId);
await Groups.AddToGroupAsync(Context.ConnectionId, GetItemGroupIdentifier(itemId, _applicationSettings.EnvironmentShortName));
securityLogger.LogDataCreate($"connection to itemhub on item with id: {itemId}");
}
Client side:
NotificationService.getItemConnection().invoke("AddToItemGroup" + Constants.environmentShortName, itemId)
.then(() => {
this.addGroupToActiveGroups("item", "AddToItemGroup" + Constants.environmentShortName, itemId);
connectionChangedCallback(connectionStatus.connected);
callback();
})
.catch((error) => {
connectionChangedCallback(connectionStatus.disconnected);
console.log(`Failed adding client to group. Error: ${error}`);
throw Error(`Failed adding client to group. Error: ${error}`);
});
getItemConnection looks like this:
private static getItemConnection() {
if (!this.itemConnection) {
this.itemConnection = new SignalR.HubConnectionBuilder()
.withUrl(Constants.itemHubUrl, {
accessTokenFactory: () => {
return getUserAccesTokenOrForceLogin(this.authService);
}})
.withAutomaticReconnect()
.build();
}
return this.itemConnection;
}
Related
I using a net core 3.1 app with a very basic Hub. Everything works as expected locally. But on server the websocket connection is closed after first call is executed.
I am sure that websockets are enabled on the server. Another part using Blazor Server works fine. And also as long as no hub method is called the websocket connection is alive.
Also the client receives the data send by the hub.
Testing in chrome, after clicking the button, alert box shows the three messages. Then the connection is lost:
Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.
I already tested to connect to server with client from local machine, which does not work. So it shouldn't be a client issue here.
Working with local client and local running app, everything works fine.
TestHub.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace Test
{
public class TestHub : Hub
{
public async Task Test(string name)
{
await Clients.Caller.SendAsync("Message", $"Hello, World! ({ name }) 1");
await Clients.Caller.SendAsync("Message", $"Hello, World! ({ name }) 2");
await Clients.Caller.SendAsync("Message", $"Hello, World! ({ name }) 3");
}
}
}
Index.html
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.min.js"></script>
<h1>Test</h1>
<button onclick="test()">Test</button>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("/Hub/Test")
.configureLogging(signalR.LogLevel.Information)
.build();
function start() {
try {
connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
};
connection.on("Message", (message) => {
alert(message);
});
function test() {
try {
connection.invoke("Test", "Foo");
} catch (err) {
alert(err);
}
}
start();
</script>
Referencing the nuget Mongo2Go caused the problems. With version 3.1.3 they added System.Text.Json 5.0.1 which breaks signalr.
Downgrading Mongo2Go to version 3.1.1 made signalr working again.
I have a SignalR hub written in my MVC solution, with a Javascript client connecting from the view.
The point of the connection is to receive changes for a wallboard from the server. This has to happen almost instantly and requires a lifetime connection, since the webpage is running on a screen without direct pc access.
So far the SignalR connection works for a couple of hours before it gives error.
The error I get is
Error: Connection disconnected with error 'Error: Server timeout elapsed without receiving a message form the server.'.
Failed to load resource: net::ERR_CONNECTION_TIMED_OUT
Warning: Error from HTTP request. 0:
Error: Failed to complete negotiation with the server: Error
Error: Failed to start the connection: Error
Uncaught (in promise) Error
at new HttpError (singlar.js:1436)
at XMLHttpRequest.xhr.onerror (singalr.js:1583)
My client code
let connection = new signalR.HubConnectionBuilder()
.withUrl("/wbHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.start().then(function () {
connection.invoke("GetAllWallboards").then(function (wallboard) {
for (var i = 0; i < wallboard.length; i++) {
displayWallboard(wallboard[i]);
}
startStreaming();
})
})
connection.onclose(function () {
connection.start().then(function () {
startStreaming();
})
})
function startStreaming() {
connection.stream("StreamWallboards").subscribe({
close: false,
next: displayWallboard
});
}
Hub Code:
public class WallboardHub : Hub
{
private readonly WallboardTicker _WallboardTicker;
public WallboardHub(WallboardTicker wallboardTicker)
{
_WallboardTicker = wallboardTicker;
}
public IEnumerable<Wallboard> GetAllWallboards()
{
return _WallboardTicker.GetAllWallboards();
}
public ChannelReader<Wallboard> StreamWallboards()
{
return _WallboardTicker.StreamWallboards().AsChannelReader(10);
}
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnDisconnectedAsync(exception);
}
}
Question 1: Is the way I handle reconnecting correct? From the error it feels like the .onclose works, but that it only tries one time? Is there anyway to try for x min before showing error?
Question 2: Reloading the website makes the connection work again, is there potential anyway to refresh the browser on signalR connection error?
I have the same issue (Question 1), and i resolve with this:
const connection = new SignalR.HubConnectionBuilder()
.withUrl("/hub")
.configureLogging(SignalR.LogLevel.Information)
.build();
connect(connection);
async function connect(conn){
conn.start().catch( e => {
sleep(5000);
console.log("Reconnecting Socket");
connect(conn);
}
)
}
connection.onclose(function (e) {
connect(connection);
});
async function sleep(msec) {
return new Promise(resolve => setTimeout(resolve, msec));
}
Every 5 seconds tries to reconnect, but i don't know if this is the right way to do this.
ASP.NET Core 2.1 (current LTS release) with the corresponding SignalR release doesn't seem to have some integrated reconnecting method avaliable. The code from #Shidarg doesn't work for me, it calls the reconnect method in a infinitive loop crashiny my browser. I also like the async/await syntax from C# more, so I updated it:
let reconnectWaitTime = 5000
let paramStr = '?myCustomArg=true'
let client = new signalR.HubConnectionBuilder()
.withUrl("/overviewHub" + paramStr)
.build();
client.onclose(async () => {
console.warn(`WS connection closed, try reconnecting with loop interval ${reconnectWaitTime}`)
tryReconnect(client)
})
await tryReconnect(client)
async function tryReconnect(client) {
try {
let started = await client.start()
console.log('WS client connected!')
// Here i'm initializing my services, e.g. fetch history of a chat when connection got established
return started;
} catch (e) {
await new Promise(resolve => setTimeout(resolve, reconnectWaitTime));
return await tryReconnect(client)
}
}
But for ASP.NET Core 3 they included a reconnecting method:
let client = new signalR.HubConnectionBuilder()
.withUrl("/myHub")
.withAutomaticReconnect()
.configureLogging(signalR.LogLevel.Information)
.build();
Per default it try three reconnects: First after 2 seconds, second after 10 seconds and the last about 30 seconds. This could be modificated by passing the intervalls as array parameter:
.withAutomaticReconnect([5000, 1500, 50000, null])
This example re-trys after 5s, 15s and 50s. The last null param tell SignalR to stop re-trying. More information could be found here: https://www.jerriepelser.com/blog/automatic-reconnects-signalr/
Configuring automatic reconnects only requires a call to withAutomaticReconnect on the HubConnectionBuilder. Here is what my JavaScript code looks like for configuring my connection:
connection = new signalR.HubConnectionBuilder()
.withUrl("/publish-document-job-progress")
.withAutomaticReconnect()
.configureLogging(signalR.LogLevel.Information)
.build();
You can configure the backoff period by passing an array of retry delays to the call to withAutomaticReconnect(). The default for this is [0, 2000, 10000, 30000, null]. The null value tells SignalR to stop trying. So, for example, if I wanted it to retry at 0, 1 second and 5 seconds, I can configure my HubConnectionBuilder as follows:
connection = new signalR.HubConnectionBuilder()
.withUrl("/publish-document-job-progress")
.withAutomaticReconnect([0, 1000, 5000, null])
.configureLogging(signalR.LogLevel.Information)
.build();
I am facing some problem related to service worker before some time i am using gcm and service worker file name was service-worker.js after releasing fcm i changed my code and now my service worker file name is firebase-messaging-sw.js but in some my client browser calling old service-worker.js file which is generating an error(service-worker.js not found 500). I already used following code before gettoken().
const messaging = firebase.messaging();
navigator.serviceWorker.register('/firebase-messaging-sw.js')
.then((registration) => {
messaging.useServiceWorker(registration);
// Request permission and get token.....
});
but its still showing this error.
In general, if you have multiple service workers registered with different scopes, and you want to get a list of them from a client page (and potentially unregister some of them, based on either matching scope or SW URL), you can do the following:
async unregisterSWs({matchingScope, matchingUrl}) {
const registrations = await navigator.serviceWorker.getRegistrations();
const matchingRegistrations = registrations.filter(registration => {
if (matchingScope) {
return registration.scope === matchingScope;
}
if (matchingUrl) {
return registration.active.scriptURL === matchingUrl;
}
});
for (const registration of matchingRegistrations) {
await registration.unregister();
console.log('Unregistered ', registration);
}
}
and then call it passing in either a scope or SW script URL that you want to use to unregister:
unregisterSWs({matchingScope: 'https://example.com/push'});
unregisterSWs({matchingUrl: 'https://example.com/my-push-sw.js'});
I'm experimenting with SignalR hosted in an asp.net MVC5 application and am having an issue detecting authorization failures from a web client.
I have a simple hub as follows:
public class ChitChat : Hub
{
[Authorize]
public string Hi(string incoming)
{
Clients.All.echo(incoming);
return "Echoed " + incoming;
}
}
And on the web page:
$(function() {
var hub = $.connection.chitChat;
hub.client.echo = function(msg) {
console.log(msg);
}
$.connection.hub.start().done(function() {
console.log("Done starting hub");
hub.server.hi("Client message")
.done(function() {
console.log(arguments);
})
.fail(function() {
console.log(arguments);
});
})
.fail(function() {
console.log("Fail hub" + arguments);
});
});
When I enable detailed errors in the hub configuration, I get this in the promise rejection for hi
Error: Caller is not authorized to invoke the Hi method on ChitChat.
Without detailed errors, I just get
Error: There was an error invoking Hub method 'chitchat.Hi'.
I'd like to keep detailed errors off, but still get some dependable way of identifying auth failures (I'd assumed I would get a 401/403 code somewhere). Is there a way to achieve this?
SignalR uses jQuery under the hood, so detecting the 401 status code could be achieved by following:
$.ajaxSetup({
statusCode: {
401: function() {
// your code is here
}
}
});
Given a rejection r, check r.source.status. This should give you a 401/403 when it is an authorization problem.
I have external services that I want to call every 20-30 seconds and make accessible to the client using SignalR but I'm not sure if I should have the client call the server in a JavaScript setInterval loop or if I should have the server run its own loop and push the information to the client. I'm looking to do dynamic page updates to show what a user is currently playing on Xbox 360/Xbox One or PlayStation Network.
Currently, I'm calling the server side code from the client in a setInterval loop, but I'm not sure how well this will scale. I had been doing Web API 2 calls through AJAX previously, but that will quickly eat up bandwidth. The external services are not mine so the only way to get simulated real-time results is to send a request to the external services periodically. The only problem I see with doing a server side loop is thread blocking. I don't want to stop the current thread while the loop is running.
Client side code:
var profile = $.connection.profileHub;
profile.client.receiveXboxProfile = function (profile) {
console.log(profile.GamerTag);
};
profile.client.receivePSNProfile = function (profile) {
console.log(profile.PSNId);
};
$.connection.hub.start()
.done(function () {
function GetGamerProfiles() {
profile.server.sendXboxProfile(window.UserName)
.done(function() {
})
.fail(function (error) {
console.log('SignalR ' + error);
});
profile.server.sendPSNProfile(window.UserName)
.done(function () {
})
.fail(function (error) {
console.log('SignalR ' + error);
});
}
GetGamerProfiles();
var id = setInterval(function () {
GetGamerProfiles();
}, 20000);
});
Server side hub:
[Authorize]
public async Task SendXboxProfile(string username)
{
// Makes call to external service to get Xbox profile
Clients.Caller.receiveXboxProfile(await xboxapi.GetProfile(username));
}
[Authorize]
public async Task SendPSNProfile(string username)
{
// Makes call to external service to get PSN profile
Clients.Caller.receivePSNProfile(await psnapi.GetProfile(username));
}
Maybe SignalR isn't the best solution for this problem since I don't control the external services, but I am very intrigued by the concept of SignalR and HTML 5 websockets. In general, is it better to do periodic updates on the client or server with SignalR?