Background
I have a NodeJS/Express application that uses WebSockets for certain endpoints that need to hold a connection for backend functions that take several minutes to complete.
An example of one of these functions is the creation and processing of an Excel document that is sent back to the client for download.
Problem
The creation of this Excel document is triggered by a button click on the frontend. After the button is clicked, a WebSocket message is sent to the server that identifies an Excel document needs to be created. A function is called that begins creating the document. As part of this document's creation, a number of API and function calls are made, which causes the whole process to take several minutes to complete. After 2 minutes of waiting for the document to be created and sent back, the server restarts, preventing the document from being created and sent to the client.
Important Notes
When I test my code locally, everything works as intended. Furthermore, I have a heartbeat (ping/pong) setup so the WebSocket connection isn't closed from inactivity. After the WebSocket connection is made, the connection stays active indefinitely; however, clicking the button that causes the Excel document to be created is what causes the server to restart after 2 minutes.
Code Snippets
Code for setting up the application, routes, WebSocket connections, etc.
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server:server });
// More Application Setup Here...
wss.on('connection', function connection(ws) {
ws.on('message', async function incoming(message) {
if (message.toString() == '__ping__') {
ws.send('__pong__');
return;
}
let msg = JSON.parse(message);
switch(msg.call) {
case "create_excel":
var workbook = await create_excel();
ws.send(workbook); // Workbook is converted to buffer in real code
break;
default:
break;
}
});
});
// Routes Here...
const port = process.env.PORT || 3000;
server.listen(port, () => console.log(`Listening on port ${port}...`));
Client code for connecting to socket and sending request for file:
const socket = new WebSocket("wss://myurl.com/path");
socket.onopen = function() {
setInterval(ping, 30000);
};
socket.onmessage = function(event) {
if (event.data == "__pong__") {
pong();
return;
}
if (typeof event.data == 'object') {
# Call function to save file
return;
}
# Handle other messages here...
};
function ping() {
socket.send('__ping__');
tm = setTimeout(5000);
}
function pong() {
clearTimeout(tm);
}
button.onclick = function() {
socket.send(JSON.stringify({ call: "create_excel" }));
}
Attempted Fixes
Prior to using WebSockets, I just used HTTP requests for this same function; however, that would fail after 2 minutes as well in the form of a 504 Gateway Error. I transitioned to WebSockets hoping to fix this issue. I've tried the solutions suggested here with no luck.
Could this be a server configuration problem? Are there other options for trying to increase the timeout?
Related
I have a simple snippet on the front end as follows which I can verify is working. I can do this by changing the port to something other than 3000 and it will error.
It is definitely finding the server at that port:
// Create WebSocket connection .. will error if I change the port
const socket = new WebSocket('ws://localhost:3000');
console.log('DEBUG: Web socket is up: ');
// Connection opened
socket.addEventListener('open', function (event) {
socket.send('Hello Server!');
});
I am using ws-express on the server side as follows. This was the minimal example given in the NPM docs:
const expressWs = require('express-ws')(app);
app.ws('/echo', (ws, req) => {
ws.on('message', (msg) => {
ws.send(msg);
});
});
However, the open event on the client never fires. I would like to send messages from the client to the server, but I assume, that I need an open event to fire first.
I have a data provider which gives me stock prices via TCP connection. The data provider only allows a static IP to connect to their service.
But since I need to format the data before sending it to my front-end I want to use my express back-end as a proxy.
What that means is:
I need to connect my back-end to my data provider via websocket(socket.io) in order to get the data (back-end acts as client)
I need my back-end to broadcast this received data to my front-end(back-end acts as server)
My question is: Is that possible at all? Is there an easier way to achieve this? Is there a documentation on how to use an express app as websocket server and client at once?
EDIT:
I got this working now. But my current solution kills my AWS EC2 instance because of huge CPU usage. This is how I've implemented it:
const net = require('net');
const app = require('express')();
const httpServer = require('http').createServer(app);
const client = new net.Socket();
const options = {
cors: {
origin: 'http://someorigin.org',
},
};
const io = require('socket.io')(httpServer, options);
client.connect(1337, 'some.ip', () => {
console.info('Connected to some.ip');
});
client.on('data', async (data) => {
// parse data
const parsedData = {
identifier: data.identifier,
someData: data.someData,
};
// broadcast data
io.emit('randomEmitString', parsedData);
});
client.on('close', () => {
console.info('Connection closed');
});
httpServer.listen(8081);
Does anyone have an idea why this causes a huge CPU load? I've tried to profile my code with clinicjs but I couldn't find a apparent problem.
EDIT2: To be more specific: My data provider provides my with stock quotes. So every time a quote changes, I get new data. I then parse this data and emit it via io.emit. Could this be some kind of bottleneck?
This is the profile I get after I run clinicjs:
I don't know how many resources you have on your AWS, but 1,000 clients shouldn't be a problem.
I have personally encountered 2 bottlenecks:
Clients connected with Ajax, not WS (It used to be a common problem with old socket.io)
The socket.io libraries were served by Node, not Nginx / Apache. Node is poor at keeping-alive management.
Check also:
How often do you get data from some.ip? Good idea is aggregate and filter it.
Do you need to notify all customers of everything? Is it enough just to inform interested? (Live zone)
Maybe it is worth moving the serving to serviceWorker.js or Push Events?
As part of the experiment, log yourself events. Receiving data, connecting and disconnecting the client. Observe the server logs.
As part of the debugging process, log events. Receiving data, connecting and disconnecting the client. Observe the server logs.
Or maybe this code is not responsible for the problems, but the data download for the first view. Do you have data in the buffer, or do you read for GET index.html?
To understand what was going on with your situation, I created an elementary TCP server that published JSON messages every 1ms to each client that connects to it. Here is the code for the server:
var net = require('net');
var server = net.createServer(function(socket) {
socket.pipe(socket);
});
server.maxConnections = 10
server.on('close', () => console.log('server closed'))
server.on('error', (err) => console.error(err))
server.on('listening', () => console.log('server is listening'))
server.on('connection', (socket) => {
console.log('- client connected')
socket.setEncoding('utf8')
var intervalId = setInterval(() => socket.readyState === "open" &&
socket.write(JSON.stringify({
id: intervalId,
timestamp: Date.now(),
}) + '\n'), 1)
socket.on('error' , (err) => console.error(err))
socket.on('close' , () => {
clearInterval(intervalId)
console.log('- client closed the connection')
})
})
server.listen(1337, '0.0.0.0');
As you see, we set up a setInterval function that will emit a simple JSON message to each connected client every 1 ms.
For the client, I used something very similar to what you have. At first, I tried pushing every message received by the server to the browser to the WebSocket connection. In my case, it also pushed the CPU to 100%. I don't know exactly why.
Nonetheless, even though your data is being updated every 1 ms, it is doubtful that you need to refresh your webpage at that rate. Most websites work at 60 fps. That would mean updating the data every 16ms. So, a straightforward solution would be to batch the data and send it to the browser every 16 ms. Just this modification greatly increases performance. You can go even further by extending the batch time or filtering some of the sent data.
Here is the code for the client, taking advantage of batch messages. Bear in mind that this is a very naive implementation made to show the idea. A better adjustment would be to work the streams with libraries like RxJS.
// tcp-client.js
const express = require('express');
const http = require('http');
const { Server } = require("socket.io");
const net = require('net')
const app = express();
const server = http.createServer(app);
const io = new Server(server);
const client = new net.Socket()
app.get('/', (req, res) => {
res.setHeader('content-type', 'text/html')
res.send(`
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>TCP - Client</title>
</head>
<body>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.on('msg', (msg) => document.body.textContent = msg);
</script>
</body>
</html>
`);
});
io.on('connection', (socket) => {
console.log('- user connected');
socket.on('disconnect', () => {
console.log('- user disconnected');
});
});
var buffer = []
setInterval(() => {
io.emit("msg", JSON.stringify(buffer))
buffer = []
}, 16)
client.connect(1337, '127.0.0.1', function() {
console.log('- connected to server');
});
client.on('data', function(data) {
buffer.push(data.toString("utf8"))
});
client.on('close', function() {
console.log('- connection to server closed');
});
server.listen(3000, () => {
console.log('listening on 0.0.0.0:3000');
});
I want to display the real-time speech to text data in the browser. By real-time what I mean is, "while I am speaking I am getting the text output simultaneously". I have implemented the speech-to-text part in Python using the Google cloud service API. Then I used "child process" to run my python program in the node.js environment. Till now everything is fine. Next, I want to display the real-time text in the browser. In another word, I want to send the real-time text output from the python (which is now running in node.js using the child process) to the web browser. I was trying to do that with socket.io. Here is my server side (node.js) code where socket.io is also applied:
const express = require('express');
//const router = express.Router();
const {spawn} = require('child_process');
const path = require('path');
const app = express();
const http = require('http');
const server = http.createServer(app);
//const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server);
function runScript(){
return spawn('python3', [
"-u",
path.join(__dirname, 'script.py')
]);
}
const subprocess = runScript()
// print output of the script
app.get('/', (req,res) => {
res.sendFile(__dirname + '/index.html');
});
io.on('connection', (socket) => {
subprocess.stdout.on('data', (data) => {
//console.log(`data:${data}`);
socket.on('message', (data) => {
socket.broadcast.emit('message', data);
});
});
});
server.listen(3000, () => {
console.log('listening on *:3000');
});
Above, I am first using the child process to call the python program in node.js and then I am using socket.broadcast.emit to send the text output of the python program to my client side. The client-side code looks like this:
<!DOCTYPE html>
<html>
<head>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
var messages = document.getElementById('messages');
//const EventEmitter = require('events');
//const emitter = new EventEmitter()
//emitter.setMaxListeners(50)
socket.on('messages', function(data) {
document.querySelector("#style1".innerHTML = `<p>${data1}</p>`
});
</script>
</head>
<body id="messages">
<h1> This is crazy </h1>
<div id="style1">
</div>
</body>
</html>
Above, I want to display the real-time text output from the python program inside the <p> tag.
The problem is, I am not able to get anything in the web browser.
My objective is, I want to display whatever I am speaking as text in the web browser in real-time.
I don't know much about socket.io. In fact, this is the first time I am using this technology.
Your Node.js server will act as the socket server. As your code shows, it listens on a port for a socket connection, and on connection, creates a socket, which you then send messages too. From a simple cursory review, the server code looks okay.
On your webpage, you are creating the socket, and listening for messages.
However the socket running on the webpage hasn't yet connected to the server, which is why nothing is working yet.
Assuming you're doing this on localhost, just add the socket server address to it's constructor, and then listen for connect.
const socket = io('ws://localhost:3000');
socket.on('connect', () => {
// do any authentication or handshaking here.
console.log('socket connected');
});
More advanced implementations should gracefully handle closing sockets.
Per the following comment:
I added the lines you suggested above. Even now nothing is visible on the webpage but I am getting this warning: (node:14016) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 message listeners added. Use emitter.setMaxListeners() to increase limit
Looking more closely at your server code, I believe this is the root issue
subprocess.stdout.on('data', (data) => {
//console.log(`data:${data}`);
socket.on('message', (data) => {
socket.broadcast.emit('message', data);
});
});
});
Each time you receive data from subprocess.stdout, you are adding a new onmessage event handler to your socket, so after a period of time, you have added too many event handlers.
Re-write your logic so that you only add socket.on('message') once (usually after your create the socket).
It is also worth noting that in the above code, data from stdout is not being used, as that data variable is being redefined in a lower scope by your onmessage function. Since data is being redefined, the output of your Python program is being ignored.
I think this is what you want:
//echo any message you receive from the socket back to the socket
socket.on('message', (data) => {
socket.broadcast.emit('message', data);
});
//send data from std out to the socket.
subprocess.stdout.on('data', (data) => {
//console.log(`data:${data}`);
socket.broadcast.emit('message', data);
});
I have this scenario with socket.io:
I want to receive the data from a sever and Forward the data to webclient.But when I receive a lot of data and close the page, it console
DISCONNECTED FROM CLIENT
DISCONNECTED FROM CLIENT
DISCONNECTED FROM CLIENT
DISCONNECTED FROM CLIENT
DISCONNECTED FROM CLIENT
DISCONNECTED FROM CLIENT
DISCONNECTED FROM CLIENT
...(a lot)
Here is the code:
server:
var express=require('express');
var app=express();
var net=require('net');
var http=require('http').createServer(app);
var io=require('socket.io')(http);
var net=require('net');
var nodeServer = new net.Socket();
var aSocket=null;
io.on('connection', function (socketIO) {
aSocket=socketIO;
};
nodeServer.on('data', function(data) {
if(aSocket!=null){
aSocket.emit('pushToWebClient',useData);
aSocket.on('disconnect', function () {
console.log('DISCONNECTED FROM CLIENT');
});
}
client:
socket.on('pushToWebClient', function (useData) {
});
I find
aSocket.on('disconnect', function () {
console.log('DISCONNECTED FROM CLIENT');
});
console a lot of'DISCONNECTED FROM CLIENT' but actually it should console just once in the code.
I had even console.log(aSocket.id),it console just only one.
I don't know why it is console so many times.
I haved used setMaxListeners(10) to try to avoid it .
Will it lead to a memory leak?
It appears that you are registering multiple event listeners for the same disconnect event. In this code:
nodeServer.on('data', function(data) {
if(aSocket!=null){
aSocket.emit('pushToWebClient',useData);
aSocket.on('disconnect', function () {
console.log('DISCONNECTED FROM CLIENT');
});
}
You appear to be registering a new disconnect event listener every time you get a data message. So, if you have multiple listeners, then each one will get called when the socket disconnects and the result is that you will log the same message multiple times all for the same socket.
You can verify this is what is happening by moving your disconnect handler into the connection handler so it is only ever attached just once for each socket.
In addition putting asocket into a global or module-level variable means that your server code would only ever work with one single client at a time. It is not clear exactly what you are trying to do when you get data on the nodeserver connection - whether you're trying to send that data to only one specific client or to all connected clients.
I try to delete the code:
aSocket.on('disconnect', function () {
console.log('DISCONNECTED FROM CLIENT');
});
or moving it out of nodeServer handler,
it turn into normal and never suggest me to setMaxlisener.
I think maybe it is incorrect put one API into a API
And the envent maybe not release the socket,so it console multiple times .
EDIT: I'm moving this to the top because I saw that someone already provided my solution but you were having a problem managing the data sent to the client. Your aSocket variable will be overwritten by every new client that connects to your app. If you want to send data to a specific client using your server nodeServer, you should create a global variable (an array) that keeps track of all of your client socket connections. So instead of using one global variable aSocket do the following:
var net=require('net');
var nodeServer = new net.Socket();
var clients = [];
io.on('connection', function (socketIO) {
clients.push(socketIO);
var clientNum = clients.length-1;
aSocket.on('disconnect', function () {
clients.splice(clientNum, 1);
console.log('DISCONNECTED FROM CLIENT: '+socketIO.id);
});
};
nodeServer.on('data', function(data) {
//have your data object contain an identifier for the client that caused the handler to fire
//for the sake of the answer I just use data.id
var clientID = data.id;
if(clients[clientID]!=null){
clients[clientID].emit('pushToWebClient', useData);
}
}
Let me know how it goes! My original answer is below:
Try moving
aSocket.on('disconnect', function () {
console.log('DISCONNECTED FROM CLIENT');
});
out of your nodeServer.on('data', ...) event listener into the io.on('connection', ...) event listener like so:
io.on('connection', function (socketIO) {
aSocket=socketIO;
aSocket.on('disconnect', function () {
console.log('DISCONNECTED FROM CLIENT');
});
};
socket.io is designed to keep polling for the presence of the server/client. If either the server or the client are disconnected, the remaining 'side' continues to receive polling requests and, consequently, will continuously print an error.
You can see this effect on the client side in your browser when you disconnect your server and leave the client page open. If you look at the browser's error/console log what you should see is a continuous stream of net::ERR_CONNECTION_REFUSED errors. By placing the disconnect event handler in the .on('data', ...) handler for your server, you are seeing the converse of this situation.
net:ERR_CONNECTION_REFUSED example
This is basic code for socket.io
The following example attaches socket.io to a plain Node.JS HTTP
server listening on port 3000.
var server = require('http').createServer();
var io = require('socket.io')(server);
io.on('connection', function(client){
client.on('event', function(data){});
client.on('disconnect', function(){});
});
server.listen(3000);
I think, you should try.
I'm trying to push messages from server to client using ws module in node.js application.
Please find below the code in app.js.
var server = app.listen(port);
var WebSocketServer = require('ws').Server
var wss = new WebSocketServer({ server: server});
wss.on('connection', function (ws) {
console.log('connected');
});
ws.on('message', function (data, flags) {
});
});
code in the client side js file.
window.WebSocket = window.WebSocket || window.MozWebSocket;
var host = window.document.location.host.replace(/:.*/, '');
var port = window.document.location.port;
var socketConnection = new WebSocket('ws://'+ host+':'+ port);
socketConnection.onerror = function (error) {
// TODO : report error here..
};
socketConnection.onmessage = function (message) {
var obj = JSON.parse(message.data)
};
The problem is when i'm running the node application locally.i.e http://localhost:port and when I try to handshake with ws connection through "ws://localhost:port\". The handshake happens and i am able to send and receive messages.
But when i access the same application using the ip of my machine i.e as http://10.xx.yy.zz:port. Im able to open the application and do evrything but the handshake with websockets time out. The same happens when the app is running in another machine and i try to connect to it from my machines browser.The handshake doesnot happen and it times out.
I have tried the same with demos provided out of box by ws module. The same happens. Am I missing something when creating the websocket server?