socket.io client persistent retries to unreachable host - node.js

I'm trying to get a persistent connection from my socket.io-client (running on Node.js) to a remote websocket. I do not have control over the remote socket, and sometimes it can go down entirely. I would like to attempt to reconnect() whenever an error or disconnect occurs. In the following example, I'm trying to test the case where the remote host is refusing a connection. In this case, I would like to attempt to reconnect after 1 second. It calls a second time, and exits.
Here's the code:
var events = require('events'),
util = require('util'),
io = require('socket.io-client'),
url = "ws://localhost:12345", // intentionally an unreachable URL
socketOptions = {
"transports" : [ "websocket" ],
"try multiple transports" : false,
"reconnect" : false,
"connect timeout" : 5000
};
// The goal is to have this socket attempt to connect forever
// I would like to do it without the built in reconnects, as these
// are somewhat unreliable (reconnect* events not always firing)
function Test(){
var self = this;
events.EventEmitter.call(self);
var socket;
function reconnect(){
setTimeout(go, 1000);
}
function go(){
console.log("connecting to", url, socketOptions);
socket = io.connect(url, socketOptions);
socket.on('connect', function(){
console.log("connected! wat.");
});
socket.on('error', function(err){
console.log("socket.io-client 'error'", err);
reconnect();
});
socket.on('connect_failed', function(){
console.log("socket.io-client 'connect_failed'");
reconnect();
});
socket.on('disconnect', function(){
console.log("socket.io-client 'disconnect'");
reconnect();
});
}
go();
}
util.inherits(Test, events.EventEmitter);
var test = new Test();
process.on('exit', function(){
console.log("this should never end");
});
When running it under node 0.11.0 I get the following:
$ node socketio_websocket.js
connecting to ws://localhost:12345 { transports: [ 'websocket' ],
'try multiple transports': false,
reconnect: false,
'connect timeout': 5000 }
socket.io-client 'error' Error: connect ECONNREFUSED
at errnoException (net.js:878:11)
at Object.afterConnect [as oncomplete] (net.js:869:19)
connecting to ws://localhost:12345 { transports: [ 'websocket' ],
'try multiple transports': false,
reconnect: false,
'connect timeout': 5000 }
this should never end

The ECONNREFUSED is an exception you don't manage.
Try with this:
process.on('uncaughtException', function(err) {
if(err.code == 'ECONNREFUSED'){
reconnect();
}
}
Edit
Modify the options like this:
socketOptions = {
"transports" : [ "websocket" ],
"try multiple transports" : false,
"reconnect" : false,
'force new connection': true, // <-- Add this!
"connect timeout" : 5000
};
and the reconnect function (look in the comments for the explanation)
function reconnect(){
socket.removeAllListeners();
setTimeout(go, 1000);
}
Probably socket.io reuse the same connection without creating a new one, forcing it the app works

Related

The NodeJS socket.io server is not responding

You can see for yourself that the request hangs:
curl "http://europasprak.com:9001/socket.io/?EIO=4&transport=polling"
It also hangs when sending the request from the server machine itself:
curl "http://localhost:9001/socket.io/?EIO=4&transport=polling"
I'm trying to create a socket.io server following the documentation.
I have the NodeJS socket server:
var http = require('http');
var socketio = require('socket.io');
var httpServer = http.createServer(utils.httpHandler);
httpServer.listen(config.socketio.port, function() {
console.log('The NodeJS HTTP server [port: ' + config.socketio.port + '] is listening...');
});
Using the DEBUG variable to start the server as in:
DEBUG=socket* node /usr/local/learnintouch/engine/api/js/socket/elearning-server.js 2>&1 >> /usr/local/learnintouch/logs/nodejs.log
shows:
socket.io:server creating engine.io instance with opts {"cors":{"origin":"*"},"path":"/socket.io"} +2ms
socket.io:server attaching client serving req handler +5ms
socket.io:server initializing namespace / +1ms
socket.io:server initializing namespace /elearning +10ms
The NodeJS log shows:
The NodeJS HTTP server [port: 9001] is listening...
The server object is:
{
io: Server {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
_nsps: Map { '/' => [Namespace] },
parentNsps: Map {},
_path: '/socket.io',
clientPathRegex: /^\/socket\.io\/socket\.io(\.min|\.msgpack\.min)?\.js(\.map)?$/,
_connectTimeout: 45000,
_serveClient: true,
_parser: {
protocol: 5,
PacketType: [Object],
Encoder: [Function: Encoder],
Decoder: [Function: Decoder]
},
encoder: Encoder {},
_adapter: [Function],
sockets: Namespace {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
sockets: Map {},
_fns: [Array],
_ids: 0,
server: [Circular],
name: '/',
adapter: [RedisAdapter],
[Symbol(kCapture)]: false
},
opts: { cors: [Object] },
[Symbol(kCapture)]: false
}
}
The connection is then handled like this:
module.exports.io = socketio(httpsServer, {
cors: {
origin: '*',
}
});
module.exports.io.adapter(ioredis({ host: config.redis.hostname, port: config.redis.port }));
var redisClient = redis.createClient(config.redis.port, config.redis.hostname);
module.exports.io.use(function (socket, handler) {
console.log('The namespace middleware is registered');
console.log(socket.request.headers.cookie);
if (socket.request.headers.cookie) {
socket.request.cookies = cookie.parse(decodeURIComponent(socket.request.headers.cookie));
socket.request.sessionID = socket.request.cookies['PHPSESSID'];
socket.request.socketSessionId = socket.request.cookies['socketSessionId'];
console.log("Authorization attempt with sessionID: " + socket.request.sessionID + " and socketSessionId: " + socket.request.socketSessionId);
redisClient.get("PHPREDIS_SESSION:" + socket.request.sessionID, function (error, reply) {
if (error) {
console.log("The redis client had an error: " + error);
return handler(new Error('The connection was refused because the redis client had an error.'));
} else if (!reply) {
console.log('The connection was refused because the redis client did not find the sessionID.');
return handler(new Error('The connection was refused because the redis client did not find the sessionID.'));
} else {
var redisSocketSessionId = utils.getRedisValue(reply, "socketSessionId");
if ('undefined' == typeof socket.request.socketSessionId || redisSocketSessionId != socket.request.socketSessionId) {
console.log('The connection was refused because the socketSessionId was invalid.');
return handler(new Error('The connection was refused because the socketSessionId was invalid.'));
} else {
console.log('The connection was granted.');
handler();
}
}
});
} else {
console.log('The connection was refused because no cookie was transmitted.');
return handler(new Error('The connection was refused because no cookie was transmitted.'));
}
});
The client connection:
<script type="text/javascript">
var elearningSocket;
$(function() {
if ('undefined' != typeof io && 'undefined' == typeof elearningSocket) {
console.log("Creating a socket on //dev.learnintouch.com:9001/elearning");
elearningSocket = io.connect('//dev.learnintouch.com:9001/elearning', { reconnect: true, rejectUnauthorized: false });
}
if ('undefined' != typeof elearningSocket) {
console.log("A socket on //dev.learnintouch.com:9001/elearning has been created");
elearningSocket.on('connect', function() {
console.log("The elearning namespace socket connected");
elearningSocket.emit('watchLiveCopilot', {'elearningSubscriptionId': '63', 'elearningClassId': '7'});
});
elearningSocket.on('postLogin', function(data) {
isAdmin = data.admin;
});
elearningSocket.on('message', function(message) {
console.log(message);
});
}
});
</script>
And the Chrome browser console:
Creating a socket on //dev.learnintouch.com:9001/elearning
A socket on //dev.learnintouch.com:9001/elearning has been created
But when sending a client connection, the log never shows the The namespace middleware is registered message.
Versions:
http#0.0.1-security
https#1.0.0
socket.io#4.1.3
cors#2.8.5
redis#3.1.2
socket.io-redis#6.1.1
connect#3.7.0
cookie#0.4.1
lodash#4.17.21
It looks like the connection cannot be established.
After a while the browser console shows an ERR_EMPTY_RESPONSE message as in:
socket.io.min.js:6 GET http://dev.learnintouch.com:9001/socket.io/?EIO=4&transport=polling&t=NbGNn6V net::ERR_EMPTY_RESPONSE
But the NodeJS socket server seems to be listening:
netstat -l | grep 9001
tcp6 0 0 [::]:9001 [::]:* LISTEN
The NodeJS socket server is not reachable.
But the firewall seems not to be the issue:
sudo ufw status verbose
Status: active
To Action From
-- ------ ----
9001 ALLOW IN Anywhere
The error can be seen live at
http://www.europasprak.com/elearning/subscription/2938/course
by first logging at
http://www.europasprak.com/engine/modules/user/login.php
with using the user demo#demo.com with the demo password.
UPDATE: I could solve the issue. It related to a handler preventing the connection from being established.
When changing the following:
module.exports.io.use((socket, handler) => {
to the following:
module.exports.io.of('/elearning').use((socket, handler) => {
the connection could then be done.
you need to determine whether the socket.io connection is successful, and then add events such as cores, adapters, events etc...to track down the problem step by step.
judging from the error report you provided above, socket.io was not established successfully.

When mongodb server is down how to catch the error while running mongoose query

I am using mongoose for connecting node.js with mongoDB, now i wrote below query
var trans = new transmodel({method: method, trans_id: r});
trans.save(function(err) {
if (err) {
console.error("Razor_pay_webhook Error 4 err: " + err);
res.write('statusCode: 200');
res.end();
} else {
res.write('statusCode: 400');
res.end();
}
});
I thought when my mongodb cluster will be down then i will get 'err' while executing above mongoose query, but when i ran above query while my mongo cluster was down nothing happened(No err was called). Can anyone please tell me how can i catch the error if my mongodb server is down inside my query. Also for reconnecting again with my cluster i have set below parameters but my node server is not trying to reconnect again with my mongodb server i don't know what's going wrong.
var mongoose = require('mongoose');
var config = require('./config/database.js');
var DB_URL = config.db.url;
mongoose.connection.on("connected", function(ref) {
console.log("Connected to " + " DB!");
});
mongoose.connection.on("error", function(err) {
console.error('Failed to connect to DB ' + ' on startup ', err);
if (err) {
return next(err);
}
});
mongoose.connection.on('disconnected', function(err) {
console.log('Mongoose default connection to DB :' + ' disconnected');
if (err) {
return next(err);
}
});
var gracefulExit = function() {
mongoose.connection.close(function () {
console.log('Mongoose default connection with DB :' + ' is disconnected through app termination');
process.exit(0);
});
}
process.on('SIGINT', gracefulExit).on('SIGTERM', gracefulExit);
exports.con_close = function () {
console.log('Mongoose connection disconnected');
mongoose.connection.close();
}
var options = {
server: {
socketOptions: {
keepAlive: 1000,
connectTimeoutMS: 30000
}
},
replset: {
rs_name: 'replicaset',
auto_reconnect:true,
socketOptions: {
keepAlive: 1000, // doubt about it
connectTimeoutMS: 30000
}
},
user: 'root',
pass: 'G3saGT2Y',
auth: {
authdb: 'admin'
}
}
mongoose.connect(DB_URL, options, function(err) {
console.log('ho rha hai');
if (err) {
console.log('error connection to mongo server!');
console.log(err);
}
});
You are using mongoose, it emits events (the EventEmitter pattern) when the database is down and when the database is reconnecting and up again.
from mongoose code found here we can see that the library db connection - connection.js
has the following events that are emitted:
* #param {Mongoose} base a mongoose instance
* #inherits NodeJS EventEmitter
http://nodejs.org/api/events.html#events_class_events_eventemitter
* #event connecting: Emitted when connection.{open,openSet}() is executed on this connection.
#event connected: Emitted when this connection successfully connects to the db. May be emitted multiple times in reconnected scenarios.
#event open: Emitted after we connected and onOpen is executed on all of this connections models.
#event disconnecting: Emitted when connection.close() was executed.
#event disconnected: Emitted after getting disconnected from the db.
#event close: Emitted after we disconnected and onClose executed on all of this connections models.
#event reconnected: Emitted after we connected and subsequently disconnected, followed by successfully another successfull connection.
#event error: Emitted when an error occurs on this connection.
#event fullsetup: Emitted in a replica-set scenario, when primary and at
least one seconaries specified in the connection string are connected.
#event all: Emitted in a replica-set scenario, when all nodes specified in the connection string are connected.
When the database is down you will receive two events:
1. disconnected
2. error (the error that driver encountered)
When the database is up again you will receive the reconnect event.
So you don't need to try catch the error rather you should listen to these events.
More helpful information about connection failures and reconnecting can be found here.
This article explain how to use and configure the autoReconnect and the bufferMaxEntries according to your settings.

Node 4.1.0 TLSSocket issues

Strange behavior in Node with TLSSocket and tls.connect.
var port = 7000;
var host = '94.125.182.252'; //freenode
var tls = require('tls');
var net = require('net');
var socket = new net.Socket();
var secure;
secure = new tls.TLSSocket( socket, {
isServer: false,
rejectUnauthorized: false
});
// edit (left out of original post, but present in my test code, whoops)
secure.connect( {
port: port,
host: host
});
secure.setEncoding( 'utf8' );
secure.on( 'connect' , function() {
console.log( 'connected' );
})
.on( 'secureConnect', function() {
console.log( 'secure connect' );
})
.on( 'error', function( e ) {
console.log( 'error', e );
})
.on( 'data', function( data ) {
console.log( data );
});
if ( secure.isPaused() ) {
console.log( 'socket was paused' );
secure.resume();
}
This doesn't even attempt to connect and no error messages are produced. I have wireshark monitoring and there is no activity captured.
A different approach:
secure = tls.connect( {
rejectUnauthorized: false,
host: host,
port: port,
socket: socket
});
Same story, nothing captured, no errors. If I remove the socket: socket aspect above it will connect. This makes some sense as the docs state that if the socket option is specified it will ignore port and host. The above works on my previous Node version( 0.12.7).
If I want to use the existing socket I have to tell it to connect before calling tls.connect.
socket.connect( {
port: port,
host: host
});
secure = tls.connect( {
rejectUnauthorized: false,
socket: socket
});
This doesn't seem proper.
Passing a connecting socket to tls.TLSSocket( socket, ...) seems to have no effect.
The 'connect' event is fired but I imagine that is not related to TLSSocket.
I could not get tls.TLSSocket(...) to work on previous Node iterations.
Stepping through with node debug did not expose any obvious problems.
The options for net.Socket([options]) don't seem to accept a port or host for configuring until you try to connect, and trying to connect before passing to tls.connect seems counter intuitive. It would suggest that is not the intended usage.
So my questions would be:
What am I doing wrong with tls.TLSSocket() or perhaps is it a bug?
Am I correct to assume that passing an existing socket into tls.connect() is for already established connections switching protocol? If not, whats the proper way to assign a port and host?
Edit:
As per suggestion:
secure = tls.connect( {
rejectUnauthorized: false,
socket: socket
});
socket.connect( {
port: port,
host: host
});
This works.
secure = new tls.TLSSocket( socket , {
isServer: false,
rejectUnauthorized: false
});
socket.connect( {
port: port,
host: host
});
Unfortunately this does not work. A 'connect' event is emitted, never a 'secureConnect' and never any other events or data.
In your first two (non-working) examples, you only created a socket and never started actually connected it. Add a socket.connect(); at the end of your original code and it should work fine.
tls.connect() when passed a plain socket, does not actually call socket.connect(); internally, it merely sets up to start listening for data on the socket so it can decrypt incoming data properly.

Socket.get callback not triggered in socket.on function

I've been stuck on this issue for a while the answer might be really basic but I fail to understand what the problem is. AFAIU It execute the function but doesnt trigger the callback and I dont know why.
My script aim to have both a tcp server to have a device (raspberry pi) that connect a tcp socket and a client to connect to a websocket on a sailsjs app.
I manage to have both this thing running on the following code, the problem is they only work separatly, simultanuously but separatly, when I try a get outside the socket everything works fine but when I do inside, the io.socket object is just piling up the get request in a requestQueue.
{ useCORSRouteToGetCookie: true,
url: 'http://localhost:1337',
multiplex: undefined,
transports: [ 'polling', 'websocket' ],
eventQueue: { 'sails:parseError': [ [Function] ] },
query:'__sails_io_sdk_version=0.11.0&__sails_io_sdk_platform=node&__sails_io_sdk_language=javascript',
_raw:
{ socket:
{ options: [Object],
connected: true,
open: true,
connecting: false,
reconnecting: false,
namespaces: [Object],
buffer: [],
doBuffer: false,
sessionid: '0xAlU_CarIOPQAGUGKQW',
closeTimeout: 60000,
heartbeatTimeout: 60000,
origTransports: [Object],
transports: [Object],
heartbeatTimeoutTimer: [Object],
transport: [Object],
connectTimeoutTimer: [Object],
'$events': {} },
name: '',
flags: {},
json: { namespace: [Circular], name: 'json' },
ackPackets: 0,
acks: {},
'$events':
{ 'sails:parseError': [Function],
connect: [Object],
disconnect: [Function],
reconnecting: [Function],
reconnect: [Function],
error: [Function: failedToConnect],
undefined: undefined } },
requestQueue:
[ { method: 'get', headers: {}, data: {}, url: '/', cb: [Function] },
{ method: 'get', headers: {}, data: {}, url: '/', cb: [Function] } ] }
The code is the following :
//library to connect to sailsjs websockets
var socketIOClient = require('socket.io-client');
var sailsIOClient = require('sails.io.js');
//library to do the tcp server
var net = require('net');
// Instantiate the socket client (`io`)
// (for now, you must explicitly pass in the socket.io client when using this library from Node.js)
var io = sailsIOClient(socketIOClient);
// Set some options:
// (you have to specify the host and port of the Sails backend when using this library from Node.js)
io.sails.url = 'http://localhost:1337';
var server = net.createServer(function(tcpSocket) { //'connection' listener
//socket was sucessfully connected
console.log('client connected');
//notify on deconnection
tcpSocket.on('end', function() {
console.log('client disconnected');
});
// Handle incoming messages from clients.
tcpSocket.on('data', function (data) {
console.log(data.toString('utf8', 0, data.length));
//if data is PING respond PONG
if(data.toString('utf8', 0, 4)=='PING'){
console.log('I was pinged');
tcpSocket.write('PONG\r\n');
}
console.log(io.socket);//debugging purpose
//trigger a socket call on the sails app
io.socket.get('/', function (body, JWR) {
//display the result
console.log('Sails responded with: ', body);
console.log('with headers: ', JWR.headers);
console.log('and with status code: ', JWR.statusCode);
});
});
});
server.listen(8124, function() { //'listening' listener
console.log('server bound');
});
It looks like your socket isn't autoconnecting. Try connecting manually:
// Instantiate the socket client (`io`)
// (for now, you must explicitly pass in the socket.io client when using this library from Node.js)
var io = sailsIOClient(socketIOClient);
// Set some options:
// (you have to specify the host and port of the Sails backend when using this library from Node.js)
io.sails.url = 'http://localhost:1337';
var socket = io.sails.connect();
socket.on('connect', function() {
... connect TCP server and continue ...
});
I found a solution, I just got rid of sails.io.js and used plain socket.io it now works as intended feel free to explain though why it didnt in sails.io.js
//library to connect to sailsjs websockets
var socketIOClient = require('socket.io-client');
//var sailsIOClient = require('sails.io.js');
//library to do the tcp server
var net = require('net');
var socket=socketIOClient.connect('http://localhost:1337', {
'force new connection': true
});
var server = net.createServer(function(tcpSocket) { //'connection' listener
//socket was sucessfully connected
console.log('client connected');
//notify on deconnection
tcpSocket.on('end', function() {
console.log('client disconnected');
});
// Handle incoming messages from clients.
tcpSocket.on('data', function (data) {
console.log(data.toString('utf8', 0, data.length));
console.log(data.toString('utf8', 0, data.length));
//if data is PING respond PONG
if(data.toString('utf8', 0, 4)=='PING'){
console.log('I was pinged');
tcpSocket.write('PONG\r\n');
}
if(data.toString('utf8', 0, 4)=='test'){
socket.emit('test',{message : 'test'});
//io.socket.disconnect();
}
});
});

Nodejs HTTPS client timeout not closing TCP connection

I need to close http connection if they take longer than 3s, so this is my code:
var options = {
host: 'google.com',
port: '81',
path: ''
};
callback = function(response) {
var str = '';
response.on('data', function (chunk) {
str += chunk;
});
response.on('end', function () {
console.log(str);
});
response.on('error', function () {
console.log('ERROR!');
});
}
var req = https.request(options, callback);
req.on('socket', function(socket) {
socket.setTimeout(3000);
socket.on('timeout', function() {
console.log('Call timed out!');
req.abort();
//req.end();
//req.destroy();
});
});
req.on('error', function(err) {
console.log('REQUEST ERROR');
console.dir(err);
req.abort();
//req.end();
});
req.end();
This is what I get after 3s:
Call timed out!
REQUEST ERROR
{ [Error: socket hang up] code: 'ECONNRESET' }
Using a watch on lsof | grep TCP | wc -l I can see that the TCP connection remains open, even after receiving the 'timeout' event.
After an eternity, I get this and the connection is closed:
REQUEST ERROR
{ [Error: connect ETIMEDOUT] code: 'ETIMEDOUT', errno: 'ETIMEDOUT', syscall: 'connect' }
Does anyone know why this is happening? Why does calling req.abort() or req.end() or req.destory() not close the connection? Is it because I'm setting the timeout on the socket instead of the actual HTTP call? If yes, how do I close the connection?
you need to set the timeout on the connection:
req.connection.setTimeout(3000);
This timeout will change the socket status from ESTABLISHED to FIN_WAIT1 and FIN_WAIT2.
In Ubuntu there is a default timeout of 60 seconds for FIN_WAIT socket status, so the total time for the socket to close is 63 seconds if it doesn't receive any traffic. If the sockets receive traffic, the timeouts will start over.
If you need to close the socket within 3 seconds, I guess you have to set the connection timeout to 3000ms and lower the kernel tcp fin wait timeout.

Resources