I am attempting to write an web application with a persistent echo connection to a laravel-echo-server instance, which needs to detect disconnections and attempt to reconnect gracefully. The scenario I am attempting to overcome now is a user's machine has gone to sleep / reawoke and their session key has been invalidated (echo server requires an active session in our app). Detecting this situation from an HTTP perspective is solved - I setup a regular keepAlive, and if that keepAlive detects a 400-level error, it reconnects and updates the session auth_token.
When my Laravel session dies, I cannot tell that has happened from an echo perspective. The best I've found is I can attach to the 'disconnect' event, but that only gets triggered if the server-side laravel-echo-server process dies, rather than the session is invalid:
this.echoConnection.connector.socket.on('connect', function() {
logger.log('info', `Echo server running`);
})
this.echoConnection.connector.socket.on('disconnect', function() {
logger.log('warn', `Echo server disconnected`);
});
On the laravel-echo-server side, I can tell that the connection is dead - it will show this error:
⚠ [7:03:30 PM] - 5TwHN2qUys5VEFP5AAAG could not be authenticated to private.1
I cannot figure out how to catch this failure event programmatically from the client. Is there a way to capture it? Again, I can tell the session is dead eventually because I poll the server regularly via a http keepAlive function, but I would definitely also like to tell directly from the echo connection if possible, as it polls at a much higher natural rate.
As a second (more important) question, if I detect that my session has died, what should I do to recycle the echo connection (after I have logged in again via HTTP and gotten a new auth_token)? Is there anything specific I should call / etc? I've had some success calling disconnect() then setting up the connection again from scratch, but I do see errors such as:
websocket.js:201 WebSocket is already in CLOSING or CLOSED state.
Here is my current (naive) reconnection code, which is my initial connection code with an attempt to disconnect first stapled onto it:
async attemptEchoReconnect() {
if (this.echoConnection !== null) {
this.echoConnection.disconnect();
this.echoConnection = null;
}
const thisConnectionParams = this.props.connections[this.connectionName];
const curThis = this;
this.echoConnection = new Echo({
broadcaster: 'socket.io',
host: thisConnectionParams.echoHost,
authEndpoint: 'api/broadcasting/auth',
auth: {
headers: {
Authorization: `Bearer ` + thisConnectionParams.authToken
}
}
});
this.echoConnection.connector.socket.on('connect', function() {
logger.log('info', `Echo server running`);
})
this.echoConnection.connector.socket.on('disconnect', function() {
logger.log('warn', `Echo server disconnected`);
});
this.echoConnection.join('everywhere')
.here(users => {
logger.log('info', `Rejoined presence channel`);
});
this.echoConnection.private(`private.${this.props.id}`)
.listen(...);
setTimeout(() => { this.keepAlive() }, 120 * 1000);
}
Any help would be so great - these APIs are not well documented to the end that I really want, and I am hoping I can get some stability with this connection rather than having to do something ugly like force restart.
For anyone who needs help with this problem, my above echo reconnection code seems to be pretty stable, along with a keepAlive function to determine the state of the HTTP connection. I am still a bit uncertain of the origin of the console errors I am seeing, but I suspect they have to do with connection loss during a sleep cycle, which is not something I am particularly worried about.
I'd still be interested in hearing other thoughts if anyone has any. I am somewhat inclined to believe long-term stability of an echo connection is possible, though it does appear you have to proactively monitor it with what tools you have available.
Related
i'd like to ask some question about how to close a websocket client when offline/switched network.
when i try to close the socket for the 2 case in chrome, after i call websocket.close, i cannot recieve onclose event for a long time (around 60s), then i can recieve it finally.
after i check the readystate, i found that in the coming 60s, the state is 2(CLOSEING), not turned to 3(CLOSED).
so i'd like to know is there any steps i missed when i call websocket.close() in offline/switched network condition. while it runs well when the network is normal.
what's your back-end framework?
If you try to handle client's network that suddenly turned offline, there're two way you can try to close websocket from client as follows.
Kindly refer to source code here.
Using the js offline event handle
If we would like to detect if the user went offline we simply add websocket close function into offline event function.
front-end
function closeWebSocket() {
websocket.close();
}
$(window).on('beforeunload offline', event => {
closeWebSocket();
});
back-end (WebSocketServer)
#OnClose
public void onClose(Session session) {
CURRENT_CLIENTS.remove(session.getId());
}
Using Ping interval on the client-side and decrease the websocket timeout on server-side
If websocket server doesn't receive any message in specific time, it will lead to a timeout. So we can use this mechanism to decrease the timeout to close session if the client doesn't send any ping due to offline.
front-end
// send ping to server every 3 seconds
const keepAlive = function (timeout = 20000) {
if (websocket.readyState === websocket.OPEN) {
websocket.send('ping');
}
setTimeout(keepAlive, timeout);
};
back-end (WebSocketConfig)
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxSessionIdleTimeout(5000L);
return container;
}
I create 60 client connections to socket.io server in google chrome browser.
Server at specific time send screenshot to the clients. And some websocket connections, that are subprotocol of socket.io are broken, so connection at about 1-4 chrome tabs are closed. I tried to increase pingTimeout, it helped to overcome tcp transport close problem only (this problem I have as well), but this solution doesn't help to fix sending screenshot problem.
In my opinion google chrome can't support about 50-60 tabs at one time, because CPU and RAM are increased to the max values because of sending screenshots to 60 clients (each client has 2 websocket connection: the first for simple messages, the second for graphics (to send screenshots)), so chrome closes some websocket connections.
Part of code for the server socket io here:
// server
this.http = this._createHttpServer(sslCert, sslKey);
this.io = socketIo(this.http, {
'pingTimeout': 180000,
'pingInterval': 60000
});
const jwtAuth = socketioJwt.authorize({
secret: jwtSecret,
timeout: 15000
});
this.io.on('connection', (socket) => {
socket.once('authenticate', (data) => {
socket.rawAuthData = data;
});
jwtAuth(socket);
});
// client
var connOptions = {
"reconnectionAttempts": 2
};
var socket = io(options.url, connOptions);
socket.on('connect', function() {
if (options.token) {
socket.emit('authenticate', {token: options['token'], tag : tag});
socket.on('authenticated', function() {
ctx.printLog('Authorized. Waiting for handshake');
socket.once('tunnel-handshake', function() {
ctx.printLog('handshake received! connection is ready');
processConnected();
});
}).on('unauthorized', function(msg) {
ctx.printLog("Authorization failed: " + JSON.stringify(msg.data));
eventHandlers.onerror({ code: ctx.ERROR_CODE.INVALID_TOKEN});
});
} else {
processConnected();
}
});
socket.on('reconnect_failed', eventHandlers.onerror.bind(this, {code: 1, reason: "Reconnection failed"}));
socket.on('disconnect', eventHandlers.onclose);
socket.on('error', eventHandlers.onerror);
Does exist any ideas, what the cause could be? Does exist any solution of this problem?
Is it google chrome problem or socket.io options problem?
Thanks
Changing socket.io to 3.0 version can't resolve the issue. socket.io v3.0 has engine.io v4.0. Next information in release notes of engine.io v4.0 describes the problem ("Heartbeat mechanism reversal" title):
We have received a lot of reports from users that experience random disconnects due to ping timeout, even though their Internet connection is up and the remote server is reachable. It should be noted that in that case the client reconnects right away, but still it was an annoying issue.
After analysis, it seems to be caused by delayed timers on the
client-side. Those timers are used in the ping-pong mechanism which
helps to ensure the connection between the server and the client is
still healthy. A delay on the client-side meant the client sent the
ping packet too late, and the server considered that the connection
was closed.
That’s why the ping packets will now be sent by the server, and the
client will respond with a pong packet.
But increasing pingTimeout and pingInterval to 1073741823 value resolves the issue.
I am trying to create an app that uses websockets, but am running into the dreaded "multiple connection" issue where everytime the page is reloaded rather than closing and reopening a new websocket, another connection is simply added to the list. My question is if anyone knows the best/proper way to close a Socket.io websocket from Angular 2/4+.
Here's what I have as far as code:
service.ts
getUserSocket(userID: string): Observable<any> {
return new Observable(_Observer => {
// Setup the socket namespace subscription
this.UserSocket = io(NTC_API_URL + `/user/${userID}`, { secure: true });
this.UserSocket.on('message', _Message => {
console.log(_Message);
})
})
}
closeUserSocket() {
this.UserSocket.disconnect();
this.UserSocket.close();
}
component.ts
ngOninit() {
// Setup the User Socket
this.UserSocket = this._UsersService.getUserSocket(this.currentUser.userID)
.subscribe(_UserSocketMessage => {
console.log(_UserSocketMessage);
})
}
ngOnDestroy() {
this.UserSocket.unsubscribe();
this._UsersService.closeUserSocket();
}
This doesn't seem to be working as I can still watch the connections pile up just by logging in an out of the application which I have confirmed calls the component to be destroyed.
Another option I tried was using the once listener instead of the on listener, which worked, but I am unsure about the side effects and have read several places that it's not necessarily a fix for not closing the connection, which I can understand.
After quite a bit of tinkering and research it occurred to me that web sockets are made to be resilient. In other words, since they were engineered to reconnect if possible, focusing on disconnect and close was the wrong approach. It was far easier to enforce policies with regard to the Creation of web socket connections.
For starters, I had to refactor a few things on the server side to make sure particular namespaces were only initialized once. This wasn't achieved so much by altering initialization code as it was by making sure that web socket initialization only occurred in places that either only happened on startup, or during processes that are only highly likely to happen once such as a particular user being created.
On the angular side it was also pretty straightforward in that all I had to do was assign the socket to a property within the service, then check if it existed before making the socket connection. Hence only creating the connection if no connection existed previously.
service.ts
// Setup the socket namespace subscription
if (!this.UserSocket) {
// Initialize user permissions socket
this.UserSocket = io(NTC_API_URL + `/user/${this.LocalUser.userID}`, { secure: true });
}
Is there any default timeout value that after a number of tries if connection not establish then i got timeout from socket.io API ? in my application i try to connect with Nodejs server using socket.io but if connection not establish or unreachable i want that at least i get some event after x number of tries and then i should inform a user that there is a connection problem with server. but some how my client continuously trying to connect with a server and print the following exception on console:
socket.io-1.3.5.js:2 GET https://chatapp.local:8898/socket.io/?EIO=3&transport=polling&t=1485528658982-172 net::ERR_CONNECTION_REFUSED
Here is my code:
socket = io(socketUrl, {'force new connection': true});
socket.on('connect', function () {
uiHandler("socket.connect");
});
socket.on('error', function (err) {
uiHandler("socket.error", {error: err});
});
socket.on('disconnect', function() {
uiHandler("socket.disconnect");
});
socket.on('end', function() {
uiHandler("socket.end");
});
How i can set a timeout if connection not establish within 30sec. Any suggestion please.
From what I read in the API docs you can set the timeout value and the number of retries on each connection, so if you want to try for 30 seconds you basically have
maxTime = timeout * reconnectionAttempts
Please note that you have a delay between each retry (which default to 1000 ms) and a randomization factor.If you want to have total control over the duration before emitting a ConnectionError to your clients you will have to tinker with them a little bit.
From the API docs you can also see that each time an a connection fail an error is emitted as either connect_timeout or connection_error. If every available attempts fail then a reconnect_failed will be fired. Then you will be able to tell your user that something went wrong.
In a more general way you have several options to implement a control over an asynchronous process. Two come to mind immediately : promises and observables. You might want to explore them for a more general & extensible approach.
Please feel free to ask in the comms if you want more details or if I do not answer properly.
I am working on Backbone based application (It is also an tablet application), which uses websockets, .
Websockets are working well, but Application doesn't have any logic for error/network handing.
App should show message to user that he is disconnected, and app should retry to connect again, and once its connected back, things starts working again, like gmail.
I haven't written any server-side websocket code, and I am new to Websockets.
Is there any good article on how to handle network disconnection, reconnection for Websockets?
I am unable to find how to set timeout on Websockets, Or how to reconnect again etc.
As its an tablet app, so there will be frequent network disconnections, also App will be getting into sleep mode. Is there any special considerations or practices ?
According to this article, you can use try/catch to treat erroneous cases, like not being able to connect:
function connect(){
try{
var socket;
var host = "ws://localhost:8000/socket/server/startDaemon.php";
var socket = new WebSocket(host);
message('<p class="event">Socket Status: '+socket.readyState);
socket.onopen = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (open)');
}
socket.onmessage = function(msg){
message('<p class="message">Received: '+msg.data);
}
socket.onclose = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (Closed)');
}
} catch(exception){
message('<p>Error'+exception);
}
}
I couldn't find any mention of setting timeout duration even in the WebSocket spec, that might not be possible.