So I want to be able to share methods across different node.js processes created using the cluster module.
If I run the code below I can share the method server.handleRequest across the child processes, however If I uncomment //server.test(); in the second file and try to use the method test in the original process, node crashes.
"use strict";
var os = require('os');
var http = require('http');
var cluster = require('cluster');
function testMethod() {
console.log('Test');
}
function handleRequest(req, res) {
res.writeHead(200);
res.end("This answer comes from the process " + process.pid);
}
var createServer = function createServer(opts) {
var server = {};
server.test = testMethod;
server.handleRequest = handleRequest;
if (cluster.isMaster) {
var cpuCount = require('os').cpus().length;
for (var i = 0; i < cpuCount; i += 1) {
cluster.fork();
}
return server;
} else {
// Create HTTP server.
http.Server(function(req, res) {
server.handleRequest(req, res);
}).listen(8080);
}
}
module.exports = {
createServer: createServer,
};
The second file that includes the above file.
"use strict";
var router = require('./test.js');
var server = router.createServer();
//server.test();
But If I don't use the cluster module I can use the test method outside the factory function with out crashing. So how do I share methods created in factory functions across all node processes, while using the cluster module? And why do the child processes execute test when only the original process calls server.test()?
var http = require('http');
function testMethod() {
console.log('Test');
}
function handleRequest(req, res) {
res.writeHead(200);
res.end("This answer comes from the process " + process.pid);
}
var createServer = function createServer(opts) {
var server = {};
server.test = testMethod;
server.handleRequest = handleRequest;
http.Server(function(req, res) {
server.handleRequest(req, res);
}).listen(8080);
return server;
}
module.exports = {
createServer: createServer,
};
Related
i want to send a websocket, using express-ws out from a different controller, not by route and I have the following code in the server.js:
var SocketController = require('./routes/ws.routes');
var app = express();
var server = require('http').createServer(app);
var expressWs = require('express-ws')(app);
app.ws('/', SocketController.socketFunction);
the SocketController looks like that:
exports.socketFunction = function (ws, req) {
ws.on('message', function(msg) {
console.log(msg);
ws.send("hello");
});
}
Is it possible to call the
ws.send()
event from another controller? How do i get the "ws" object?
thanks!
You will have to store your sockets in memory. To access stored sockets from different modules, you can store references for these sockets in a separate module.
For example, you can create a module socketCollection that stores all the active sockets.
socketCollection.js:
exports.socketCollection = [];
You can import this module where you define your web socket server:
var socketCollection = require('./socketCollection');
var SocketController = require('./routes/ws.routes');
var app = express();
var server = require('http').createServer(app);
var expressWs = require('express-ws')(app);
expressWs.getWss().on('connection', function(ws) {
socketCollection.push({
id: 'someRandomSocketId',
socket: ws,
});
});
app.ws('/', SocketController.socketFunction);
Now every time new client connects to the server, it's reference will be saved to 'socketCollection'
You can access these clients from any module by importing this array
var socketCollection = require('./socketCollection');
var ws = findSocket('someRandomSocketId', socketCollection);
var findSocket = function(id, socketCollection) {
var result = socketCollection.filter(function(x){return x.id == id;} );
return result ? result[0].socket : null;
};
I'm using node with express and socket.io to create a game server. I've put the game server in a extra module.
I have the problem that I can't access the game server object within the io event.
How do I solve this problem or should I structure my project differently?
// gameServer.js
'use strict'
var io = require('socket.io');
var socketioJwt = require('socketio-jwt');
var config = require('../config.js');
class GameServer {
constructor(http) {
this.io = new io(http);
this.connectedUsers = [];
this.initSocket();
}
initSocket() {
console.log('users', this.connectedUsers);
this.io.on('connection', this.onConnection);
}
onConnection(socket) {
console.log('users', this.connectedUsers); // undefined
}
}
I initialize the server like that:
'use strict'
var express = require('express');
var app = express();
var http = require('http').Server(app);
// init game server
var gameServerApp = new GameServer(http);
http.listen(3000, function() {
console.log('listening on *:3000');
});
I assume this is a common question but I can't figure out how to solve it.
You can bind this to this.onConnection
initSocket() {
console.log('users', this.connectedUsers);
this.io.on('connection', this.onConnection.bind(this));
}
I am aiming for ease of use in my unit tests, I have created a master_test.js file that I will include below. In it, I simply require other test.js files as a function and run them. An issue I had is that Mocha does not detect any unit tests to be run unless I encase my startup in a describe() block, so I am forced to have a master describe encase my application to resolve that. My problem now is that in order to run the other tests, I have to call a callback() after a done(). Is there any easy way to fix my problem?
function app_setup() {
options.database = 'testing';
it('app launched successfully', function(done) {
require('../app.js').initialize(0, options, function(err, app) {
remove_all_entities(function() {
app.set('port', port);
var server = app.listen(app.get('port'), function() {
console.log('Express server listening on port ' + server.address().port);
//ISSUE LOCATION, NEED TO CALL run_tests() callback after done()//
done();
run_tests();
});
});
});
});
}
function run_tests() {
var database = require('../database.js');
var entity_controller_test = require('./controllers/entity_controller_test.js').entity_controller_test;
var login_test = require('./controllers/login_test.js').login_test;
var token_access_test = require('./controllers/token_access_test.js').token_access_test;
var token_auth_test = require('./controllers/auth_token_test.js').token_auth_test;
var business_rules_insert = require('./business_rules/basic_database_rules.js').business_rules_insert_test;
var logout_test = require('./controllers/logout_test.js').logout_test;
var schema_override = require('./business_rules/schema_overrides').schema_overrides;
var aggregation_test = require('./entity_specific/aggregation').aggregation_test;
var tests = [login_test, aggregation_test, logout_test];
async.series(tests, function() {
test_teardown(done);
});
}
function test_teardown(done) {
remove_all_entities(done);
};
I've had no trouble testing my own route handlers but in this case I want to test express's static handler. I can't for the life of me figure out why it's hanging. Clearly there's some callback I'm missing or some event I need to emit.
I tried to make the smallest example I could.
var events = require('events');
var express = require('express');
var stream = require('stream');
var util = require('util');
function MockResponse(callback) {
stream.Writable.call(this);
this.headers = {};
this.statusCode = -1;
this.body = undefined;
this.setHeader = function(key, value) {
this.headers[key] = value;
}.bind(this);
this.on('finish', function() {
console.log("finished response");
callback();
});
};
util.inherits(MockResponse, stream.Writable);
MockResponse.prototype._write = function(chunk, encoding, done) {
if (this.body === undefined) {
this.body = "";
}
this.body += chunk.toString(encoding !== 'buffer' ? encoding : undefined);
done();
};
function createRequest(req) {
var emitter = new events.EventEmitter();
req.on = emitter.on.bind(emitter);
req.once = emitter.once.bind(emitter);
req.addListener = emitter.addListener.bind(emitter);
req.emit = emitter.emit.bind(emitter);
return req;
};
describe('test', function() {
var app;
before(function() {
app = express();
app.use(express.static(__dirname));
});
it('gets test.js', function(done) {
var req = createRequest({
url: "http://foo.com/test.js",
method: 'GET',
headers: {
},
});
var res = new MockResponse(responseDone);
app(req, res);
function responseDone() {
console.log("done");
done();
}
});
});
Setup,
mkdir foo
cd foo
mkdir test
cat > test/test.js # copy and paste code above
^D
npm install express
npm install mocha
node node_modules/mocha/bin/mocha --recursive
it just times out.
What am I missing?
I also tried making the request a Readable stream. No change
var events = require('events');
var express = require('express');
var stream = require('stream');
var util = require('util');
function MockResponse(callback) {
stream.Writable.call(this);
this.headers = {};
this.statusCode = -1;
this.body = undefined;
this.setHeader = function(key, value) {
this.headers[key] = value;
}.bind(this);
this.on('finish', function() {
console.log("finished response");
callback();
});
};
util.inherits(MockResponse, stream.Writable);
MockResponse.prototype._write = function(chunk, encoding, done) {
if (this.body === undefined) {
this.body = "";
}
this.body += chunk.toString(encoding !== 'buffer' ? encoding : undefined);
done();
};
function MockMessage(req) {
stream.Readable.call(this);
var self = this;
Object.keys(req).forEach(function(key) {
self[key] = req[key];
});
}
util.inherits(MockMessage, stream.Readable);
MockMessage.prototype._read = function() {
this.push(null);
};
describe('test', function() {
var app;
before(function() {
app = express();
app.use(express.static(__dirname));
});
it('gets test.js', function(done) {
var req = new MockMessage({
url: "http://foo.com/test.js",
method: 'GET',
headers: {
},
});
var res = new MockResponse(responseDone);
app(req, res);
function responseDone() {
console.log("done");
done();
}
});
});
I've still been digging. Look inside static-server I see it creates a Readable stream by calling fs.createReadStream. It does effectively
var s = fs.createReadStream(filename);
s.pipe(res);
So trying that myself works just fine
it('test stream', function(done) {
var s = fs.createReadStream(__dirname + "/test.js");
var res = new MockResponse(responseDone);
s.pipe(res);
function responseDone() {
console.log("done");
done();
}
});
I thought maybe it's something about express waiting for the input stream to finish but that doesn't seem to be it either. If I consume the mock input stream with the response it works just fine
it('test msg->res', function(done) {
var req = new MockMessage({});
var res = new MockResponse(responseDone);
req.pipe(res);
function responseDone() {
console.log("done");
done();
}
});
Any insight what I might be missing would be helpful
Note: while suggestions for 3rd party mocking libraries are appreciated I'm still really looking to understand what I'm missing to do it myself. Even if I eventually switch to some library I still want to know why this isn't working.
I found two issues that prevent the finish callback from being executed.
serve-static uses send module which is used to create file readstream from the path and pipe it to res object. But that module uses on-finished module which checks if finished attribute is set to false in response object, otherwise it destroys the file readstream. So filestream never gets a chance to emit data event.
express initialization overwrites the response object prototype. So the default stream methods like end() method is overwritten by http response prototype:
exports.init = function(app){
return function expressInit(req, res, next){
...
res.__proto__ = app.response;
..
};
};
To prevent this, I added another middleware right before static middleware to reset it back to MockResponse prototype:
app.use(function(req, res, next){
res.__proto__ = MockResponse.prototype; //change it back to MockResponse prototype
next();
});
Here are the changes made to make it work with MockResponse:
...
function MockResponse(callback) {
...
this.finished = false; // so `on-finished` module doesn't emit finish event prematurely
//required because of 'send' module
this.getHeader = function(key) {
return this.headers[key];
}.bind(this);
...
};
...
describe('test', function() {
var app;
before(function() {
app = express();
//another middleware to reset the res object
app.use(function(req, res, next){
res.__proto__ = MockResponse.prototype;
next();
});
app.use(express.static(__dirname));
});
...
});
EDIT:
As #gman pointed out, it is possible to use direct property instead of prototype method. In that case the extra middleware to overwrite prototype isn't necessary:
function MockResponse(callback) {
...
this.finished = false; // so `on-finished` module doesn't emit finish event prematurely
//required because of 'send' module
this.getHeader = function(key) {
return this.headers[key];
}.bind(this);
...
//using direct property for _write, write, end - since all these are changed when prototype is changed
this._write = function(chunk, encoding, done) {
if (this.body === undefined) {
this.body = "";
}
this.body += chunk.toString(encoding !== 'buffer' ? encoding : undefined);
done();
};
this.write = stream.Writable.prototype.write;
this.end = stream.Writable.prototype.end;
};
It appears my answer is not complete. For some reason the app works only if the file is not found. First thing to debug is do the following in your shell (or cmd):
export DEBUG=express:router,send
then run the test, you'll get more info.
Meanwhile I am still looking into this, for now, ignore my answer below.
----------- ignore this till I verify that it does work -----------
It seems like express static does not favor the absolute path you give it (__dirname).
Try:
app.use(express.static('.'));
and it will work. Note that your current dir for the mocha runner is 'test/'
I have to admit this is quite a mistery. I tried 'fulling' it by doing:
app.use(express.static(__dirname + '/../test')
but still it didn't work. Even specifying a full path did not solve this. Strange.
In the following code I'm expecting console.log to output the data that is passed along with the custom emitter 'output' but that's not occurring. From what I can tell Looper.prototype.output is called properly from withing the server handler but it's not responding to the emitter 'output' event that's defined in Looper.prototype.run. Why isn't my output event handler recognizing these events?
var express = require('express');
var http = require('http');
var spawn = require('child_process').spawn;
var util = require('util');
var fs = require('fs');
var EventEmitter = require("events").EventEmitter;
var sys = require("sys");
function Looper(req) {
this.req = req;
EventEmitter.call(this);
}
sys.inherits(Looper, EventEmitter);
Looper.prototype.run = function() {
var cmd = spawn('./flow',[this.req]); // <-- script that outputs req every second
cmd.stdout.setEncoding('utf8');
cmd.stdout.on('data', function(data) {
this.emit('output',data);
});
}
Looper.prototype.output = function(callback) {
this.on('output', function(data) {
return callback(data.trim());
});
}
var looper = new Looper('blah');
looper.run();
var app = express();
var webServer = http.createServer(app);
app.use(express.static(__dirname + '/public'));
app.get('/', function(req, res) {
res.send(
"<h1>hello world</h1>"
);
looper.output(function(res) {
console.log('blah');
console.log(res);
});
});
webServer.listen(3000);
Looper.prototype.run = function() {
var cmd = spawn('./flow',[this.req]); // <-- script that outputs req every second
cmd.stdout.setEncoding('utf8');
cmd.stdout.on('data', function(data) {
this.emit('output',data);
// ^ not what you think it is.
});
}
I think that this is not what you think it is in that callback. You need to capture the value of this outside of the callback first.
Looper.prototype.run = function() {
var self = this; // save this
var cmd = spawn('./flow',[this.req]); // <-- script that outputs req every second
cmd.stdout.setEncoding('utf8');
cmd.stdout.on('data', function(data) {
self.emit('output',data); // use previously saved value of this
});
}
Otherwise, this would default to the global object, and when the global object emits an event, noone is listening to it.