How to use only one DB connection per web socket? - javascript

I am using socket.io in my Express.js web app to open sockets for clients that are listening for events that call a SQL query. The server socket looks like this:
socket.on('updates', function(PEMSID){
setInterval(function(){
gps_helper.get_gps(PEMSID, function(data){ //query GPS table for marker coordinates
socket.emit('message', {message: data});
});
}, 10000);
So, every ten second the get_gps() function is being called that does long polling to check if any new records have been added to a table. The get_gps() function looks like this:
exports.get_gps = function(PEMSID, callback) {
var sql = require('msnodesql');
var connStr = "Driver={SQL Server Native Client 11.0};Server=myHost,1433;Database=myDB;UID=Username;PWD=Password;";
var mQuery = "EXEC Z_CoordsByPEMS '" + PEMSID + "'";
sql.open(connStr, function(err,conn){
if(err)
return console.error("Could not connect to sql: ", err);
conn.query(mQuery,function(err,results){
if (err)
return console.error("Error running query: ", err);
callback(results); //return query results
});
});
}
The problem I'm encountering is the get_gps() is opening a new connection to the SQL db everytime it polls for any updates. This is obviouslly causing insane overhead on the server hosting the db and needs to be changed, as the server's CPU eventually reaches maximum capacity and all future queries time out. I'm using the module msnodesql to do the SQL db tasks, but it doesn't appear there's a close() function in the API to close existing connections. I think I'll need to create one global connection and then have all new sockets reference that in order to do their long polling. I'm unsure however how to set up the global connection, if it's even possible, given the asynchronous nature of Express.js/node.js.

Currently, you're creating a new SQL connection for each query your send. Instead, you've said you want to have one SQL connection per socket.io connection. Create the SQL connection whenever you get an updates request (assuming you only get this message once per socket.io connection):
socket.on('updates', function(PEMSID){
var sql = require('msnodesql');
// open a new socket for each `updates` message
sql.open(connStr, function(err,conn){
if(err) { return console.error("Could not connect to sql: ", err); }
// when the connection is made, start the interval
setInterval(function(){
// each interval, get_gps, and pass `conn` as an argument
gps_helper.get_gps(conn, PEMSID, function(data){ //query GPS table for marker coordinates
socket.emit('message', {message: data});
});
}, 10000);
});
});
Then your get_gps function will accept a conn argument:
exports.get_gps = function(conn, PEMSID, callback) {
var connStr = "Driver={SQL Server Native Client 11.0};Server=myHost,1433;Database=myDB;UID=Username;PWD=Password;";
var mQuery = "EXEC Z_CoordsByPEMS '" + PEMSID + "'";
// no need to open `conn`; it's already made
conn.query(mQuery,function(err,results){
if (err)
return console.error("Error running query: ", err);
callback(results); //return query results
});
}
If you want one global SQL connection, simply move the sql.open call even higher:
// create conn outside of the socket.io message callback
sql.open(connStr, function(err,conn){
if(err) { return console.error("Could not connect to sql: ", err); }
io.sockets.on("connection", function(socket) {
socket.on('updates', function(PEMSID){
setInterval(function(){
gps_helper.get_gps(conn, PEMSID, function(data){ //query GPS table for marker coordinates
socket.emit('message', {message: data});
});
}, 10000);
});
});
});

Related

how to reply and send message after receive message in node js using websocket

After receive message from client, I have to connect db(mysql) and save data and need to response the result to client and inform to other(admin) client.
So I need to get current socket client and special client(admin) from the socket list.
Is it possible to get current socket outside of wss connection block?
Thanks.
const WebSocketServer = require('ws');
// Creating a new websocket server
const wss = new WebSocketServer.Server({ port: 8080 });
const clients = new Map();
// Creating connection using websocket
wss.on("connection", ws => {
console.log("new client connected");
client_id = Date.now();
clients.set(client_id, ws);
// sending message
ws.on('message', function(message) {
//wss.broadcast(JSON.stringify(message));
console.log('Received: ' + message);
BuyCoin(message);
//console.log()
});
ws.on("close", () => {
console.log("the client has connected");
});
ws.onerror = function () {
console.log("Some Error occurred")
}
// ws.send('You successfully connected to the websocket.');
});
function BuyCoin(strValue){
const req_info = JSON.parse(strValue);
console.log(req_info.user_id)
console.log('betting!');
var sql = 'SELECT * from users where id = ? LIMIT 1'
connection.query(sql, req_info.user_id, (ws)=>{
return function(err, rows, fields) {
//console.log("ix="+ix);
ws.send(rows[0]);
};
});
}
}
You have several options:
#1: You can put the BuyCoin function logic inside the ws scope to make it a local function that is in scope of the ws variable for the current connection like this:
const WebSocketServer = require('ws');
// Creating a new websocket server
const wss = new WebSocketServer.Server({ port: 8080 });
const clients = new Map();
// Creating connection using websocket
wss.on("connection", ws => {
console.log("new client connected");
client_id = Date.now();
clients.set(client_id, ws);
// sending message
function BuyCoin(strValue) {
const req_info = JSON.parse(strValue);
console.log(req_info.user_id)
console.log('betting!');
var sql = 'SELECT * from users where id = ? LIMIT 1'
connection.query(sql, req_info.user_id, (ws) => {
return function(err, rows, fields) {
//console.log("ix="+ix);
ws.send(rows[0]);
};
});
}
ws.on('message', function(message) {
//wss.broadcast(JSON.stringify(message));
console.log('Received: ' + message);
BuyCoin(message);
//console.log()
});
ws.on("close", () => {
console.log("the client has connected");
});
ws.onerror = function() {
console.log("Some Error occurred")
}
// ws.send('You successfully connected to the websocket.');
});
#2: You can pass the ws value to your BuyCoin() function as an argument by just changing the function call from this:
BuyCoin(message);
to this:
BuyCoin(ws, message);
And, then changing your function declaration from this:
function BuyCoin(strValue) {...}
to this:
function BuyCoin(ws, strValue) {...}
Is it possible to get current socket outside of wss connection block?
No, there really is no such thing as the current socket. When using asynchronous code in nodejs, lots of different pieces of code can be "in-flight" at the same time so there is no global sense of the current socket. Instead, you have manage data specific to your current operation either by using scope, by passing as an argument or by setting as a properties on some other object that is passed as an argument. Since there is no natural object that BuyCoin() already has access to here that is specific to the user with the activity, then that leaves the first two options (using scope and passing as an argument).
FYI, this code looks a bit problematic because you're allowing the webSocket to send in the user_id that will be operated on without any visible authentication. That exposes you to rogue sockets that can pretend to be users that they aren't.
Also, it doesn't appear you have code that removes webSockets from the clients Map object when they disconnect so that Map object will just get larger and larger and contain lots of dead connections.
Another thing that needs fixing is that your connection.query() code is declaring a callback that does nothing but return another function and it tried to make up a value of ws that would never actually be passed. That function you create inside the callback is never called. Change from this:
connection.query(sql, req_info.user_id, (ws) => {
return function(err, rows, fields) {
//console.log("ix="+ix);
ws.send(rows[0]);
};
});
to this:
connection.query(sql, req_info.user_id, (err, rows, fields) => {
//console.log("ix="+ix);
ws.send(rows[0]);
});
And, combine that with one of the above two solutions to get access to the ws value.

How to properly end a MongoDB client connection on error?

I have a MongoDB instance and two JavaScript services running on a Linux server. The first service, moscaService.js, listens to MQTT topics on the server, and records what is sent in a MongoDB collection. The second service, integrationService.js, runs every second, reading data on the same MongoDB collection and, if there's a new register (or more), sends it to Ubidots.
The problem is that both services work on the same IP/port: localhost:27017; and, if there ever is an occasion in which both of them are active simultaneously (say, moscaService.js is recording something and then the integrationService.js tries to connect), there will be a connection error and the service will restart.
Here are the connection parts of both services:
var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://127.0.0.1:27017/myGateway';
//integrationService.js
var job1 = new CronJob('*/1 * * * * *', function() {
MongoClient.connect(url, function(err, db) {
if(err != null) {
logger.error({message: 'Connection error: ' + err});
process.exit(0);
} else {
executeService();
}
function executeService() {
// execution block
}
});
}, null, true, timeZone);
//moscaService.js
server.on('published', function(packet, client) {
//the packet is read here
MongoClient.connect(url, function(err, db) {
if(err != null) {
logger.error({message: 'Connection error: ' + err});
process.exit(0);
} else {
executeService();
}
function executeService() {
// execution block
}
});
});
What I need is a way to properly handle the err instead of just exiting the service, because if there are new messages being published while the service is restarting, they will be lost. Something like testing if the port is open before connecting, or open a different port.
I tried creating another instance of MongoDB on a different port, in order to have each service listen to one, but it looks like Mongo locks more than one instance if it's trying to connect to the same database.
The code snippets here are just a small part; if anyone needs more parts to answer, just say so and I'll add them.
I have made an alteration and it solved this issue. I altered the code in a way that integrationService connects to MongoDB before starting the CronJob; that way, it only connects once and it keeps the connection alive.
Here's the connection part of the code:
var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://127.0.0.1:27017/myGateway';
//integrationService.js
MongoClient.connect(url, function(err, db) {
var job1 = new CronJob('*/1 * * * * *', function() {
if(err != null) {
logger.error({message: 'Connection error: ' + err});
process.exit(0);
} else {
executeService();
}
function executeService() {
// execution block
}
}, null, true, timeZone); // end CronJob
}); // end MongoClient.connect
Since this has solved the problem, I've left the err treatment as is was (although a more elegant way to treat it is still desirable).
Solving the problem on integrationService has solved it on moscaService as well, but I plan to make the same alteration on the second service too.

Nodejs callback with bad workflow

I'm new with NODE and I'm trying this code:
require('../../constants.js');
function dbConnect() {
var mysql = require('mysql');
var connection = mysql.createConnection({
host : DB_HOST,
database : DB_NAME,
user : DB_USER,
password : DB_PASS,
});
connection.connect(function(err) {
console.log("Database is connected ...");
});
return connection;
}
function dbDisconnect(connection) {
connection.end();
console.log("Closing DB Connection");
}
function findPlayer(surname) {
var connection = dbConnect();
console.log("Finding -> " + surname);
var query = 'SELECT * FROM players WHERE surname = "' + surname + '"';
connection.query(query, function(err, rows, fields)
{
if (err) throw err;
for (var i in rows) {
console.log('Players: ', rows[i].surname);
}
});
dbDisconnect(connection);
}
exports.findPlayer = findPlayer;
After that, I've a laucher file:
require('./constants.js');
var aar = require('./libs/DBManager.js')
console.log('Loading DBManager');
var player = aar.findPlayer('Potter');
console.log(player);
Correct workflow is:
Loading DBManager
Database is connected...
Finding -> Potter
Players: Potter
Closing DB Connection
However the result is:
Loading DBManager
Finding -> Potter
Closing DB Connection
undefined
Database is connected...
Players: Potter
What's my wrong? Is some callback issue?
You are closing your connection before your query is for sure done.
you should put the dbDisconnect(connection); after the end of the query but inside the callback.
connection.query(query, function(err, rows, fields)
{
if (err) throw err;
for (var i in rows) {
console.log('Players: ', rows[i].surname);
}
dbDisconnect(connection);
});
also the findPlayer is not returning anything, so the console.log(player); will be undefined
Javascript is Async in nature, it doesn't wait for I/O operations. Any network call including request to connect to a DB comes under I/O operations.
The problem is that you are requesting a DB connection and before it is returned, you are making call to get data for players. I suggest you pass a callback method to the
dbConnect(callback)
And in that callback, you pass the query or operation you want to execute:
Create a callback method that contains the code to retrieve the players information
Call dbConnect() to establish the connection to DB and pass the callback as an argument
After connection is established, execute the callback
After you are done, you can close the connection, but make sure the code to close the connection, is with in the callback and is executed only after you received the data from Db
On the contrary if you are comfortable with ORM, i suggest you try Sequelize, it abstracts out the requirement to create and disconnect connections with DB, so you can focus on your crud operations.

phonegap app: Uncaught ReferenceError: require is not defined

I'm trying to connect to a remote database from my phonegap app.
I'm using an example script I found, but it's not working, as I keep getting require is not defined error.
Here's my code (it's inside a tag):
var Client = require('mysql').Client;
var client = new Client();
client.host ='1**.**.**.**8:****';
client.user = '*****';
client.password = '*****************';
console.log("connecting...");
client.connect(function(err, results) {
if (err) {
console.log("ERROR: " + err.message);
throw err;
}
console.log("connected.");
clientConnected(client);
});
clientConnected = function(client)
{
tableHasData(client);
}
tableHasData = function(client)
{
client.query(
'SELECT * FROM test_db.Users LIMIT 0,10',
// you can keep this function anonymous
function (err, results, fields) {
if (err) {
console.log("ERROR: " + err.message);
throw err;
}
console.log("Got "+results.length+" Rows:");
for(var i in results){
console.log(results[i]);
console.log('\n');
//console.log("The meta data about the columns:");
//console.log(fields);
}
client.end();
});
};
What am I doing wrong??
You are probably using nodeJS code that can't be run on Cordova. This must be run with node.
What you need to do is create a server (where you will run your code with nodeJS) and expose your data through an API for the client (your Cordova app) to come and fetch them. (Use AJAX requests)

zeromq push-pull guaranteed delivery (re-transmit on error)

How can I fail a PULL to get retransmit from the pusher?
In the following example, if I uncomment the // throw ... to trigger an exception on every received message, all the PULL messages are failing, but when I put back the comment, all those messages are lost and not retransmitted by the pusher.
How can I make sure the pusher will retransmit the messages if the PULL method fail?
P.S. The following code is JavaScript in Meteor framework on top of nodejs, but it's not relevant to the problem as I suppose it's similar in every zmq implementations.
var zmq = Npm.require(modulePath + '/zmq');
var sock = zmq.socket('pull');
sock.identity = 'receiving' + process.pid;
sock.bind('tcp://172.17.4.25:5551', function(err) {
if (err) throw err
if (debug) console.log('Listening for ZMQ messages')
});
sock.on('message', Meteor.bindEnvironment(function(data) {
// throw { name: 'MessagePullFailed', message: 'Failing Pull message to check if ZMQ can recover from it' }
var jsondata = JSON.parse(data)
if (jsondata.cmd == 'upsert') {
jsondata['set']['utctime'] = new Date(jsondata['set']['utctime'])
Mon.upsert({_id: jsondata._id}, {'$set': jsondata['set']}, function(err) {
if (err) console.log(err)
})
}
}))

Categories