I have a server application that performs firmware update on remote devices via radio.
Sometimes the update may continue like forever (if there is disturbance in the radio network). In that case the user may wish to interrupt the update by refreshing or leaving the page. In this case I have to:
Alert the user that he is about to interrupt the update (which is not recommended), and if the user is sure about his decision (and confirm):
Send a socketIO event to the server to inform that the update should be canceled.
I searched through the internet and came across to different solutions. The best of them are assigning handlers to the global object events onbeforeunload and onunload.
Using onbeforeunload I don't have choice. Can't send the SocketIO message only if the user confirm, and do nothing if the user decide to wait the update to finish. I can send the SocketIO message to the server, but what if the user decide to wait? The harm is already done.
Using onunload - it seems that doesn't work for me. I see that the socket event is being send by the browser, but before handled by the server the connection is closed.
Is there any way keep the SocketIO connection or delay the browser refresh so the server can handle the event?
I think that this is problem in the server because it runs on CPU with very limited resources, even CPU speed. It is ARM A7 architecture.
There is a way:
Server: Create a user id with:
var custom_id = 0;
io.engine.generateId = (req) => {
let id = custom_id++;
return id++; // very basic but unique id
}
Server: Create a listener to an attempt to close event and store the user id attempt:
var userAttempt = 0;
socket.on('attempt to close', function(data){
userAttempt = socket.id;
console.log(data)
})
Server: Modify disconnect event to check which id user is disconnected:
socket.on('disconnect', function(){
if(userAttempt === socket.id){
console.log("user attempt and close");
//the update should be canceled !!!!
}
console.log('user disconnected');
});
Client: Create the event emitter attempt to close inside onbeforeunload, this event is always going to be fired if user attempt to close or reload the page.
window.onbeforeunload = function (e) {
e.returnValue = " ";
var socket = io();
socket.emit('attempt to close', "user attempt to close");
};
If user try to close the tab, we fire the event attempt to close, we check if user close or not the tab checking the disconnect event. If user disconnected is the same as user attempted to close, the update should be cancelled.
What I did is place the socket event in
window.addEventListener('unload', function (e) {});
window.addEventListener('beforeunload', function (e) {
e.preventDefault();
e.returnValue = ' ';
});
window.addEventListener('unload', function (e) {
socket.emit('some event', {data to be sent});
});
it worked fine to accomplish the task
Although this doesn't fully answer the question, here are some observations I've made using Chrome Version 95.0.4638.54 (Official Build) (64-bit) and socket.io.
The window.beforeunload or window.unload events cause the socket to disconnect before you can send a message via socket.io that the window/tab is closing.
It's probably better to manage this on the server where you can detect the disconnection:(https://socket.io/docs/v4/server-api/)
socket.on("disconnect", (reason) => {
// reason = disconnect, server shutting down, ping timeout,
//transport close, transport error
console.log('disconnect:'+reason+ ' '+socket.id);
//then do your stuff
});
If you're managing the connections between user ids and socket ids on the server it's posssible to track who is doing what etc. I suspect that the answers given may have worked 3 yrs ago but Chrome has changed a lot and I always got a socket disconnect before I could use socket.emit.
Hopefully this will help.
Update 6-11-21
The latest version (v4.1.0 =>) has by default 'closeOnBeforeunload' as true
This will cause the socket to close before you can send a message.
You can of course set this to false.
(quote from docs):
"Whether to (silently) close the connection when the beforeunload event is emitted in the browser.
With closeOnBeforeunload set to false, a disconnect event will be emitted by the Socket instance when the user reloads the page on Firefox (but not on Chrome or Safari).
With closeOnBeforeunload set to true, all browsers will have the same behavior (no disconnect event when reloading the page). But this might cause issues if you use the beforeunload event in your application.
"
see: https://socket.io/docs/v4/client-options/#closeonbeforeunload
Related
I have a socket.io connected to a https server listening for javascript to emit information to it. I am finding that when I refresh, the socket is recording the connection - so if i console.log something in the on('connection', function(socket) { } part of the code, I will see a response in the console.
What I am finding though, is that on the server side, the socket.on() calls are not picking up anything.
So if I have a line in Javascript which is socket.emit('ping', msg), 'ping' will work on the server side for the first 9-10 refreshes, and then on the 11th refresh it will stop working - and it will stop working on all devices and all browsers. So if I open another browser and try to load it up, it won't work either.
I have checked to see if there are multiple connections building up or something like that, but I am not seeing anything. Any idea why this weird behaviour is happening?
Server Side
io.use(function(socket, next) {
// Authentication of the user is happening here - checking for cookies, sessions, etc.
}).on('connection', function(socket) {
// Setting up some variables and checking other stuff
var socketID = socket["id"];
var username = socketUsers[socketID];
locked[socketID] = {};
// Checking for the username
if(username == '' || typeof username == "undefined") {
// Emit some stuff back to Javascript
io.to(`${socketID}`).emit('criticalError', true);
return false;
}
else {
// We have a valid login, so connect to the server
++connected;
// This line will console log every time I refresh
console.log("Some text");
socket.on('ping', function(msg) {
// This line will console log for the first 9-10 refreshes, and then it suddenly stops working. Any ideas why?
console.log('Server was pinged by ' + username);
});
// This line will console log every time I refresh
console.log("Some other text");
}
});
Client side
var socket = io('https://example.com:1337');
$(document).ready(function() {
socket.emit('ping', true);
});
This is an interesting problem that you are facing. There are multiple points to check on - However the first problem I would suggest is with the below code:
var socket = io('https://example.com:1337');
$(document).ready(function() {
socket.emit('ping', true);
});
If this is the exact code on the client side, chances are that the socket.emit is getting called before the connection handshake goes through. This can be random as sometimes the document ready event might be delayed. Try setting a timeout for the emit event -
var socket = io('https://example.com:1337');
$(document).ready(function() {
setTimeout(function(){socket.emit('ping', true);},2000);
});
and give it a try. If this works, you found the problem. Next step is to ensure authentication is working correctly - Without the complete code, advising on this is difficult. Feel free to edit the question and share the code if your issue isn't resolved.
Code on server-side sends a message immediately after connection is opened (it sends initial configuration/greetings to a client).
And the following code is on client-side:
var sock = new WebSocket(url);
sock.addEventListener('error', processError);
sock.addEventListener('close', finish);
sock.addEventListener('message', processMessage);
I worry about losing this first configuration/greetings-related message from server. Theoretically nothing prevents it from being received before message event handler is set.
On the other hand, practically it never occurred to me. And AFAIK JavaScript WebSocket API doesn't have countermeasures against this theoretical issue: the WebSocket constructor neither allows message event handler to be set, nor allows WebSocket to be created in suspended state.
So:
Either I am missing something, and loss of message with the above code is impossible even theoretically.
Or it is bug in JavaScript WebSocket API design.
Or everyone is just happy because message loss is practically impossible.
Or such behavior (sending message from server on connection) is somewhy considered bad practice, so no one bothers about possibility to implement it theoretically correct.
?
P.S.: Do such simple-but-theoretical questions better fit Stack Overflow or Programmers#Stack Exchange?
Don't worry.
Your code is running within a single threaded event loop.
This line: var sock = new WebSocket(url); doesn't initiate a websocket connection at all. The spec says that it must perform the actual connection only after returning the web socket, in parallel with the thread handling the event loop your code is running on:
Return a new WebSocket object, but continue these steps [in parallel][2].
That alone wouldn't be sufficient, but all subsequent WebSocket events for that socket are scheduled inside the same single-threaded event loop that is running your code. Here's what the spec says about receiving a message:
When a WebSocket message has been received with type type and data data, the user agent must queue a task to follow these steps
That task is queued on the same event loop. That means that the task to process the message cannot be run until the task where you created your WebSocket has run to completion. So your code will finish running before the event loop will process any connection related messages.
Even if you're running your code in a browser that uses many threads, the specific code will run on a single threaded event loop and each event loop will be independent.
Different event loops can and do communicate by pushing tasks into each other's task-queues. But these tasks will be executed within the single-threaded event-loop that received the task, keeping your code thread-safe.
The task "handle this event" will be handled by the single threaded event loop finding the appropriate event handler and calling its callback... but this will only happen once the task is already being handled.
To be clearer:
I'm not claiming that each event-loop actually handles the IO - but the IO scheduler will send your code events and these events will run sequentially within a single thread (sort of, they do have priority management that uses different "task queues").
EDIT: client code concerns
It should be noted that the Websocket API wasn't designed for the DOM's function addEventListener.
Instead, the Websocket API follows the HTML4 paradigm, where event callbacks are object properties (rather than the EventListener collection). i.e.:
// altered DOM API:
sock.addEventListener('message', processMessage);
// original WebSocket API:
sock.onmessage = processMessage;
Both APIs work correctly on all the browsers I tested (including safe delivery of first message). The difference in approaches is probably handled by the HTML4 compatibility layer.
However the specification regarding event scheduling is different, so the use of addEventListener should probably be avoided.
EDIT 2 : Testing the Theory
Regarding Bronze Man's answer concerning failed message responses...
I couldn't reproduce the claimed issue, even though I wrote a test using a small Ruby application and a small Javascript Client.
The Ruby application starts up a Websocket echo server with a welcome message (I'm using plezi.io).
The Javascript client contains a busy-wait loop that causes the Javascript thread to hang (block) for the specified amount of time (2 seconds in my tests).
The onmessage callback is set only after the block is released (after 2 seconds) - so the welcome message from the server will arrive at the browser before the callback is defined.
This allows us to test if the welcome message is lost on any specific browser (which would be a bug in the browser).
The test is reliable since the server is a known quantity and will send the message to the socket as soon as the upgrade is complete (I wrote the Iodine server backend in C as well as the plezi.io framework and I chose them because of my deep knowledge of their internal behavior).
The Ruby application:
# run from terminal using `irb`, after `gem install plezi`
require 'plezi'
class WebsocketEcho
def index
"Use Websockets"
end
def on_message data
# simple echo
write data
end
def on_open
# write a welcome message
# will ths message be lost?
write "Welcome to the WebSocket echo server."
puts "New Websocket connection opened, welcome message was sent."
end
end
# adds mixins to the class and creates route
Plezi.route("/", WebsocketEcho)
# running the server from the terminal
Iodine.threads = 1
Iodine::Rack.app = Plezi.app
Iodine.start
The Javascript Client:
function Client(milli) {
this.ws = new WebSocket("ws" + window.document.location.href.slice(4, -1));
this.ws.client = this;
this.onopen = function (e) { console.log("Websocket opened", e); }
this.ws.onopen = function (e) { e.target.client.onopen(e); }
this.onclose = function (e) { console.log("Websocket closed", e); /* reconnect? */ }
this.ws.onclose = function (e) { e.target.client.onclose(e); }
if(milli) { // busy wait, blocking the thread.
var start = new Date();
var now = null;
do {
now = new Date();
} while(now - start < milli);
}
this.onmessage = function (e) { console.log(e.data); }
// // DOM API alternative for testing:
// this.ws.addEventListener('message', function (e) { e.target.client.onmessage(e); });
// // WebSocket API for testing:
this.ws.onmessage = function (e) { e.target.client.onmessage(e); }
}
// a 2 second window
cl = new Client(2000);
Results on my machine (MacOS):
Safari 11.01 initiates the Websocket connection only after the new client was creation is complete (after the thread is done processing the code, as indicated by the Ruby application's delayed output). The message obviously arrived once the connection was made.
Chrome 62.0 initiates the Websocket connection immediately. The message arrives once the 2 second window ends. Message wasn't lost even though it arrived before the onmessage handler was set.
FireFox 56.0 behaves the same as Chrome, initiating the Websocket connection immediately. The message arrives once the 2 second window ends. Message wasn't lost.
If someone could test on Windows and Linux, that would be great... but I don't think the browsers will have implementation issues with the event scheduling. I believe the specifications can be trusted.
Your theory is true and real.
I ACTUALLY got into this situation on chrome 62 on ubuntu 1404 when my chrome extension background page open a websocket connection to 127.0.0.1 server. My server send serval messages first to the app. And the first serval messages may lost and may not lost. But this bug do not happen on my mac chrome 62. I think this is what data race looks like.It may never happen, but it may happen in theory. So we need to prevent it happen.
Here is my client code looks like:
var ws = new WebSocket(url);
var lastConnectTime = new Date();
ws.onerror = processError;
ws.onclose = finish;
ws.onmessage = processMessage;
Solution
The solution should be the server must wait client first message(even if it do not have any information) then send message to client.
Here is my solution in client js in code:
var ws = new WebSocket(url);
var lastConnectTime = new Date();
ws.onerror = processError;
ws.onclose = finish;
ws.onmessage = processMessage;
ws.onopen = function(){
ws.send("{}");
};
Here is my solution in golang server:
func (s *GoServer)ServeHTTP(w http.ResponseWriter, r *http.Request){
fmt.Println("WebsocketServeHttp recv connect",r.RemoteAddr)
conn,err:=websocket.Upgrade(w,r,nil,10240,10240)
if err!=nil{
panic(err)
}
_,_,err=conn.ReadMessage()
if err!=nil{
panic(err)
}
//... (you can send message to the client now)
}
Confirming that the problem does exist (as a rare but real situation) on Chrome 62 and 63 on Ubuntu: occasional loss of first message from server. I confirmed with tcpdump that there is indeed a handshake packet and then the packet for the first message. In the client, the first message even shows up in the Networking tab as a first frame on the websocket. Then onopen callback is called, but onmessage is NOT.
I agree that it doesn't seem possible, and looking at WebKit's implementation of WebSocket, it doesn't seem possible, and I've never seen it on Chrome Mac or in Firefox, so my only guess is that Chrome on Ubuntu introduced a race condition with some optimization.
You can definitely lose messages! The accepted answer is misleading. All that has to happen is that you do an operation that relinquishes the thread of control between the open event and configuring a message listener.
Is that likely to happen? Who knows, it depends on your application. Here's the situation that led me to waste too much time debugging this (the api sucks!) using the ws library on the server:
On the server:
async handleOpen(socket, request) {
const session = await getSession(cookie.parse(request.headers.cookie).id);
const user = new User(session.user.id, socket);
user.socket.addEventListener('message', this.handleMessage.bind(this, user));
}
See that await? That relinquishes control and allows events to be lost. For what it's worth, the session was stored in memcached so was not immediately available.
I want to add a script (JavaScript) to each html-document, that sends two messages to the server:
page did open
page will close (this message contains how long the page was open)
The open-message should be sent when the document is loading (or when it finished loading). This is the easy part.
The close-message should de sent when the document is unloaded from the browser's viewport. (User clicks on a link that has not target="_blank"; User closes the browser tab/window; User reloads the page; User quits the browser; anything else that makes the page disappear)
I tried it this way:
//================================================================================
//Global Variables
//================================================================================
gl = {}; //container for global variables (1 scalar and 1 function)
gl.start = Math.floor(Date.now()); //timestamp of page-loading
//================================================================================
//function: Send message to server
//================================================================================
gl.sendData = function (action) {
var message = {
'href' : window.location.href,
'height' : $(window).height(),
'width' : $(window).width(),
'action' : action, //can be 'open' or 'close' (i.e. 'load' and 'unload')
'rand' : Math.random() //random number to outwit cache
};
if (action == 'close') {
//how long was the page open? [milliseconds]
message.duration = Math.floor(Date.now()) - gl.start;
};
window.alert(action); //debugging: show if gl.sendData is executed
$.ajax({
'url' : 'http://' + window.location.hostname + '/path/to/script.pl',
'data' : message,
'success' : function(){}, //not interested in server's answer
'error' : function(){} //errors will be ignored
});
};
//================================================================================
//Things to do as soon as page load is complete
//================================================================================
$(document).ready(function(){
//send message "page did open"
gl.sendData('open');
//add event-handler to send the message "page will close" when the page is closing
$(window).on("unload", function() {
gl.sendData('close');
});
});
Sending a message when the page did open is working perfectly fine. But the browser doesn't send the close-message.
I found out this facts:
"unload" seems to be the correct event. The alert-message pops up when the page is closing.
"beforeunload" doesn't work because it is not fired (Safari on Mac, when clicking on a link that navigates to another page)
the ajax-request is sent when the page is loading, so it seems to be ok.
the ajax-request doesn't send data to the server when the page is closing.
my Question:
Is there a way to send a massage to the server in the moment when a document is unloaded from the browser?
I want to send the duration of the page beeing displayed to the server. Is there another way to do this?
There is no 100% reliable way to send data to a server when the page is closing that works in all browsers.
With the advent and general availability of webSockets in browsers, there are some who are using a webSocket connection from client to server as a means of tracking this. When the page is opened, it makes a webSocket (or socket.io) connection to the server. This connection is kept open for the duration of the page. When the user leaves the page or closes the browser, the webSocket will get closed by the browser. The server can then see that the webSocket has been closed and can mark that as the time that the page was closed.
One other possibility that is less efficient than the webSocket connection is for the open webpage to regularly poll the server via Ajax (say every 30 seconds or so to announce that the page is still open). When the server sees the polling stop, it assumes that page has been closed.
Both of these techniques require regular connectivity with the server in order to do this tracking (e.g. can't be used to track offline usage).
HTML5 Web Sockets behave strangely if users are refreshing the page, or navigating
away from it. During reloading of the page, the socket connections between webserver and browser seem to stay open, and are closed if the page reloads in the browser, in both Firefox and Chrome. This means it works only every second time to establish a connection between browser and server, because the socket is closed by the browser on reload. The error message in the Firebug console from Firefox is "The connection to ws://.../websocket" was interrupted while the page was loading". So apparently the websocket connection is still open when the page is reloaded, which means the connection is closed during page load instead of opened every second page load. Log files read for instance like this (created with the Websocket-Rails gem)
==> first page load
[ConnectionManager] Connection opened: #<Connection::fef69428febee72f4830>
[Channel] #<Connection::fef69428febee72f4830> subscribed to channel xyz
==> second page load
[Channel] #<Connection::dfc4b33090b95826e08e> unsubscribed from channel xyz
[ConnectionManager] Connection closed: #<Connection::dfc4b33090b95826e08e>
Is there are way to close all open sockets and connections in the onbeforeunload Javascript event, something
like (in Coffeescript)..
window.onbeforeunload = () ->
close_all_sockets()
It is a bit tricky, but you can do it by creating a new WebSocket, and sending the identification of the user who should be disconnected through its protocol parameter.
For instance :
window.onbeforeunload = function () {
new WebSocket(myWebSocket.url, myUserName + "*" + myPassword);
}
The server must close the appropriate connection when it receives a new connection request with this non standard protocol.
This is what I've done in the handshake code of my server (C#) :
switch (handshakeKey)
{
// ..........
case "Sec-WebSocket-Protocol":
string[] infos = handshakeValue.Split('*');
if (infos.Length == 2)
{
Guest aguest = server.FindGuest(infos[0]);
if (aguest != null && aguest.password == infos[1]) server.RemoveGuest(aguest);
// The removeGuest function closes its socket }
TCPClient.Close(); // close the temporary connection
break;
// ........
}
Hope this helps.
In Firefox (at least), if you hit ESC, then it will close all open WebSockets connections.
I need to capture that disconnection and try to re-connect once it's available again.
Here's an example of the code I've tried to implement, but nothing I can figure out will catch the error and allow me to handle it gracefully.
Have a look at the code: http://jsfiddle.net/w5aAK/
var url = "ws://echo.websocket.org";
try {
socket = window['MozWebSocket'] ? new MozWebSocket(url) : new WebSocket(url);
socket.onopen = function(){
console.log('Socket is now open.');
};
socket.onerror = function (error) {
console.error('There was an un-identified Web Socket error');
};
socket.onmessage = function (message) {
console.info("Message: %o", message.data);
};
} catch (e) {
console.error('Sorry, the web socket at "%s" is un-available', url);
}
setTimeout(function(){
socket.send("Hello World");
}, 1000);
Turn on your console and watch the output.
Am I doing something wrong here, or is it just not possible because the connection is running outside of the scope of the JS script?
Any input would be helpful.
Thanks!
You can attach a handler to the socket.onclose event. It will be called when you hit ESC and the connection is interrupted.
See: http://jsfiddle.net/w5aAK/1/
One problem that you can't get around at the moment is the interrupted error being output to the console. There's no way of capturing that at the moment I'm afraid.
You can't catch it and it's not your fault. It's FireFox bug. Vote for it here:
https://bugzilla.mozilla.org/show_bug.cgi?id=712329
I personally tried all kind of solutions:
event handlers onunload onbeforeunload onclose try..catch some js error handling 3rd party services etc.
You can log to console your socket, it's closed before unload, but FF thinks different.. :(
Solution (not answer directly to the answer, but it works):
It's a bug, so you can't catch, but this info is not Solution. After all kind of crazy workarounds and tries to catch that bug, i finally found this working. If you use socket.io to work with WebScokets it can work with different transport technologies. xhr-polling works with Firefox.
if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) { //test for Firefox/x.x or Firefox x.x (ignoring remaining digits);
socket = io.connect('//' + node_server + '/', {
transports: ['polling']
});
} else {
socket = io.connect('//' + node_server + '/');
}
What helped me - might help you too:
Web Socket support in Node.js/Socket.io for older browser
Define transport types on the client side
socket.io doens't work with transports: [ 'xhr-polling' ]