node.js hapi.js socket memory leak on server AWS - javascript

I have a stack of servers on AWS, and it seems to be leaking memory. A heapdump is showing that I have ever increasing Sockets in an Array, with the peer of those Sockets to be the ELBs that sit in front of the server instances.
The ELBs ping with a health check every 6 seconds.
How do I close these sockets if I am using hapi.js? How do I fix this memory leak?
const config = require('./config');
const Hapi = require('hapi');
const Path = require('path');
const Fs = require('fs');
server = new Hapi.Server({
debug: { request: ['error'] },
connections: {
routes: {
files: {
relativeTo: Path.join(__dirname, 'public')
}
}
}
})
var connectionDict = {
port: config.port,
host: config.host}
server.connection(connectionDict);
module.exports = server;
server.start(function () {
setImmediate(function(){
server.log([CONTROLLER_NAME, "server-start"], 'Server started at: ' + server.info.uri);
});
});
server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {
return reply('Welcome to your API!\n').header("Connection","Close");
}
});
setInterval(function (){
global.gc();
}, 60000);
One interesting tidbit is that this server has virtually no load - the only thing hitting the servers are the ELBs.
The sockets seen above are essentially repeats of the same peer connection. I'm not sure why the server is not reusing the existing socket and creating new ones.
The .header on the reply also seems to be doing nothing. Sockets will leak whether or not "Connection:close" is on the return header.
Unfortunately, the global.gc() also doesn't clean up the sockets.
EDIT
Not that it matters, but I am using a t2.micro instance.
Simpler code, still leaking:
const Hapi = require('hapi');
const Fs = require('fs');
server = new Hapi.Server();
var connectionDict = {
port: 8443,
host: '0.0.0.0',
tls: {
key: Fs.readFileSync('./cert/https/projectchange.pem'),
cert: Fs.readFileSync('./cert/https/projectchange.cert'),
passphrase: 'somepassword'
}
}
server.connection(connectionDict);
server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {
return reply('Welcome to Change API!\n').header("Connection","Close");
}
});
server.start(function (err) {
console.log('Server started');
});
require('heapdump');
Fs.readdirSync('.').map(function (filename) {
if (filename.match(/^heapdump-/)) {
console.log(filename);
Fs.unlinkSync(filename);
}
});
setInterval(function heapDumper() {
process.kill(process.pid, 'SIGUSR2');
}, 60000);

Related

How to connect AWS elasticache redis from Node.js application?

How to connect AWS elasticache redis from Node.js application ?
You're connecting to Redis. The fact that it's a managed AWS service is not really that important in this respect.
So, use a Node.js package that implements a Redis client interface, for example:
redis
node-redis
You can use the package ioredis
and stablish a connection like this
const Redis = require('ioredis')
const redis = new Redis({
port: 6379,
host: 'your-redis-host',
connectTimeout: 10000 // optional
});
You can try connecting using ioredis.
var Redis = require('ioredis');
var config = require("./config.json");
const redis = new Redis({
host: config.host,
port: config.port,
password: config.password, // If you have any.
tls: {}, // Add this empty tls field.
});
redis.on('connect', () => {
console.log('Redis client is initiating a connection to the server.');
});
redis.on('ready', () => {
console.log('Redis client successfully initiated connection to the server.');
});
redis.on('reconnecting', () => {
console.log('Redis client is trying to reconnect to the server...');
});
redis.on('error', (err) => console.log('Redis Client Error', err));
//check the functioning
redis.set("framework", "AngularJS", function(err, reply) {
console.log("redis.set ", reply);
});
redis.get("framework", function(err, reply) {
console.log("redis.get ", reply);
});

Socket IO namespace not working with Express

I have tried setting up a namespace on the backend,
const server = require("http").createServer(app);
const connectedUsers = {};
const io = require("socket.io")(server, {
path: "/socket",
serveClient: false,
// below are engine.IO options
pingInterval: 10000,
pingTimeout: 5000,
cookie: false
});
const singularConnection = io.of("/singular-socket");
singularConnection.on("connection", socket => {
console.log("unique user connected with socket ID " + socket);
}
And on my client, I try connecting with,
const socket = io(GATEWAY, {
path: "/socket/singular-socket",
transports: ["websocket"],
jsonp: false
});
socket.connect();
socket.on("connect", () => {
console.log("connected to socket server");
});
I've tried different variation of the URL, getting rid of /socket and moving other stuff around, but I can't seem to get it working. What am I doing wrong here?
I don't have any experience in using socket.io, but from the docs...
To connect to a namespace, the client code would look like.
const socket = io('http://localhost/admin', {
path: '/mypath'
});
Here, the socket connects to the admin namespace, with the custom path
mypath.
The request URLs will look like:
localhost/mypath/?EIO=3&transport=polling&sid= (the namespace is
sent as part of the payload).
Following the above lines, your code should look like..
const socket = io("http://localhost/singular-socket", {
path: "/socket",
transports: ["websocket"],
jsonp: false
})
Where /singular-socket is the namespace and /socket is the path.
Try this repl

xterm.js reconnect with same PWD

I am using xterm.js in my web project to have a terminal on the web page. Every time I refresh my page or reconnect socket when a socket connection is broken due to internet fluctuation from the client. The current PWD directory is lost and it falls to specified CWD directory which is user home in my case. So again I have to do cd where I was working.
How can I connect and remain at same PWD where I was last time before page refreshing or socket disconnect?
One of the things I tried is to store term object and connect through the same object when reconnecting if it is already present. Not deleting process and object in on WebSocket disconnect.
var http = require('http');
var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
var pty = require('node-pty');
var cors = require('cors');
app.use(cors());
app.options('*', cors());
var terminals = {}; //global terminals
function getUser(token) {
return new Promise((resolve, reject) => {
try {
return http.get({
host: '',
path: '',
headers: {'token': token}
}, function(response) {
// Continuously update stream with data
var body = '';
response.on('data', function(d) {
body += d;
});
response.on('end', function() {
return resolve(JSON.parse(body));
});
});
} catch (err) {
console.log('Api failed');
console.log(err);
reject;
}
})
}
app.ws('/terminals/:user_id', function (ws, req) {
try {
getUser(req.params.user_id) /* cheking with api if user exist in my database*/
.then(user_info => {
if(terminals[parseInt(req.params.user_id)]){
var term = terminals[parseInt(req.params.user_id)];
}else {
var term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
name: 'xterm-color',
cwd: cwd,
env: process.env
});
terminals[parseInt(req.params.user_id)] = term;
}
term.on('data', function(data) {
ws.send(data);
});
ws.on('message', function(msg) {
term.write(msg);
});
ws.on('close', function () {
// process.kill(term.pid);
// delete terminals[parseInt(req.params.pid)];
// delete logs[req.params.pid];
});
})
.catch(err => {
})
} catch (err) {
console.log('Terminal webSocket failed');
console.log(err);
}
});
app.listen(3000);
This is not working for me. This gets me connect only first time but when I refresh my page terminal does not connect with existing store object.
Also, this has a problem if the spawned process is killed by the system but it still remains in javascript object and script try to reconnect with same term object it will fail.
Any guidelines how to achieve reconnect with same PWD.
Details
OS version: Mac OS ,
xterm.js version: 2.2.3
This can be solved very easily by just updating the ~/.bashrc on server
Putting below two line in ~/.bashrc file worked for me
PROMPT_COMMAND+='printf %s "$PWD" > ~/.storepwd'
[ -s ~/.lastdirectory ] && cd `cat ~/.lastdirectory`
Ref Save last working directory on Bash logout

Seneca-web timeout configuration

First of all I would like to say that I am new in senecajs.
I am testing this configuration.
I have configured Senecjs microservice running on port 9007, which is running and handling request correctly. When I request this service directly I receive response after cca 10s (it is request for oracle db data).
But when I request for same data but through the Hapi + Seneca-web I receive this error: "statusCode":504,"error":"Gateway Time-out"
["client","invalid_origin",{"port":9007,"pin":"mc:bankgtw","pg":"mc:bankgtw","type":"web","id":"pg:mc:bankgtw,pin:mc:bankgtw,port:9007","role":"transport","hook":"client","plugin$":{"name":"client$"},"fatal$":true,"meta$":{"mi":"wbn8u45tb7uh","tx":"o3f8eyia3f4n","id":"wbn8u45tb7uh/o3f8eyia3f4n","pattern":"hook:client,role:transport,type:web","action":"(q1yytemztu3k)","plugin_name":"transport","plugin_tag":"-","prior":{"chain":[],"entry":true,"depth":0},"start":1487199713842,"sync":true},"tx$":"o3f8eyia3f4n","host":"0.0.0.0","path":"/act","protocol":"http","timeout":5555,"max_listen_attempts":11,"attempt_delay":222,"serverOptions":{}},{"kind":"res","res":null,"error":{"isBoom":true,"isServer":true,"output":{"statusCode":504,"payload":{**"statusCode":504,"error":"Gateway Time-out**","message":"Client request timeout"},"headers":{}}},"sync":true,"time":{"client_recv":1487199799177}}]
A few seconds before microservice return data.
And this is my configuration:
const Hapi = require('hapi');
const Seneca = require('seneca');
const SenecaWeb = require('seneca-web');
const config = {
adapter: require('seneca-web-adapter-hapi'),
context: (() => {
const server = new Hapi.Server();
server.connection({
port: 3001,
routes: {
cors: true,
payload:{timeout:60000},
timeout:{server: 60000, socket:90000}
}
});
server.route({
path: '/routes',
method: 'get',
handler: (request, reply) => {
const routes = server.table()[0].table.map(route => {
return {
path: route.path,
method: route.method.toUpperCase(),
description: route.settings.description,
tags: route.settings.tags,
vhost: route.settings.vhost,
cors: route.settings.cors,
jsonp: route.settings.jsonp,
server: server.info
}
})
reply(routes)
}
});
return server;
})()
};
const seneca = Seneca({timeout: 99999})
.use(SenecaWeb, config)
.use(require('./hapi_api.js'))
.client({ port:9007, pin:'mc:bankgtw' })
.ready(() => {
const server = seneca.export('web/context')();
server.start(() => {
server.log('server started on: ' + server.info.uri);
});
});
What I am doing wrong or what timeout is causing this?
I've had the same issue, fixed it, but its VERY BAD PRACTICE.
Go to 'transport.js' at seneca-transport folder.
You will see 'timeout: 5555'
Go ahead and change that to whatever you need.
I'm not sure why this is not getting USER defaults.
To the best of my knowledge, this is referring to client timeout. make sure you still use server timeout.

How to mock a server error in hapi

I have set up a hapi server as follows:
'use strict'
// node modules
const Hapi = require('hapi')
const Inert = require('inert')
const Path = require('path')
// server config
const server = new Hapi.Server()
const port = 4000
server.connection({
port: port
})
// Hapi plugins
const plugins = [
Inert
]
server.register(plugins, (err) => {
if (err) {
throw err
}
server.route([
{
method: 'GET',
path: '/',
handler: (request, reply) => {
reply.file(Path.join(__dirname, '../front/production/index.html'))
}
},
{
method: 'GET',
path: '/{param*}',
handler: {
directory: {
path: 'front/production'
}
}
}
])
})
module.exports = server
I have written tests for it, but have a problem being able to cover this line:
if (err) {
throw err <----
}
how can I mock a server error in server.register, so far I have tried:
server.inject({method: 'GET', url: '/notanendpoint'}...
server.inject({method: 'NOTMETHOD', url: '/'}...
server.inject({method: 'GET', url: '/', simulate: {error: true}}...
I've been looking through the hapi api docs and cant work out how to cover this line, any help would be greatly appreciated.
Let me know if I can add more description to my question or include what I have tried in my tests (let me know if you want to see more of my test file)

Categories