Extending Node's EventEmitter in ES5 - node.js

I'm learning JavaScript/Nodejs and I've decided to start with ES5 before looking at ES6. I'm looking at prototypical inheritance and the EventEmitter.
I want to extend EventEmitter with something simple, my code sample is below. However I receive the error:
this.emit('messageRead', message);
TypeError: this.emit is not a function
What am I doing wrong?
var EventEmitter = require('events').EventEmitter;
var MyEmitter = function() {
EventEmitter.call(this);
this.messages = []
}
MyEmitter.prototype = Object.create(EventEmitter.prototype);
MyEmitter.prototype.constructor = MyEmitter;
MyEmitter.prototype.addMessage = function(message) {
this.messages.push(message)
this.emit('messageAdded', message);
return this;
}
MyEmitter.prototype.readMessages = function() {
this.messages.forEach(function(message){
this.emit('messageRead', message);
});
return this;
}
var emitter1 = new MyEmitter();
emitter1
.addMessage('hello')
.addMessage('goodbye')
.on('messageAdded', function(message) { console.log('message added: ' + message)})
.on('messageRead', function(message) { console.log('message read: ' + message)})
.readMessages();

You must inherit from EventEmitter, not only run constructor in MyEmitter instance scope
const util = require('util');
...
util.inherits(MyEmitter, EventEmitter);

The issue with my code was with this and closures. See the updated readMessages below.
But thanks to #Yarsolav who has probably pointed at a better solution.
var EventEmitter = require('events').EventEmitter;
var MyEmitter = function() {
EventEmitter.call(this);
this.messages = []
}
MyEmitter.prototype = Object.create(EventEmitter.prototype);
MyEmitter.prototype.constructor = MyEmitter;
MyEmitter.prototype.addMessage = function(message) {
this.messages.push(message)
this.emit('messageAdded', message);
return this;
}
MyEmitter.prototype.readMessages = function() {
var that = this;
this.messages.forEach(function(message){
that.emit('messageRead', message);
});
return this;
}
var emitter1 = new MyEmitter();
emitter1
.on('messageAdded', function(message) { console.log('message added: ' + message)})
.on('messageRead', function(message) { console.log('message read: ' + message)})
.addMessage('hello')
.addMessage('goodbye')
.readMessages();

Related

Issue with EventEmitter inheritance

I'm having a small issue, playing around with module patterns. I'm trying to attach an eventemitter to my library, but it doesn't seem to work and I get :
cmd.on('message',function(msg){
^
TypeError: undefined is not a function
My lib looks like :
var util = require('util');
var EventEmitter = require("events").EventEmitter;
var TestLib = function() {
var self = this;
function sendRandom(cb){
self.emit('message','whatever');
cb(null,0);
}
return {
init: function(cb) {
console.log('init');
cb(null);
},
run: function(params,cb){
console.log('running ',params);
sendRandom(function(err,res){
if(err){
cb(new Error(err));
}else{
cb(null,res);
}
});
},
close: function(cb) {}
};
};
util.inherits(TestLib, EventEmitter);
module.exports = TestLib;
And I call it as such :
var cmd = require(__dirname+'/bin/testlib.js')();
cmd.on('message',function(msg){
log(msg);
});
cmd.init(function(err){
if(err){
log(err);
}else{
cmd.run(line,function(err,res){
if(err){
log(err);
}else{
log(res);
}
});
}
});
I'm sure I'm overlooking something simple, but what?
see whether the example below helps you.
testLib.js
var EventEmitter = require('events').EventEmitter;
var util = require('util');
function TestLib(ms) {
var self = this;
EventEmitter.call(this);
this.random = function() {
//emitting 'random' event randomly
var ms = Math.random() * 100000 % 3000;
console.log('emitting random event in ' + ms + ' milliseconds');
setTimeout(function() {
self.emit('random', ms);
self.random();
}, ms);
return self;
}
}
util.inherits(TestLib, EventEmitter);
module.exports = TestLib;
test.js
var TestLib = require('./testLib');
new TestLib()
.random()
.on('random', function(ms) {
console.log('random event emitted after ' + ms + ' milliseconds');
});
to run it, execute node test.js

making event in module.exports functions

I'm trying to make a module with an even that I could call from the index file (after a require).
My code includes var events = require("events"); and I've wrote only the tricky part here.
index.js:
var reqw = require('./module.js');
reqw.on('data', function(d) {
console.log(d);
});
module.js:
module.exports = {
listaccts: function() {
events.EventEmitter.call(this);
}
}
util.inherits(exports.listaccts, events.EventEmitter);
exports.listaccts.prototype.listme = function() {
thisList = this;
var req = https.request(requestOptions, function(res) {
res.on('data', function(chuck) {
store = chuck;
});
res.on('end', function(d) {
thisList.emit("data", store.toString());
});
});
}
Searched the whole we and yet to find a proper answer..
Modified your code slightly :
module.js
function listaccts(){
}
util.inherits(listaccts, EventEmitter);
listaccts.prototype.listMe = function(){
var self = this;
var store = [];
console.log('here');
var req = https.request(requestOptions, function(res) {
res.on('data', function(chuck) {
console.log('data');
store.push(chuck);
});
res.on('end', function() {
console.log('end');
self.emit("data", store);
});
});
req.end();
};
module.exports = listaccts;
index.js
var reqw = require('./module');
var obj = new reqw();
obj.listMe();
obj.on('data', function(err, data) {
console.log(err);
});
req.end is important, I have forgot to include and got a never-ending cycle.
Created instance for binding this, so no need for EventEmitter.call.
Maybe you want to the listMe function to inside your constructor.
Hope this help.

NodeJS: Out of memory when sending messages to child_processes

My goal is to create a parent process that streams messages to the child processes indefinitely. To test this, i tried the below code for 150M messages to a child process that does nothing.
(Possible Answer Edited Below) Why would this code run out of memory?
I am using the ofe module but I do not know what to look for in the resulting heapdump, what method could I have used to search the heapdump for clues?
code:
server.js
// After 20M messages i get:
// FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory
var cp = require('child_process');
var xrange = require('xrange');
var maxChildren = require('os').cpus().length;
var util = require('util');
require('ofe').call();
var childError = function(err) {
console.log('Error: ' + err);
};
var childExit = function(code, signal) {
console.log('Exit Code: ' + code);
console.log('Exit Signal: ' + signal);
};
var childClose = function(code, signal) {
console.log('Close Code: ' + code);
console.log('Close Signal: ' + signal);
};
var childDisconnect = function() {
console.log('Disconnect');
};
var childMessage = function(msg, handle) {
console.log('Msg: ' + msg);
};
var createChild = function(){
var child = cp.fork("./child");
child.on('error', childError);
child.on('exit', childExit);
child.on('close', childClose);
child.on('disconnect', childDisconnect);
child.on('message', childMessage);
console.log("Child Created: " + child.pid);
return child;
}
var createChildren = function(){
var children = [];
xrange(maxChildren).each(function(i) {
children[i] = createChild();
});
return children;
}
var sendMessages = function(children) {
xrange(150000000).each(function(num) {
var idx = num % maxChildren;
if (num % 1000000 == 0) {
console.log(num);
}
children[idx].send(num);
});
};
child.js (contents)
process.on('message', function(msg) {});
Answer
Since this code is asynchronous , the parent process will send all the messages to the children without waiting for them to be processed, which seems to overload the clients. I believe the solution is to send a message back from the clients to 'pull' the next number after processing. Assuming this is the answer, I have a follow up question.
1) I'd like to write this but i'm not sure how to turn the xrange into a generator without the ability to yield (no-harmony nodejs), is there a callback solution? :
function getNextNumber(){
//pull the next available number from the 150M
}
child.on('message',function(msg) {
child.send(getNextNumber());
});
Followup
If I just wanted to iterate, this seems to be the way: https://stackoverflow.com/a/5784473/1578888 .
If I am reading this alternate answer correctly, it seems that implementing the xrange call as a true generator is not possible pre-harmony: https://stackoverflow.com/a/7442013/1578888
This is the code i ended up using (also renamed server.js to parent.js):
parent.js
var cp = require('child_process');
var xrange = require('xrange');
var maxChildren = require('os').cpus().length;
var util = require('util');
require('ofe').call();
var childError = function(err) {
console.log('Error: ' + err);
};
var childExit = function(code, signal) {
console.log('Exit Code: ' + code);
console.log('Exit Signal: ' + signal);
};
var childClose = function(code, signal) {
console.log('Close Code: ' + code);
console.log('Close Signal: ' + signal);
};
var childDisconnect = function() {
console.log('Disconnect');
};
var childMessage = function(msg, handle) {
//no output anymore!
//console.log('Msg: ' + msg);
};
var createChild = function(){
var child = cp.fork("./child.js");
child.on('error', childError);
child.on('exit', childExit);
child.on('close', childClose);
child.on('disconnect', childDisconnect);
child.on('message', childMessage);
console.log("Child Created: " + child.pid);
return child;
}
var getNextFn = (function () {
var i = 0
return function(cb) {
i = i + 1;
if (i < 150000000) {
return i;
} else {
return null;
}
}
})();
var createChildren = function(){
var children = [];
xrange(maxChildren).each(function(i) {
var child = createChild();
child.on('message', function(msg) {
var next = getNextFn();
if (next) {
if (next % 1000000 == 0) {
console.log(next + " " + new Date().toISOString());
}
child.send(next);
} else {
child.kill();
}
});
children[i] = child;
});
return children;
}
var c = createChildren();
child.js
process.on('message', function(msg) {
process.send({});
});
process.send({});

Implementing STARTTLS in a protocol in NodeJS

I'm trying to add a STARTTLS upgrade to an existing protocol (which currently works in plaintext).
As a start, I'm using a simple line-based echoing server (it's a horrible kludge with no error handling or processing of packets into lines - but it usually just works as the console sends a line-at-a-time to stdin).
I think my server is right, but both ends exit with identical errors when I type starttls:
events.js:72
throw er; // Unhandled 'error' event
^
Error: 139652888721216:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:../deps/openssl/openssl/ssl/s23_clnt.c:766:
at SlabBuffer.use (tls.js:232:18)
at CleartextStream.read [as _read] (tls.js:450:29)
at CleartextStream.Readable.read (_stream_readable.js:320:10)
at EncryptedStream.write [as _write] (tls.js:366:25)
at doWrite (_stream_writable.js:221:10)
at writeOrBuffer (_stream_writable.js:211:5)
at EncryptedStream.Writable.write (_stream_writable.js:180:11)
at Socket.ondata (stream.js:51:26)
at Socket.EventEmitter.emit (events.js:95:17)
at Socket.<anonymous> (_stream_readable.js:746:14)
Have I completely misunderstood how to do an upgrade on the client side?
Currently, I'm using the same method to add TLS-ness to plain streams at each end. This feels wrong, as both client and server will be trying to play the same role in the negotiation.
tlsserver.js:
r tls = require('tls');
var net = require('net');
var fs = require('fs');
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
// This is necessary only if using the client certificate authentication.
requestCert: true,
// This is necessary only if the client uses the self-signed certificate.
ca: [ fs.readFileSync('client-cert.pem') ],
rejectUnauthorized: false
};
var server = net.createServer(function(socket) {
socket.setEncoding('utf8');
socket.on('data', function(data) {
console.log('plain data: ', data);
// FIXME: this is not robust, it should be processing the stream into lines
if (data.substr(0, 8) === 'starttls') {
console.log('server starting TLS');
//socket.write('server starting TLS');
socket.removeAllListeners('data');
options.socket = socket;
sec_socket = tls.connect(options, (function() {
sec_socket.on('data', function() {
console.log('secure data: ', data);
});
return callback(null, true);
}).bind(this));
} else {
console.log('plain data', data);
}
});
});
server.listen(9999, function() {
console.log('server bound');
});
client.js:
var tls = require('tls');
var fs = require('fs');
var net = require('net');
var options = {
// These are necessary only if using the client certificate authentication
key: fs.readFileSync('client-key.pem'),
cert: fs.readFileSync('client-cert.pem'),
// This is necessary only if the server uses the self-signed certificate
ca: [ fs.readFileSync('server-cert.pem') ],
rejectUnauthorized: false
};
var socket = new net.Socket();
var sec_socket = undefined;
socket.setEncoding('utf8');
socket.on('data', function(data) {
console.log('plain data:', data);
});
socket.connect(9999, function() {
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(data) {
if (!sec_socket) {
console.log('sending plain:', data);
socket.write(data);
} else {
console.log('sending secure:', data);
sec_socket.write(data);
}
if (data.substr(0, 8) === 'starttls') {
console.log('client starting tls');
socket.removeAllListeners('data');
options.socket = socket;
sec_socket = tls.connect(options, (function() {
sec_socket.on('data', function() {
console.log('secure data: ', data);
});
return callback(null, true);
}).bind(this));
}
});
});
Got it working, thanks to Matt Seargeant's answer. My code now looks like:
server.js:
var ts = require('./tls_socket');
var fs = require('fs');
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
// This is necessary only if using the client certificate authentication.
requestCert: false,
// This is necessary only if the client uses the self-signed certificate.
ca: [ fs.readFileSync('client-cert.pem') ],
rejectUnauthorized: false
};
var server = ts.createServer(function(socket) {
console.log('connected');
socket.on('data', function(data) {
console.log('data', data);
if (data.length === 9) {
console.log('upgrading to TLS');
socket.upgrade(options, function() {
console.log('upgraded to TLS');
});
}
});
});
server.listen(9999);
client.js:
var ts = require('./tls_socket');
var fs = require('fs');
var crypto = require('crypto');
var options = {
// These are necessary only if using the client certificate authentication
key: fs.readFileSync('client-key.pem'),
cert: fs.readFileSync('client-cert.pem'),
// This is necessary only if the server uses the self-signed certificate
ca: [ fs.readFileSync('server-cert.pem') ],
rejectUnauthorized: false
};
var socket = ts.connect(9999, 'localhost', function() {
console.log('secured');
});
process.stdin.on('data', function(data) {
console.log('sending:', data);
socket.write(data);
if (data.length === 9) {
socket.upgrade(options);
}
});
tls_socket.js:
"use strict";
/*----------------------------------------------------------------------------------------------*/
/* Obtained and modified from http://js.5sh.net/starttls.js on 8/18/2011. */
/*----------------------------------------------------------------------------------------------*/
var tls = require('tls');
var crypto = require('crypto');
var util = require('util');
var net = require('net');
var stream = require('stream');
var SSL_OP_ALL = require('constants').SSL_OP_ALL;
// provides a common socket for attaching
// and detaching from either main socket, or crypto socket
function pluggableStream(socket) {
stream.Stream.call(this);
this.readable = this.writable = true;
this._timeout = 0;
this._keepalive = false;
this._writeState = true;
this._pending = [];
this._pendingCallbacks = [];
if (socket)
this.attach(socket);
}
util.inherits(pluggableStream, stream.Stream);
pluggableStream.prototype.pause = function () {
if (this.targetsocket.pause) {
this.targetsocket.pause();
this.readable = false;
}
}
pluggableStream.prototype.resume = function () {
if (this.targetsocket.resume) {
this.readable = true;
this.targetsocket.resume();
}
}
pluggableStream.prototype.attach = function (socket) {
var self = this;
self.targetsocket = socket;
self.targetsocket.on('data', function (data) {
self.emit('data', data);
});
self.targetsocket.on('connect', function (a, b) {
self.emit('connect', a, b);
});
self.targetsocket.on('secureConnection', function (a, b) {
self.emit('secureConnection', a, b);
self.emit('secure', a, b);
});
self.targetsocket.on('secure', function (a, b) {
self.emit('secureConnection', a, b);
self.emit('secure', a, b);
});
self.targetsocket.on('end', function () {
self.writable = self.targetsocket.writable;
self.emit('end');
});
self.targetsocket.on('close', function (had_error) {
self.writable = self.targetsocket.writable;
self.emit('close', had_error);
});
self.targetsocket.on('drain', function () {
self.emit('drain');
});
self.targetsocket.on('error', function (exception) {
self.writable = self.targetsocket.writable;
self.emit('error', exception);
});
self.targetsocket.on('timeout', function () {
self.emit('timeout');
});
if (self.targetsocket.remotePort) {
self.remotePort = self.targetsocket.remotePort;
}
if (self.targetsocket.remoteAddress) {
self.remoteAddress = self.targetsocket.remoteAddress;
}
};
pluggableStream.prototype.clean = function (data) {
if (this.targetsocket && this.targetsocket.removeAllListeners) {
this.targetsocket.removeAllListeners('data');
this.targetsocket.removeAllListeners('secureConnection');
this.targetsocket.removeAllListeners('secure');
this.targetsocket.removeAllListeners('end');
this.targetsocket.removeAllListeners('close');
this.targetsocket.removeAllListeners('error');
this.targetsocket.removeAllListeners('drain');
}
this.targetsocket = {};
this.targetsocket.write = function () {};
};
pluggableStream.prototype.write = function (data, encoding, callback) {
if (this.targetsocket.write) {
return this.targetsocket.write(data, encoding, callback);
}
return false;
};
pluggableStream.prototype.end = function (data, encoding) {
if (this.targetsocket.end) {
return this.targetsocket.end(data, encoding);
}
}
pluggableStream.prototype.destroySoon = function () {
if (this.targetsocket.destroySoon) {
return this.targetsocket.destroySoon();
}
}
pluggableStream.prototype.destroy = function () {
if (this.targetsocket.destroy) {
return this.targetsocket.destroy();
}
}
pluggableStream.prototype.setKeepAlive = function (bool) {
this._keepalive = bool;
return this.targetsocket.setKeepAlive(bool);
};
pluggableStream.prototype.setNoDelay = function (/* true||false */) {
};
pluggableStream.prototype.setTimeout = function (timeout) {
this._timeout = timeout;
return this.targetsocket.setTimeout(timeout);
};
function pipe(pair, socket) {
pair.encrypted.pipe(socket);
socket.pipe(pair.encrypted);
pair.fd = socket.fd;
var cleartext = pair.cleartext;
cleartext.socket = socket;
cleartext.encrypted = pair.encrypted;
cleartext.authorized = false;
function onerror(e) {
if (cleartext._controlReleased) {
cleartext.emit('error', e);
}
}
function onclose() {
socket.removeListener('error', onerror);
socket.removeListener('close', onclose);
}
socket.on('error', onerror);
socket.on('close', onclose);
return cleartext;
}
function createServer(cb) {
var serv = net.createServer(function (cryptoSocket) {
var socket = new pluggableStream(cryptoSocket);
socket.upgrade = function (options, cb) {
console.log("Upgrading to TLS");
socket.clean();
cryptoSocket.removeAllListeners('data');
// Set SSL_OP_ALL for maximum compatibility with broken clients
// See http://www.openssl.org/docs/ssl/SSL_CTX_set_options.html
if (!options) options = {};
// TODO: bug in Node means we can't do this until it's fixed
// options.secureOptions = SSL_OP_ALL;
var sslcontext = crypto.createCredentials(options);
var pair = tls.createSecurePair(sslcontext, true, true, false);
var cleartext = pipe(pair, cryptoSocket);
pair.on('error', function(exception) {
socket.emit('error', exception);
});
pair.on('secure', function() {
var verifyError = (pair.ssl || pair._ssl).verifyError();
console.log("TLS secured.");
if (verifyError) {
cleartext.authorized = false;
cleartext.authorizationError = verifyError;
} else {
cleartext.authorized = true;
}
var cert = pair.cleartext.getPeerCertificate();
if (pair.cleartext.getCipher) {
var cipher = pair.cleartext.getCipher();
}
socket.emit('secure');
if (cb) cb(cleartext.authorized, verifyError, cert, cipher);
});
cleartext._controlReleased = true;
socket.cleartext = cleartext;
if (socket._timeout) {
cleartext.setTimeout(socket._timeout);
}
cleartext.setKeepAlive(socket._keepalive);
socket.attach(socket.cleartext);
};
cb(socket);
});
return serv;
}
if (require('semver').gt(process.version, '0.7.0')) {
var _net_connect = function (options) {
return net.connect(options);
}
}
else {
var _net_connect = function (options) {
return net.connect(options.port, options.host);
}
}
function connect(port, host, cb) {
var options = {};
if (typeof port === 'object') {
options = port;
cb = host;
}
else {
options.port = port;
options.host = host;
}
var cryptoSocket = _net_connect(options);
var socket = new pluggableStream(cryptoSocket);
socket.upgrade = function (options) {
socket.clean();
cryptoSocket.removeAllListeners('data');
// Set SSL_OP_ALL for maximum compatibility with broken servers
// See http://www.openssl.org/docs/ssl/SSL_CTX_set_options.html
if (!options) options = {};
// TODO: bug in Node means we can't do this until it's fixed
// options.secureOptions = SSL_OP_ALL;
var sslcontext = crypto.createCredentials(options);
var pair = tls.createSecurePair(sslcontext, false);
socket.pair = pair;
var cleartext = pipe(pair, cryptoSocket);
pair.on('error', function(exception) {
socket.emit('error', exception);
});
pair.on('secure', function() {
var verifyError = (pair.ssl || pair._ssl).verifyError();
console.log("client TLS secured.");
if (verifyError) {
cleartext.authorized = false;
cleartext.authorizationError = verifyError;
} else {
cleartext.authorized = true;
}
if (cb) cb();
socket.emit('secure');
});
cleartext._controlReleased = true;
socket.cleartext = cleartext;
if (socket._timeout) {
cleartext.setTimeout(socket._timeout);
}
cleartext.setKeepAlive(socket._keepalive);
socket.attach(socket.cleartext);
console.log("client TLS upgrade in progress, awaiting secured.");
};
return (socket);
}
exports.connect = connect;
exports.createConnection = connect;
exports.Server = createServer;
exports.createServer = createServer;
tls.connect() doesn't support the server doing the upgrade unfortunately.
You have to use code similar to how Haraka does it - basically creating your own shim using a SecurePair.
See here for the code we use: https://github.com/baudehlo/Haraka/blob/master/tls_socket.js#L171
STARTTLS Client-Server is trivial to implement in node, but it be a little buggy so, we need to go around.
For Server of STARTTLS we need do it:
// sock come from: net.createServer(function(sock) { ... });
sock.removeAllListeners('data');
sock.removeAllListeners('error');
sock.write('220 Go ahead' + CRLF);
sock = new tls.TLSSocket(sock, { secureContext : tls.createSecureContext({ key: cfg.stls.key, cert: cfg.stls.cert }), rejectUnauthorized: false, isServer: true });
sock.setEncoding('utf8');
// 'secureConnect' event is buggy :/ we need to use 'secure' here.
sock.on('secure', function() {
// STARTTLS is done here. sock is a secure socket in server side.
sock.on('error', parseError);
sock.on('data', parseData);
});
For Client of STARTTLS we need do it:
// sock come from: net.connect(cfg.port, curr.exchange);
// here we already read the '220 Go ahead' from the server.
sock.removeAllListeners('data');
sock.removeAllListeners('error');
sock = tls.connect({ socket: sock, secureContext : tls.createSecureContext({ key: cfg.stls.key, cert: cfg.stls.cert }), rejectUnauthorized: false });
sock.on('secureConnect', function() {
// STARTTLS is done here. sock is a secure socket in client side.
sock.on('error', parseError);
sock.on('data', parseData);
// Resend the helo message.
sock.write(helo);
});

Node.js EventEmitter error

I have an error when trying to inherit EvenEmitter
/* Consumer.js */
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var Consumer = function() {};
Consumer.prototype = {
// ... functions ...
findById: function(id) {
this.emit('done', this);
}
};
util.inherits(Consumer, EventEmitter);
module.exports = Consumer;
/* index.js */
var consumer = new Consumer();
consumer.on('done', function(result) {
console.log(result);
}).findById("50ac3d1281abba5454000001");
/* ERROR CODE */
{"code":"InternalError","message":"Object [object Object] has no method 'findById'"}
I've tried almost everything and still dont work
A couple of things. You are overwriting the prototype rather than extending it. Also, move the util.inherits() call before you add the new method:
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var Consumer = function Consumer() {}
util.inherits(Consumer, EventEmitter);
Consumer.prototype.findById = function(id) {
this.emit('done', this);
console.log('found');
};
var c = new Consumer();
c.on('done', function(result) {
console.log(result);
});
c.findById("50ac3d1281abba5454000001");

Resources